/* Grafitti tool library */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <dos/dos.h>
#include <exec/memory.h>
#include <graphics/view.h>
#include <graphics/gfxbase.h>
#include <graphics/gfxmacros.h>
#include <graphics/copper.h>
#include <graphics/videocontrol.h>
#include <hardware/custom.h>
#include <intuition/intuition.h>
#include <intuition/screens.h>

#define __USE_SYSBASE
#include <proto/exec.h>
#include <proto/graphics.h>
#include <proto/intuition.h>
#include <proto/utility.h>

#include "graffiti_intern.h"
#include "graffiti_font.h"
#include "graffiti_render.h"
#include "graffiti_rect.h"
#include "graffiti_color.h"
#include "graffiti_ellipse.h"

extern struct GfxBase * GfxBase;
extern __far struct Custom custom;

#define GCMD_NOP	    0
#define GCMD_SET_COLOR	    4
#define GCMD_SET_COLMASK    5
#define GCMD_SET_RGB	    6
#define GCMD_SET_READPOS    7
#define GCMD_START_LORES    8
#define GCMD_START_HIRES    24

static struct ColorSpec Colortable_80[] =
{
    0, 0,0,0,
    1, 8,8,9,
    -1,
},
Colortable_160[] =
{
    0, 0,0,0,
    1, 0,0,9,
    2, 8,8,0,
    3, 8,8,9,
    -1,
},
Colortable_320[] =
{
    0,	0,0,0,
    1,	0,0,1,
    2,	0,0,8,
    3,	0,0,9,
    4,	0,8,0,
    5,	0,8,1,
    6,	0,8,8,
    7,	0,8,9,
    8,	8,0,0,
    9,	8,0,1,
    10, 8,0,8,
    11, 8,0,9,
    12, 8,8,0,
    13, 8,8,1,
    14, 8,8,8,
    15, 8,8,9,
    -1,
};

static const __chip UWORD invisiblePointer[] =
{
    0,0, /* reserved */

    0,0, /* transparent image */

    0,0, /* reserved */
};

void Graffiti_BeginCmd (struct GraffitiHandle * gh)
{
    GD(gh)->cmdptr = GD(gh)->cmd;
    memset (GD(gh)->cmd, 0, sizeof (GD(gh)->cmd));
} /* Graffiti_BeginCmd */

void Graffiti_EndCmd (struct GraffitiHandle * gh)
{
    UWORD x;
    register UBYTE * p1, * p2, * p3, * p4, * cmdptr;

    *GD(gh)->cmdptr++ = GD(gh)->startcmd;
    *GD(gh)->cmdptr = 0; /* This might get displayed */
    cmdptr = GD(gh)->cmd;

    p1 = GD(gh)->CommandScreen->RastPort.BitMap->Planes[0];
    p2 = GD(gh)->CommandScreen->RastPort.BitMap->Planes[1];
    p3 = GD(gh)->CommandScreen->RastPort.BitMap->Planes[2];
    p4 = GD(gh)->CommandScreen->RastPort.BitMap->Planes[3];

    /* Wait until we can safely copy the data */
    WaitTOF ();

    /* Make the new commands visible to the graffiti */
    for (x=0; x<80; x++)
    {
	*p1++ = *cmdptr++;
	*p2++ = *cmdptr++;
	*p3++ = *cmdptr++;
	*p4++ = *cmdptr++;
    }

    /* Wait until Graffiti has read the commands */
    WaitTOF ();

} /* Graffiti_EndCmd */

void Graffiti_Nop (struct GraffitiHandle * gh)
{
    if (GD(gh)->cmdptr+2 > &GD(gh)->cmd[320])
    {
	Graffiti_EndCmd (gh);
	Graffiti_BeginCmd (gh);
    }

    *GD(gh)->cmdptr++ = GCMD_NOP;
    *GD(gh)->cmdptr++ = 0;
} /* Graffiti_Nop */

void Graffiti_SetMask (struct GraffitiHandle * gh, UBYTE mask)
{
    if (GD(gh)->cmdptr+2 > &GD(gh)->cmd[320])
    {
	Graffiti_EndCmd (gh);
	Graffiti_BeginCmd (gh);
    }

    *GD(gh)->cmdptr++ = GCMD_SET_COLMASK;
    *GD(gh)->cmdptr++ = mask;
} /* Graffiti_SetMask */

void Graffiti_SetRGB (struct GraffitiHandle * gh, UBYTE col, UWORD r, UWORD g,
    UWORD b)
{
    /* printf ("SetRGB (%3d, %3d %3d %3d)\n", col, r, g, b); */

    GD(gh)->colorcache = FALSE;
    gh->FindBestMatch = Graffiti_FindBestMatch_x;

    if (GD(gh)->cmdptr+8 > &GD(gh)->cmd[320])
    {
	Graffiti_EndCmd (gh);
	Graffiti_BeginCmd (gh);
    }

    /* Make sure the user cannot change color 0 from black */
    if (!col)
	r = g = b = 0;

    GD(gh)->gh.ColorMap[col].F = 1;
    GD(gh)->gh.ColorMap[col].R = r;
    GD(gh)->gh.ColorMap[col].G = g;
    GD(gh)->gh.ColorMap[col].B = b;

    *GD(gh)->cmdptr++ = GCMD_SET_COLOR;
    *GD(gh)->cmdptr++ = col;
    *GD(gh)->cmdptr++ = GCMD_SET_RGB;
    *GD(gh)->cmdptr++ = r>>2;
    *GD(gh)->cmdptr++ = GCMD_SET_RGB;
    *GD(gh)->cmdptr++ = g>>2;
    *GD(gh)->cmdptr++ = GCMD_SET_RGB;
    *GD(gh)->cmdptr++ = b>>2;
} /* Graffiti_SetRGB */

struct
{
    UWORD CmdRegs;
    UWORD CmdReg[256];
    UWORD CmdVal[256];

    UWORD GfxRegs;
    UWORD GfxReg[256];
    UWORD GfxVal[256];
} GraffitiConfig;

void ReadConfig (void)
{
    FILE * fh;
    char line[256], *ptr;

    if (!(fh = fopen ("graffiti.prefs", "r")) )
	return;

    while (fgets (line, sizeof (line), fh))
    {
	if (!strnicmp (line, "CMD", 3))
	{
	    GraffitiConfig.CmdReg[GraffitiConfig.CmdRegs] = strtol (line+3,&ptr,0);
	    GraffitiConfig.CmdVal[GraffitiConfig.CmdRegs] = strtol (ptr,NULL,0);
	    GraffitiConfig.CmdRegs ++;
	}
	else if (!strnicmp (line, "GFX", 3))
	{
	    GraffitiConfig.GfxReg[GraffitiConfig.GfxRegs] = strtol (line+3,&ptr,0);
	    GraffitiConfig.GfxVal[GraffitiConfig.GfxRegs] = strtol (ptr,NULL,0);
	    GraffitiConfig.GfxRegs ++;
	}
    }

    fclose (fh);
} /* ReadConfig */

struct GraffitiHandle * Graffiti_Init (Tag tag, ...)
{
    return Graffiti_InitA ((struct TagItem *)&tag);
} /* Graffiti_Init */

struct GraffitiHandle * Graffiti_InitA (struct TagItem * tags)
{
    struct TagItem * tag;
    struct GraffitiData * gd;
    struct ColorSpec * colors;
    UWORD bplcon0 = GENLOCK_AUDIO | 0x0201;
    UWORD modeid;
    int col, t;
    UWORD mode;

    if (!(gd = AllocMem (sizeof (struct GraffitiData), MEMF_ANY|MEMF_CLEAR)) )
	return NULL;

    ReadConfig ();

    if ( (tag = FindTagItem (GTI_RESOLUTION, tags)) )
	mode = tag->ti_Data;
    else
	mode = GRAFFITI_RES_320;

    switch (mode & GRAFFITI_RES_MASK)
    {
    case GRAFFITI_RES_80:
	if (mode & GRAFFITI_RES_LACE)
	    modeid = HIRESLACE_KEY;
	else
	    modeid = HIRES_KEY;

	gd->Depth = 1;
	gd->Width = 640;
	colors = Colortable_80;
	gd->startcmd = GCMD_START_LORES;
	bplcon0 |= HIRES;

	gd->gh.GetPixel = (GetPixelCBType)Graffiti_GetPixel_1;
	gd->gh.SetPixel = (SetPixelCBType)Graffiti_SetPixel_1;
	gd->gh.SetPixelColor = (SetPixelColorCBType)Graffiti_SetPixelColor_1;
	gd->gh.SetPixel3D = (SetPixel3DCBType)Graffiti_SetPixel3D_1;
	gd->gh.SetPixel3DColor = (SetPixel3DColorCBType)Graffiti_SetPixel3DColor_1;
	gd->gh.FillRect = (RectCBType)Graffiti_FillRect_x;

	break;

    case GRAFFITI_RES_160:
	if (mode & GRAFFITI_RES_LACE)
	    modeid = HIRESLACE_KEY;
	else
	    modeid = HIRES_KEY;

	gd->Depth = 2;
	gd->Width = 640;
	colors = Colortable_160;
	gd->startcmd = GCMD_START_LORES;
	bplcon0 |= HIRES;

	gd->gh.GetPixel = (GetPixelCBType)Graffiti_GetPixel_2;
	gd->gh.SetPixel = (SetPixelCBType)Graffiti_SetPixel_2;
	gd->gh.SetPixelColor = (SetPixelColorCBType)Graffiti_SetPixelColor_2;
	gd->gh.SetPixel3D = (SetPixel3DCBType)Graffiti_SetPixel3D_2;
	gd->gh.SetPixel3DColor = (SetPixel3DColorCBType)Graffiti_SetPixel3DColor_2;
	gd->gh.FillRect = (RectCBType)Graffiti_FillRect_x;

	break;

    case GRAFFITI_RES_320:
	if (mode & GRAFFITI_RES_LACE)
	    modeid = HIRESLACE_KEY;
	else
	    modeid = HIRES_KEY;

	gd->Depth = 4;
	gd->Width = 640;
	colors = Colortable_320;
	gd->startcmd = GCMD_START_LORES;
	bplcon0 |= HIRES;

	gd->gh.GetPixel = (GetPixelCBType)Graffiti_GetPixel_4;
	gd->gh.SetPixel = (SetPixelCBType)Graffiti_SetPixel_4;
	gd->gh.SetPixelColor = (SetPixelColorCBType)Graffiti_SetPixelColor_4;
	gd->gh.SetPixel3D = (SetPixel3DCBType)Graffiti_SetPixel3D_4;
	gd->gh.SetPixel3DColor = (SetPixel3DColorCBType)Graffiti_SetPixel3DColor_4;
	gd->gh.FillRect = (RectCBType)Graffiti_FillRect_4;

	break;

    case GRAFFITI_RES_640:
	if (mode & GRAFFITI_RES_LACE)
	    modeid = SUPERLACE_KEY;
	else
	    modeid = SUPER_KEY;

	gd->Depth = 4;
	gd->Width = 1280;
	colors = Colortable_320;
	gd->startcmd = GCMD_START_HIRES;
	bplcon0 |= 0x0040; /* Superhires bit */

	gd->gh.GetPixel = (GetPixelCBType)Graffiti_GetPixel_4;
	gd->gh.SetPixel = (SetPixelCBType)Graffiti_SetPixel_4;
	gd->gh.SetPixelColor = (SetPixelColorCBType)Graffiti_SetPixelColor_4;
	gd->gh.SetPixel3D = (SetPixel3DCBType)Graffiti_SetPixel3D_4;
	gd->gh.SetPixel3DColor = (SetPixel3DColorCBType)Graffiti_SetPixel3DColor_4;
	gd->gh.FillRect = (RectCBType)Graffiti_FillRect_4;

	break;
    }

    bplcon0 |= (gd->Depth << 12) & 0x7000;
    bplcon0 |= GfxBase->system_bplcon0;

    gd->gh.FindBestMatch  = Graffiti_FindBestMatch_x;
    gd->gh.DrawPixel	  = (DrawPixelCBType)Graffiti_DrawPixel_x;
    gd->gh.DrawRect	  = (RectCBType)Graffiti_DrawRect_x;
    gd->gh.DrawLine	  = (RectCBType)Graffiti_DrawLine_x;
    gd->gh.DrawEllipse	  = (RectCBType)Graffiti_DrawEllipse_x;
    gd->gh.FillEllipse	  = (RectCBType)Graffiti_FillEllipse_x;
    gd->gh.DrawGenEllipse = (GenEllipseCBType)Graffiti_DrawGenEllipse_x;
    gd->gh.FillGenEllipse = (GenEllipseCBType)Graffiti_FillGenEllipse_x;
    gd->gh.DrawText	  = (TextCBType)Graffiti_DrawText_x;

    if (mode & GRAFFITI_RES_LACE)
    {
	gd->Height = 512;
	bplcon0 |= LACE;
    }
    else
	gd->Height = 256;

    /* Open a screen for the commands */
    if (!(gd->CommandScreen = OpenScreenTags (NULL,
	SA_Left,	0,
	SA_Top, 	0,
	SA_Width,	640,
	SA_Height,	3,
	SA_Depth,	4,
	SA_Exclusive,	TRUE,
	SA_Interleaved, FALSE,
	SA_ShowTitle,	FALSE,
	SA_Quiet,	TRUE,
	SA_Type,	CUSTOMSCREEN,
	SA_DisplayID,	HIRES_KEY,
	SA_Colors,	Colortable_320,
	TAG_END)
    ) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    if (!(gd->SharedPort = CreateMsgPort ()) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    /* Open a window on the CommandScreen */
    if (!(gd->CommandWindow = OpenWindowTags (NULL,
	WA_Left,	    0,
	WA_Top, 	    0,
	WA_Width,	    640,
	WA_Height,	    3,
	WA_CustomScreen,    gd->CommandScreen,
	WA_Borderless,	    TRUE,
	WA_NoCareRefresh,   TRUE,
	WA_SimpleRefresh,   TRUE,
	WA_Activate,	    TRUE,
	WA_IDCMP,	    0L,
	TAG_END)
    ) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    gd->CommandWindow->UserPort = gd->SharedPort;

    ModifyIDCMP (gd->CommandWindow, IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY);

    /* Set a transparent cursor for the command window to ensure the
	mouse won't get in the way when the Graffiti wants to read
	its commands. */
    SetPointer (gd->CommandWindow, (UWORD *)invisiblePointer, 1, 1, 1, 0);

    /* Open a screen for the graphics */
    if (!(gd->Buffer[0] = OpenScreenTags (NULL,
	SA_Left,	0,
	SA_Top, 	3,
	SA_Width,	gd->Width,
	SA_Height,	gd->Height,
	SA_Depth,	gd->Depth,
	SA_Exclusive,	TRUE,
	SA_Interleaved, FALSE,
	SA_ShowTitle,	FALSE,
	SA_Quiet,	TRUE,
	SA_Type,	CUSTOMSCREEN,
	SA_DisplayID,	modeid,
	SA_Colors,	colors,
	SA_MinimizeISG, TRUE,
	SA_Parent,	gd->CommandScreen,
	TAG_END)
    ) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    /* Open a window on the CommandScreen */
    if (!(gd->BufferWindow[0] = OpenWindowTags (NULL,
	WA_Left,	    0,
	WA_Top, 	    0,
	WA_Width,	    gd->Width,
	WA_Height,	    gd->Height,
	WA_CustomScreen,    gd->Buffer[0],
	WA_Borderless,	    TRUE,
	WA_NoCareRefresh,   TRUE,
	WA_SimpleRefresh,   TRUE,
	WA_IDCMP,	    0L,
	TAG_END)
    ) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    gd->BufferWindow[0]->UserPort = gd->SharedPort;

    ModifyIDCMP (gd->BufferWindow[0], IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY);

    /* This block does the real magic: it sets the GENLOCK_AUDIO bit in
	BPLCON0 which signals the Graffiti, that it should do its job. */
    gd->uCopListCommand = AllocMem (sizeof (struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);

    if (!gd->uCopListCommand)
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    CINIT (gd->uCopListCommand, 256); /* Some space for what we need */
    /* Attach our command at the end of the systems' list. */
    CWAIT (gd->uCopListCommand, -2, 0);
    /* write value for Hires/Audio/4bpl into bplcon0 */
    CMOVE (gd->uCopListCommand, custom.bplcon0, 0xC301);
#if 0
    CMOVE (gd->uCopListCommand, custom.color[0], 0x0F00);
#endif
{
    int t;

    for (t=0; t<GraffitiConfig.CmdRegs; t++)
    {
	CMOVE (gd->uCopListCommand,
	    ((UBYTE*)&custom)[GraffitiConfig.CmdReg[t]],
	    GraffitiConfig.CmdVal[t]);
    }
}
    CEND (gd->uCopListCommand);

    /* Same for graphics screen */
    gd->uCopListBuffer[0] = AllocMem (sizeof (struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);

    if (!gd->uCopListBuffer[0])
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    CINIT (gd->uCopListBuffer[0], 4); /* Some space for what we need */
    /* Wait for first line of graphics screen */
    CWAIT (gd->uCopListBuffer[0], -1, 0);
    CMOVE (gd->uCopListBuffer[0], custom.bplcon0, bplcon0); /* write new value for bplcon0 into register */
{
    int t;

    for (t=0; t<GraffitiConfig.GfxRegs; t++)
    {
	CMOVE (gd->uCopListCommand,
	    ((UBYTE*)&custom)[GraffitiConfig.GfxReg[t]],
	    GraffitiConfig.GfxVal[t]);
    }
}
    CEND (gd->uCopListBuffer[0]);

    if ( (tag = FindTagItem (GTI_BUFFER, tags)) )
	gd->BufferMode = tag->ti_Data;
    else
	gd->BufferMode = GRAFFITI_BUFFER_SINGLE;

    gd->NBuffers = 1;
    gd->VisibleBuffer = 0;
    gd->RenderBuffer = 0;

    switch (gd->BufferMode)
    {
    case GRAFFITI_BUFFER_SINGLE:
	/* nop */
	break;

    case GRAFFITI_BUFFER_TRIPLE:
	gd->NBuffers ++;

	/* Open a screen for the graphics */
	if (!(gd->Buffer[2] = OpenScreenTags (NULL,
	    SA_Left,	    0,
	    SA_Top,	    3,
	    SA_Width,	    gd->Width,
	    SA_Height,	    gd->Height,
	    SA_Depth,	    gd->Depth,
	    SA_Exclusive,   TRUE,
	    SA_Interleaved, FALSE,
	    SA_ShowTitle,   FALSE,
	    SA_Quiet,	    TRUE,
	    SA_Type,	    CUSTOMSCREEN,
	    SA_DisplayID,   modeid,
	    SA_Colors,	    colors,
	    SA_MinimizeISG, TRUE,
	    SA_Parent,	    gd->CommandScreen,
	    TAG_END)
	) )
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	/* Open a window on the CommandScreen */
	if (!(gd->BufferWindow[2] = OpenWindowTags (NULL,
	    WA_Left,		0,
	    WA_Top,		0,
	    WA_Width,		gd->Width,
	    WA_Height,		gd->Height,
	    WA_CustomScreen,	gd->Buffer[2],
	    WA_Borderless,	TRUE,
	    WA_NoCareRefresh,	TRUE,
	    WA_SimpleRefresh,	TRUE,
	    WA_IDCMP,		0L,
	    TAG_END)
	) )
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	gd->BufferWindow[2]->UserPort = gd->SharedPort;

	ModifyIDCMP (gd->BufferWindow[2], IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY);

	gd->uCopListBuffer[2] = AllocMem (sizeof (struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);

	if (!gd->uCopListBuffer[2])
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	CINIT (gd->uCopListBuffer[2], 4); /* Some space for what we need */
	/* Wait for first line of graphics screen */
	CWAIT (gd->uCopListBuffer[2], -1, 0);
	CMOVE (gd->uCopListBuffer[2], custom.bplcon0, bplcon0); /* write new value for bplcon0 into register */
	CEND (gd->uCopListBuffer[2]);

	/* fall through */

    case GRAFFITI_BUFFER_MULTI: /* Multibuffering uses two buffers */
    case GRAFFITI_BUFFER_DOUBLE:
	gd->NBuffers ++;

	/* Open a screen for the graphics */
	if (!(gd->Buffer[1] = OpenScreenTags (NULL,
	    SA_Left,	    0,
	    SA_Top,	    3,
	    SA_Width,	    gd->Width,
	    SA_Height,	    gd->Height,
	    SA_Depth,	    gd->Depth,
	    SA_Exclusive,   TRUE,
	    SA_Interleaved, FALSE,
	    SA_ShowTitle,   FALSE,
	    SA_Quiet,	    TRUE,
	    SA_Type,	    CUSTOMSCREEN,
	    SA_DisplayID,   modeid,
	    SA_Colors,	    colors,
	    SA_MinimizeISG, TRUE,
	    SA_Parent,	    gd->CommandScreen,
	    TAG_END)
	) )
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	/* Open a window on the CommandScreen */
	if (!(gd->BufferWindow[1] = OpenWindowTags (NULL,
	    WA_Left,		0,
	    WA_Top,		0,
	    WA_Width,		gd->Width,
	    WA_Height,		gd->Height,
	    WA_CustomScreen,	gd->Buffer[1],
	    WA_Borderless,	TRUE,
	    WA_NoCareRefresh,	TRUE,
	    WA_SimpleRefresh,	TRUE,
	    WA_IDCMP,		0L,
	    TAG_END)
	) )
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	gd->BufferWindow[1]->UserPort = gd->SharedPort;

	ModifyIDCMP (gd->BufferWindow[1], IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY);

	gd->uCopListBuffer[1] = AllocMem (sizeof (struct UCopList), MEMF_PUBLIC|MEMF_CLEAR);

	if (!gd->uCopListBuffer[1])
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	CINIT (gd->uCopListBuffer[1], 4); /* Some space for what we need */
	/* Wait for first line of graphics screen */
	CWAIT (gd->uCopListBuffer[1], -1, 0);
	CMOVE (gd->uCopListBuffer[1], custom.bplcon0, bplcon0); /* write new value for bplcon0 into register */
	CEND (gd->uCopListBuffer[1]);

	break;
    }

    /* Init lists for Multibuffering */
    NewList (&gd->UsedBufferList);
    NewList (&gd->NewBufferList);

    /* Make sure the right screen is visible */
    ScreenDepth (gd->Buffer[0], SDEPTH_TOFRONT|SDEPTH_INFAMILY, NULL);

    Forbid ();
    gd->CommandScreen->ViewPort.UCopIns = gd->uCopListCommand;
    for (t=0; t<gd->NBuffers; t++)
	gd->Buffer[t]->ViewPort.UCopIns = gd->uCopListBuffer[t];
    Permit ();

    /* Make sure the Graffiti doesn't choke when we enable the screen */
    gd->CommandScreen->RastPort.BitMap->Planes[0][0] = gd->startcmd;

    VideoControlTags (gd->CommandScreen->ViewPort.ColorMap,
	VTAG_USERCLIP_SET, NULL,
	TAG_END
    );
    for (t=0; t<gd->NBuffers; t++)
    {
	VideoControlTags (gd->Buffer[t]->ViewPort.ColorMap,
	    VTAG_USERCLIP_SET, NULL,
	    VC_NoColorPaletteLoad, TRUE,
	    TAG_END
	);
    }
    RethinkDisplay ();

    /* Init colormap */
    for (col=0; col<256; col++)
	gd->gh.ColorMap[col].F = 0;

    /* How many pixels per line */
    gd->Width = gd->Width * gd->Depth / 8;

    /* How many bytes per line */
    gd->BPR = gd->Buffer[0]->RastPort.BitMap->BytesPerRow;

    /* Cache plane pointers for faster access */
    gd->Plane[0] = gd->Buffer[0]->RastPort.BitMap->Planes[0];
    gd->Plane[1] = gd->Buffer[0]->RastPort.BitMap->Planes[1];
    gd->Plane[2] = gd->Buffer[0]->RastPort.BitMap->Planes[2];
    gd->Plane[3] = gd->Buffer[0]->RastPort.BitMap->Planes[3];

    /* Default cliprect */
    gd->ClipLeft = 0;
    gd->ClipTop = 0;
    gd->ClipRight = gd->Width-1;
    gd->ClipBottom = gd->Height-1;

    /* Init card */
    Graffiti_BeginCmd (GH(gd));
    Graffiti_SetMask (GH(gd), 255);
    Graffiti_SetRGB (GH(gd), 0, 0,0,0);
    Graffiti_SetRGB (GH(gd), 1, 255,255,255);
    Graffiti_SetRGB (GH(gd), 2, 100,100,100);
    Graffiti_SetRGB (GH(gd), 3, 200,200,200);
    Graffiti_EndCmd (GH(gd));

    NewList (&gd->FontList);

    if (!(gd->CurrentFont = ReadFont (gd, "Topaz")) )
    {
	Graffiti_Exit (GH(gd));

	return NULL;
    }

    if ( (tag = FindTagItem (GTI_3D, tags)) && tag->ti_Data)
    {
	ULONG cnt;
	ULONG * ptr;

	cnt = gd->Width * gd->Height;

	gd->ZBuffer = AllocMem (cnt << 1, MEMF_ANY);

	if (!gd->ZBuffer)
	{
	    Graffiti_Exit (GH(gd));

	    return NULL;
	}

	ptr = (ULONG *)gd->ZBuffer;

	for (cnt>>=2; cnt; cnt--)
	{
	    *ptr++ = 0x80008000;
	    *ptr++ = 0x80008000;
	}
    }

    return GH(gd);
} /* Graffiti_InitA */

BOOL Graffiti_GetAttr (struct GraffitiHandle * gh, Tag tag, ULONG * attr)
{
    switch (tag)
    {
    case GTI_WIDTH:
	*attr = GD(gh)->Width;
	break;

    case GTI_HEIGHT:
	*attr = GD(gh)->Height;
	break;

    case GTI_3D:
	*attr = (GD(gh)->ZBuffer != NULL);
	break;

    default:
	return FALSE;
    }

    return TRUE;
} /* Graffiti_GetAttr */

static void CloseWindowSafely (struct Window * win)
{
    struct IntuiMessage * msg, * succ;

    Forbid ();

    for (msg=(struct IntuiMessage *)win->UserPort->mp_MsgList.lh_Head;
	succ=(struct IntuiMessage *)msg->ExecMessage.mn_Node.ln_Succ;
	msg=succ)
    {
	if (msg->IDCMPWindow == win)
	{
	    Remove ((struct Node *)msg);

	    ReplyMsg ((struct Message *)msg);
	}
    }

    win->UserPort = NULL;

    ModifyIDCMP (win, 0L);

    Permit ();

    CloseWindow (win);
} /* CloseWindowSafely */

void Graffiti_Exit (struct GraffitiHandle * gh)
{
    int t;

    if (gh)
    {
	if (GD(gh)->ZBuffer)
	    FreeMem (GD(gh)->ZBuffer, GD(gh)->Width * GD(gh)->Height << 1);

	for (t=0; t<GD(gh)->NBuffers; t++)
	{
	    if (GD(gh)->Buffer[t])
	    {
		if (GD(gh)->BufferWindow[t])
		    CloseWindowSafely (GD(gh)->BufferWindow[t]);

		if (GD(gh)->uCopListBuffer[t])
		    FreeVPortCopLists (&GD(gh)->Buffer[t]->ViewPort);

		CloseScreen (GD(gh)->Buffer[t]);
	    }
	}

	if (GD(gh)->CommandWindow)
	{
	    ClearPointer (GD(gh)->CommandWindow);
	    CloseWindowSafely (GD(gh)->CommandWindow);
	}

	if (GD(gh)->uCopListCommand)
	    FreeVPortCopLists (&GD(gh)->CommandScreen->ViewPort);

	if (GD(gh)->CommandScreen)
	    CloseScreen (GD(gh)->CommandScreen);

	if (GD(gh)->SharedPort)
	    DeleteMsgPort (GD(gh)->SharedPort);

	while (GD(gh)->FontList.lh_Head->ln_Succ)
	    FreeFont (GD(gh), (struct GraffitiFont *)GD(gh)->FontList.lh_Head);

	FreeMem (gh, sizeof (struct GraffitiData));
    }

} /* Graffiti_Exit */

struct IntuiMessage * Graffiti_CheckEvent (struct GraffitiHandle * gh)
{
    return (struct IntuiMessage *)GetMsg (GD(gh)->SharedPort);
} /* Graffiti_WaitEvent */

struct IntuiMessage * Graffiti_WaitEvent (struct GraffitiHandle * gh)
{
    struct IntuiMessage * im;

    if ((im = Graffiti_CheckEvent (gh)))
	return im;

    Wait (1L << GD(gh)->SharedPort->mp_SigBit | SIGBREAKF_CTRL_C);

    return Graffiti_CheckEvent (gh);
} /* Graffiti_WaitEvent */

void Graffiti_SetClip (struct GraffitiHandle * gh, UWORD left, UWORD top,
    UWORD right, UWORD bottom)
{
    if (left == 0xFFFF)
	left = 0;

    if (top == 0xFFFF)
	top = 0;

    if (right == 0xFFFF)
	right = GD(gh)->Width-1;

    if (bottom == 0xFFFF)
	bottom = GD(gh)->Height-1;

    if (left > right || left >= GD(gh)->Width ||
	top > bottom || top >= GD(gh)->Height)
	return;

    GD(gh)->ClipLeft = left;
    GD(gh)->ClipRight = right;
    GD(gh)->ClipTop = top;
    GD(gh)->ClipBottom = bottom;

} /* Graffiti_SetClip */
