/*
 *  control.c - main source file
 *  Copyright (C) 1997 Aki Laukkanen
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software Foundation,
 *  Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 */

/*
 *  includes
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/execbase.h>
#include <utility/tagitem.h>

#include <intuition/intuition.h>
#include <intuition/screens.h>

#include <graphics/gfx.h>
#include <graphics/displayinfo.h>
#include <graphics/modeid.h>
#include <graphics/videocontrol.h>
#include <graphics/copper.h>
#include <graphics/gfxmacros.h>

#include <dos/dos.h>
#include <libraries/dosextens.h>
#include <hardware/intbits.h>

#ifdef __SASC
#define __USE_SYSBASE
#endif

#ifdef __GNUC__
#define __NOLIBBASE__
#endif

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

#include "macros.h"
#include "shapeextvideo.h"
#include "evd_context.h"
#include "evd_prefs.h"

/*
 *  prototypes
 */

ULONG REGARGS SAVEDS evd_openscreen(REG(a0, struct TagItem* input), REG(a1, struct TagItem* output), REG(a6, struct Library* UtilityBase));
ULONG REGARGS SAVEDS evd_closescreen(REG(a2, context* ct), REG(a6, struct Library* UtilityBase));
extern UBYTE REGARGS mmu_mark(REG(a0, UBYTE* start), REG(d0, ULONG length), REG(d1, ULONG cm), REG(a6, struct ExecBase* SysBase));
extern void* REGARGS MakeDescriptors(REG(a0, UBYTE* start), REG(d0, ULONG length),REG(a6, struct ExecBase* SysBase));

void DeinitDriver(context* ct);
void ShowRequester(context* ct, char* text);
BOOL ParseInput(context* ct, struct TagItem *input);
BOOL CreateDisplay(context* ct);
BOOL LoadPrefs(context* ct);
void* REGARGS AllocAligned(REG(d0, ULONG), REG(d1, ULONG));
void REGARGS FreeAligned(REG(a0,void* ));
void SetOutput(context* ct, struct TagItem* output);

extern void REGARGS (*c2p1_reloc(   REG(a0, context* ct)))(void);
extern void REGARGS (*c2p2_reloc(   REG(a0, context* ct)))(void);
extern void REGARGS (*c2p4_reloc(   REG(a0, context* ct)))(void);
extern void REGARGS (*c2p8_reloc(   REG(a0, context* ct)))(void);
extern void REGARGS (*c2p15_reloc(  REG(a0, context* ct)))(void);
extern void REGARGS c2p1_deinit(    REG(a0, context* ct));
extern void REGARGS c2p2_deinit(    REG(a0, context* ct));
extern void REGARGS c2p4_deinit(    REG(a0, context* ct));
extern void REGARGS c2p8_deinit(    REG(a0, context* ct));
extern void REGARGS c2p15_deinit(   REG(a0, context* ct));

/*
 *  global variables
 */

struct ExecBase* SysBase;
extern struct palette_entry internal_palette;
extern struct Interrupt vblank_interrupt;

/*
 *  functions
 */

/*
 *  SYNOPSIS
 *      ULONG __asm evd_openscreen( register __a0 struct TagItem* input,
 *                                  register __a1 struct TagItem* output,
 *                                  register __a6 struct Library* UtilityBase );
 *
 *  FUNCTION
 *      Opens a display for the Shapeshifter MacIntosh emulation.
 *
 */

ULONG REGARGS SAVEDS
evd_openscreen(REG(a0, struct TagItem* input),REG(a1, struct TagItem* output),REG(a6, struct Library* UtilityBase))
{
	context* ct;
	SysBase = *(struct ExecBase** )4l;

	ct = AllocVec(sizeof (*ct), MEMF_ANY|MEMF_CLEAR);
	if (!ct)
		return -1;

	ct->SysBase = SysBase;
	ct->DOSBase = (struct DosLibrary* )OpenLibrary("dos.library", 37);
	ct->GfxBase = (struct GfxBase* )OpenLibrary("graphics.library", 37);
	ct->IntuitionBase = (struct IntuitionBase* )OpenLibrary("intuition.library", 37);
	ct->UtilityBase = UtilityBase;

	if (!ct->DOSBase || !ct->GfxBase || !ct->IntuitionBase)
	{
		DeinitDriver(ct);
		return -1;
	}

	if (!(SysBase->AttnFlags & AFF_68040))
	{
		ShowRequester(ct, "TurboEVD needs M68040 or better");
		DeinitDriver(ct);
		return -1;
	}

	if (!ParseInput(ct, input))
	{
		DeinitDriver(ct);
		return -1;
	}

	if (!LoadPrefs(ct))
		ShowRequester(ct, "Error while loading configuration file, using defaults instead");

	if (!CreateDisplay(ct))
	{
		DeinitDriver(ct);
		return -1;
	}

	SetOutput(ct, output);

	return 0;
}

/*
 *  SYNOPSIS
 *      ULONG __asm
 *      evd_closescreen(    register __a2 APTR,
 *                          register __a6 struct Library* UtilityBase);
 *  FUNCTION
 *      Closes the display defined in the context.
 *
 */

ULONG REGARGS SAVEDS
evd_closescreen(REG(a2, context* ct), REG(a6, struct Library* UtilityBase))
{
	DeinitDriver(ct);
	return 0;
}

/*
 *  SYNOPSIS
 *      BOOL CreateDisplay(context* ct);
 *
 *  FUNCTION
 *      1) allocates bitmap and chunky buffer(s)
 *      2) opens screen
 *      3) initializes tables and MMU information
 *      4) initializes c2p routines and all that stuff
 */

BOOL
CreateDisplay(context* ct)
{
	UBYTE* ch;
	LONG i, bmsize;

	/* palette for HAM8 screens */
	const static ULONG pal[] = {
		0x00010000, 0x00000000, 0x00000000, 0x00000000, 0x00000000
	};

	/* tags for setting the borderblank on */
	const static struct TagItem bb[] = {
			{ VTAG_BORDERBLANK_SET, TRUE },
			{ VTAG_END_CM, NULL }
	};

	/* multiplier for the fast buffer */
	const static LONG fast[] = {
		1, 2, 4, 8, 16, 32
	};

	/* multiplier for the chip buffer */
	const static LONG bpl[] = {
		1, 2, 4, 8, 8, 8
	};

	/* this is orred with DisplayID */
	const static ULONG ham[] = {
		0, 0, 0, 0, HAM_KEY, HAM_KEY
	};

	struct IntuitionBase* IntuitionBase = ct->IntuitionBase;
	struct GfxBase* GfxBase = ct->GfxBase;

	ch = AllocAligned(ct->X*ct->Y*bpl[ct->VideoMode]>>3, MEMF_CHIP|MEMF_CLEAR);
	if (!ch)
	{
		ShowRequester(ct, "Cannot allocate chip memory");
		return FALSE;
	}

	InitBitMap(&ct->BitMap, bpl[ct->VideoMode], ct->X, ct->Y);
	ct->BitMap.Flags = BMF_STANDARD|BMF_DISPLAYABLE;

	bmsize = ct->X * ct->Y >> 3;
	ct->BMSize = bmsize << 3;
	for (i = 0 ; i < bpl[ct->VideoMode] ; i ++ )
	{
		ct->BitMap.Planes[i] = ch;
		ch = ch + bmsize;
	}

	ct->ChunkyBuffer = AllocAligned(ct->Size = ct->X*ct->Y*fast[ct->VideoMode]>>3, MEMF_FAST|MEMF_CLEAR);
	ct->DeltaBuffer = AllocAligned(ct->Size, MEMF_FAST|MEMF_CLEAR);

	if (!ct->ChunkyBuffer || !ct->DeltaBuffer)
	{
		ShowRequester(ct, "Cannot allocate memory for the chunky buffer");
		return FALSE;
	}

	ct->PageDesc = MakeDescriptors(ct->ChunkyBuffer, ct->Size, SysBase);
	if (!ct->PageDesc)
	{
		ShowRequester(ct, "MMU tables incompatible or not enough memory for page descriptor cache");
		return FALSE;
	}

	ct->CM1 = mmu_mark(ct->ChunkyBuffer, ct->Size, ct->FCached ? CM_COPYBACK : CM_WRITETHROUGH, SysBase);
	ct->CM2 = mmu_mark(ct->DeltaBuffer, ct->Size, CM_WRITETHROUGH, SysBase);

	if (ct->CImprec)
		ct->CM3 = mmu_mark(ct->BitMap.Planes[0], ct->BMSize, CM_IMPRECISE, SysBase);

	ct->Screen = OpenScreenTags(NULL,
		SA_Width, ct->X,
		SA_Height, ct->Y,
		SA_Depth, bpl[ct->VideoMode],
		SA_BitMap, (ULONG)&ct->BitMap,
		SA_ShowTitle, FALSE,
		SA_Quiet, TRUE,
		SA_Overscan, ct->Overscan,
		SA_DisplayID, ct->DisplayID | ham[ct->VideoMode],
		SA_AutoScroll, TRUE,
		SA_Colors32, (ULONG)pal,
		(ct->BorderBlank ? SA_VideoControl : TAG_IGNORE), (ULONG)bb,
		TAG_END,0);
	if (!ct->Screen)
	{
		ShowRequester(ct, "Cannot open screen");
		return FALSE;
	}

	ct->ViewPort = &ct->Screen->ViewPort;

	switch (ct->VideoMode)
	{
		case VMODE_1BIT:
			ct->C2P = c2p1_reloc(ct);
			break;
		case VMODE_2BIT:
			ct->C2P = c2p2_reloc(ct);
			break;
		case VMODE_4BIT:
			ct->C2P = c2p4_reloc(ct);
			break;
		case VMODE_8BIT:
			ct->C2P = c2p8_reloc(ct);
			break;
		case VMODE_15BIT:
			ct->C2P = c2p15_reloc(ct);
	}

	if (!ct->C2P)
	{
		ShowRequester(ct, "Cannot initialize the c2p routine");
		return FALSE;
	}

	if (ct->OwnRefresh)
	{
		vblank_interrupt.is_Data = ct;
		AddIntServer(INTB_VERTB, &vblank_interrupt);
	}

	return TRUE;
}

/*
 *  SYNOPSIS
 *      void* AllocAligned(ULONG byteSize, ULONG attributes);
 *
 *  FUNCTION
 *      Allocates a memory block with the given size and
 *      attributes. This block will be aligned to a 4096 byte border
 *      (ie. its beginning address has all the lowest 12 bits as zero).
 *
 *  NOTES
 *      Ehh. very ugly. I'll fix it some day.
 *
 */

void* REGARGS SAVEDS
AllocAligned(REG(d0, ULONG byteSize), REG(d1, ULONG attributes))
{
	ULONG mem;
	ULONG* ptr;
	mem = (ULONG)AllocVec(byteSize + 4096 + 4, attributes);
	if (!mem)
		return NULL;

	ptr = (ULONG* )((mem & ~0xfff) + 4096);
	ptr[-1] = 4096 - (mem & 0xfff);
	return ptr;
}

/*
 *  SYNOPSIS
 *      void FreeAligned(void *memoryBlock);
 *
 *  FUNCTION
 *      Deallocates memory allocated by AllocAligned() and only this way
 *      allocated only.
 *
 */

void REGARGS SAVEDS
FreeAligned(REG(a0,void *memoryBlock))
{
	ULONG* ptr = memoryBlock;
	UBYTE* p2 = (UBYTE* )ptr;
	FreeVec(p2 - ptr[-1]);
}

/*
 *  SYNOPSIS
 *      void DeinitDriver(context *ct);
 *
 *  FUNCTION
 *      Deallocate and deinitialize everything.
 */

void
DeinitDriver(context* ct)
{
	struct IntuitionBase* IntuitionBase = ct->IntuitionBase;
	struct GfxBase* GfxBase = ct->GfxBase;

	if (vblank_interrupt.is_Data)
		RemIntServer(INTB_VERTB, &vblank_interrupt);

	if (ct->PrefsTmp)
		FreeVec(ct->PrefsTmp);

	if (ct->PageDesc)
		FreeVec(ct->PageDesc);

	if (ct->C2P)
	{
		switch (ct->VideoMode)
		{
			case VMODE_1BIT:
				c2p1_deinit(ct);
				break;
			case VMODE_2BIT:
				c2p2_deinit(ct);
				break;
			case VMODE_4BIT:
				c2p4_deinit(ct);
				break;
			case VMODE_8BIT:
				c2p8_deinit(ct);
				break;
			case VMODE_15BIT:
				c2p15_deinit(ct);
				break;
		}
	}

	if (ct->ChunkyBuffer)
	{
		mmu_mark(ct->ChunkyBuffer, ct->Size, ct->CM1, SysBase);
		FreeAligned(ct->ChunkyBuffer);
	}

	if (ct->DeltaBuffer)
	{
		mmu_mark(ct->DeltaBuffer, ct->Size, ct->CM2, SysBase);
		FreeAligned(ct->DeltaBuffer);
	}

	if (ct->Screen)
		CloseScreen(ct->Screen);

	if (ct->BitMap.Planes[0])
	{
		if (ct->CImprec)
			mmu_mark(ct->BitMap.Planes[0], ct->BMSize, ct->CM3, SysBase);
		WaitBlit();
		FreeAligned(ct->BitMap.Planes[0]);
	}

	if (ct->IntuitionBase)
		CloseLibrary((struct Library* )ct->IntuitionBase);
	if (ct->GfxBase)
		CloseLibrary((struct Library* )ct->GfxBase);
	if (ct->DOSBase)
		CloseLibrary((struct Library* )ct->DOSBase);
}

/*
 *  SYNOPSIS
 *      void ShowRequester(context* ct, char* text);
 *
 *  FUNCTION
 *      Shows a standard intuition requester with the
 *      message given as parameter.
 */

void
ShowRequester(context* ct, char* text)
{
	struct IntuitionBase* IntuitionBase = ct->IntuitionBase;
	struct EasyStruct req =
	{
		sizeof (struct EasyStruct),
		0,
		"TurboEVD requester",
		NULL,
		"Okay"
	};
															 
	req.es_TextFormat = text;
	EasyRequestArgs(NULL, &req, NULL, NULL);
}

/*
 *  SYNOPSIS
 *      void SetOutput(context* ct, struct TagItem* output);
 *
 *  FUNCTION
 *      Set the output information in tags for Shapeshifter to process.
 */

void SetOutput(context* ct, struct TagItem* output)
{
	struct Library* UtilityBase = ct->UtilityBase;
	struct TagItem* tag;

	if (tag = FindTagItem(SHEV_Context, output))
		tag->ti_Data = (ULONG)ct;

	if (tag = FindTagItem(SHEV_Screen, output))
		tag->ti_Data = (ULONG)ct->Screen;

	if (tag = FindTagItem(SHEV_ScreenBase, output))
		tag->ti_Data = (ULONG)ct->ChunkyBuffer;

	if (tag = FindTagItem(SHEV_BytesPerRow, output))
		tag->ti_Data = (ULONG)ct->Size/ct->Y;

	if (tag = FindTagItem(SHEV_RefreshType, output))
		tag->ti_Data = (ULONG)RTYPE_CUSTOM;
}

/*
 *  SYNOPSIS
 *      BOOL ParseInput(context* ct, struct TagItem* input);
 *
 *  FUNCTION
 *      Parse the input tags which were given to the driver
 *      by Shapeshifter and check their validity.
 *
 */

BOOL
ParseInput(context* ct, struct TagItem* input)
{
	struct Library* UtilityBase = ct->UtilityBase;

	ct->VideoMode = GetTagData( SHEV_VideoMode, VMODE_8BIT, input);

	if (ct->VideoMode == VMODE_24BIT)
	{
		ShowRequester(ct, " Video mode not supported");
		return FALSE;
	}

	ct->X = GetTagData( SHEV_ScreenX, 640, input);
	ct->Y = GetTagData( SHEV_ScreenY, 480, input);

	if (ct->X & 0x3f)
	{
		ShowRequester(ct, "Display width must be a multiple of 64");
		return FALSE;
	}

	ct->DisplayID = GetTagData( SHEV_DisplayID, 0x0, input);
	ct->Overscan = GetTagData( SHEV_OverscanType, 0x0, input);

	return TRUE;
}

/*
 *  SYNOPSIS
 *      BOOL LoadPrefs(context* ct);
 *
 *  FUNCTION
 *      1) Initialize the prefs to defaults
 *      2) Open the prefs file and parse it
 */

BOOL
LoadPrefs(context* ct)
{
	BPTR file;
	BOOL flag = FALSE;
	ULONG i;
	ALIGNED struct FileInfoBlock fib;
	struct TagItem* tags, *tag;
	struct DosLibrary* DOSBase = ct->DOSBase;
	struct Library* UtilityBase = ct->UtilityBase;

	/* defaults */
	ct->FCached = TRUE;
	ct->CImprec = TRUE;
	ct->NumPalettes = 1;
	ct->Palettes = &internal_palette;
	ct->BorderBlank = TRUE;
	ct->OwnRefresh = FALSE;
	ct->RefreshRate = 0;
	ct->UsedPalette = 0;

	file = Open(PREFS_FILENAME_USE, MODE_OLDFILE);
	if (file)
	{
		if (ExamineFH(file, &fib))
		{
			ct->PrefsTmp = tags = AllocVec(fib.fib_Size, MEMF_ANY);
			if (tags)
			{
				i = Read(file, tags, fib.fib_Size);
				if (i == fib.fib_Size && tags->ti_Tag == EVD_Version && tags->ti_Data <= PREFS_VERSION)
				{
					if (tag = FindTagItem(EVD_BBlank, tags)) ct->BorderBlank = tag->ti_Data;
					if (tag = FindTagItem(EVD_OwnRefr, tags)) ct->OwnRefresh = tag->ti_Data;
					if (tag = FindTagItem(EVD_RRate, tags)) ct->RefreshRate = tag->ti_Data;
					if (tag = FindTagItem(EVD_UsedPal, tags)) ct->UsedPalette = tag->ti_Data;
					if (tag = FindTagItem(EVD_NumPals, tags)) ct->NumPalettes =  tag->ti_Data;
					if (tag = FindTagItem(EVD_FCached, tags)) ct->FCached = tag->ti_Data;
					if (tag = FindTagItem(EVD_CImprec, tags)) ct->CImprec = tag->ti_Data;
					if (tag = FindTagItem(EVD_Palette, tags))
					{
						ct->Palettes = (struct palette_entry*) ((char*)tags + tag->ti_Data);
						for ( i = 0 ; i < ct->NumPalettes ; i++ )
						{
							ct->Palettes[i].off_cached_palname = (ULONG)((char*)tags + ct->Palettes[i].off_cached_palname);
							ct->Palettes[i].off_palname = (ULONG)((char*)tags + ct->Palettes[i].off_palname);
						}
					}
					flag = TRUE;
				}
			}
		}
		Close(file);
	}
	return flag;
}

/* kludges for the SAS/C link libraries */

#ifdef __SASC
ULONG _XCEXIT       = NULL;
#endif
