/*
 * Acess2 VGA Controller Driver
 */
#define DEBUG	0
#include <acess.h>
#include <fs_devfs.h>
#include <api_drv_video.h>
#include <modules.h>

// === CONSTANTS ===
#define	VGA_WIDTH	80
#define	VGA_HEIGHT	25

// === PROTOTYPES ===
 int	VGA_Install(char **Arguments);
size_t	VGA_Write(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags);
 int	VGA_IOCtl(tVFS_Node *Node, int Id, void *Data);
Uint8	VGA_int_GetColourNibble(Uint16 col);
Uint16	VGA_int_GetWord(const tVT_Char *Char);
void	VGA_int_SetCursor(Sint16 x, Sint16 y);
// --- 2D Acceleration Functions --
void	VGA_2D_Fill(void *Ent, Uint16 X, Uint16 Y, Uint16 W, Uint16 H, Uint32 Colour);
void	VGA_2D_Blit(void *Ent, Uint16 DstX, Uint16 DstY, Uint16 SrcX, Uint16 SrcY, Uint16 W, Uint16 H);

// === GLOBALS ===
MODULE_DEFINE(0, 0x000A, x86_VGAText, VGA_Install, NULL, NULL);
tVFS_NodeType	gVGA_NodeType = {
	//.Read = VGA_Read,
	.Write = VGA_Write,
	.IOCtl = VGA_IOCtl
	};
tDevFS_Driver	gVGA_DevInfo = {
	NULL, "x86_VGAText",
	{
	.NumACLs = 0,
	.Size = VGA_WIDTH*VGA_HEIGHT*sizeof(tVT_Char),
	.Type = &gVGA_NodeType
	}
};
Uint16	*gVGA_Framebuffer = (void*)( KERNEL_BASE|0xB8000 );
 int	giVGA_BufferFormat = VIDEO_BUFFMT_TEXT;
tDrvUtil_Video_2DHandlers	gVGA_2DFunctions = {
	NULL,
	VGA_2D_Fill,
	VGA_2D_Blit
};

// === CODE ===
/**
 * \fn int VGA_Install(char **Arguments)
 */
int VGA_Install(char **Arguments)
{
	Uint8	byte;
	
	// Enable Bright Backgrounds
	inb(0x3DA);	// Reset flipflop
	outb(0x3C0, 0x30);	// Index 0x10, PAS
	byte = inb(0x3C1);
	byte &= ~8;	// Disable Blink
	outb(0x3C0, byte);	// Write value
	
	
	// Install DevFS
	DevFS_AddDevice( &gVGA_DevInfo );
	
	return MODULE_ERR_OK;
}

/**
 * \brief Writes a string of bytes to the VGA controller
 */
size_t VGA_Write(tVFS_Node *Node, off_t Offset, size_t Length, const void *Buffer, Uint Flags)
{
	if( giVGA_BufferFormat == VIDEO_BUFFMT_TEXT )
	{
		 int	num = Length / sizeof(tVT_Char);
		 int	ofs = Offset / sizeof(tVT_Char);
		 int	i = 0;
		const tVT_Char	*chars = Buffer;
		Uint16	word;
		
		//ENTER("pNode XOffset XLength pBuffer", Node, Offset, Length, Buffer);
		
		for( ; num--; i ++, ofs ++)
		{
			word = VGA_int_GetWord( &chars[i] );
			gVGA_Framebuffer[ ofs ] = word;
		}
		
		//LEAVE('X', Length);
		return Length;
	}
	else if( giVGA_BufferFormat == VIDEO_BUFFMT_2DSTREAM )
	{
		return DrvUtil_Video_2DStream(NULL, Buffer, Length, &gVGA_2DFunctions, sizeof(gVGA_2DFunctions));
	}
	else
	{
		return 0;
	}
}

/**
 * \fn int VGA_IOCtl(tVFS_Node *Node, int Id, void *Data)
 * \brief IO Control Call
 */
int VGA_IOCtl(tVFS_Node *Node, int ID, void *Data)
{
	 int	rv;
	switch(ID)
	{
	case DRV_IOCTL_TYPE:	return DRV_TYPE_VIDEO;
	case DRV_IOCTL_IDENT:	memcpy(Data, "VGA\0", 4);	return 1;
	case DRV_IOCTL_VERSION:	*(int*)Data = 50;	return 1;
	case DRV_IOCTL_LOOKUP:	return 0;
	
	case VIDEO_IOCTL_GETSETMODE:	return 0;	// Mode 0 only
	case VIDEO_IOCTL_FINDMODE:
		if( !CheckMem(Data, sizeof(tVideo_IOCtl_Mode)) )	return -1;
		((tVideo_IOCtl_Mode*)Data)->id = 0;	// Text Only!
	case VIDEO_IOCTL_MODEINFO:
		if( !CheckMem(Data, sizeof(tVideo_IOCtl_Mode)) )	return -1;
		if( ((tVideo_IOCtl_Mode*)Data)->id != 0)	return 0;
		((tVideo_IOCtl_Mode*)Data)->width = VGA_WIDTH*giVT_CharWidth;
		((tVideo_IOCtl_Mode*)Data)->height = VGA_HEIGHT*giVT_CharHeight;
		((tVideo_IOCtl_Mode*)Data)->bpp = 4;
		return 1;
	
	case VIDEO_IOCTL_SETBUFFORMAT:
		if( !CheckMem(Data, sizeof(int)) )	return -1;
		switch( *(int*)Data )
		{
		case VIDEO_BUFFMT_TEXT:
		case VIDEO_BUFFMT_2DSTREAM:
			rv = giVGA_BufferFormat;
			giVGA_BufferFormat = *(int*)Data;
//			Log_Debug("VGA", "Buffer format set to %i", giVGA_BufferFormat);
			return rv;
		default:
			break;
		}
		return -1;
	
	case VIDEO_IOCTL_SETCURSOR:
		VGA_int_SetCursor( ((tVideo_IOCtl_Pos*)Data)->x, ((tVideo_IOCtl_Pos*)Data)->y );
		return 1;
	}
	return 0;
}

/**
 * \fn Uint8 VGA_int_GetColourNibble(Uint16 col)
 * \brief Converts a 12-bit colour into a VGA 4-bit colour
 */
Uint8 VGA_int_GetColourNibble(Uint16 col)
{
	Uint8	ret = 0;
	 int	bright = 0;
	
	col = col & 0xCCC;
	col = ((col>>2)&3) | ((col>>4)&0xC) | ((col>>6)&0x30);
	bright = ( (col & 2 ? 1 : 0) + (col & 8 ? 1 : 0) + (col & 32 ? 1 : 0) ) / 2;
	
	switch(col)
	{
	//	Black
	case 0x00:	ret = 0x0;	break;
	// Dark Grey
	case 0x15:	ret = 0x8;	break;
	// Blues
	case 0x01:
	case 0x02:	ret = 0x1;	break;
	case 0x03:	ret = 0x9;	break;
	// Green
	case 0x04:
	case 0x08:	ret = 0x2;	break;
	case 0x0C:	ret = 0xA;	break;
	// Reds
	case 0x10:
	case 0x20:	ret = 0x4;	break;
	case 0x30:	ret = 0xC;	break;
	// Light Grey
	case 0x2A:	ret = 0x7;	break;
	// White
	case 0x3F:	ret = 0xF;	break;
	
	default:
		ret |= (col & 0x03 ? 1 : 0);
		ret |= (col & 0x0C ? 2 : 0);
		ret |= (col & 0x30 ? 4 : 0);
		ret |= (bright ? 8 : 0);
		break;
	}
	return ret;
}

/**
 * \fn Uint16 VGA_int_GetWord(tVT_Char *Char)
 * \brief Convers a character structure to a VGA character word
 */
Uint16 VGA_int_GetWord(const tVT_Char *Char)
{
	Uint16	ret;
	Uint16	col;
	
	// Get Character
	if(Char->Ch < 128)
		ret = Char->Ch;
	else {
		switch(Char->Ch)
		{
		default:	ret = 0;	break;
		}
	}
	
	col = VGA_int_GetColourNibble(Char->BGCol);
	ret |= col << 12;
	
	col = VGA_int_GetColourNibble(Char->FGCol);
	ret |= col << 8;
	
	return ret;
}

/**
 * \fn void VGA_int_SetCursor(Sint16 x, Sint16 y)
 * \brief Updates the cursor position
 */
void VGA_int_SetCursor(Sint16 x, Sint16 y)
{
	 int	pos = x+y*VGA_WIDTH;
	if(x == -1 || y == -1)
		pos = -1;
	outb(0x3D4, 14);
	outb(0x3D5, pos >> 8);
	outb(0x3D4, 15);
	outb(0x3D5, pos);
}

void VGA_2D_Fill(void *Ent, Uint16 X, Uint16 Y, Uint16 W, Uint16 H, Uint32 Colour)
{
	tVT_Char	ch;
	Uint16	word, *buf;

	X /= giVT_CharWidth;
	W /= giVT_CharWidth;
	Y /= giVT_CharHeight;
	H /= giVT_CharHeight;

	ch.Ch = 0x20;
	ch.BGCol  = (Colour & 0x0F0000) >> (16-8);
	ch.BGCol |= (Colour & 0x000F00) >> (8-4);
	ch.BGCol |= (Colour & 0x00000F);
	ch.FGCol = 0;
	word = VGA_int_GetWord(&ch);

	Log("Fill (%i,%i) %ix%i with 0x%x", X, Y, W, H, word);

	if( X > VGA_WIDTH || Y > VGA_HEIGHT )	return ;
	if( X + W > VGA_WIDTH )	W = VGA_WIDTH - X;
	if( Y + H > VGA_HEIGHT )	H = VGA_HEIGHT - Y;

	buf = gVGA_Framebuffer + Y*VGA_WIDTH + X;

	
	while( H -- ) {
		 int	i;
		for( i = 0; i < W; i ++ )
			*buf++ = word;
		buf += VGA_WIDTH - W;
	}
}

void VGA_2D_Blit(void *Ent, Uint16 DstX, Uint16 DstY, Uint16 SrcX, Uint16 SrcY, Uint16 W, Uint16 H)
{
	Uint16	*src, *dst;

	DstX /= giVT_CharWidth;
	SrcX /= giVT_CharWidth;
	W /= giVT_CharWidth;

	DstY /= giVT_CharHeight;
	SrcY /= giVT_CharHeight;
	H /= giVT_CharHeight;

//	Log("(%i,%i) from (%i,%i) %ix%i", DstX, DstY, SrcX, SrcY, W, H);

	if( SrcX > VGA_WIDTH || SrcY > VGA_HEIGHT )	return ;
	if( SrcX + W > VGA_WIDTH )	W = VGA_WIDTH - SrcX;
	if( SrcY + H > VGA_HEIGHT )	H = VGA_HEIGHT - SrcY;
	if( DstX > VGA_WIDTH || DstY > VGA_HEIGHT )	return ;
	if( DstX + W > VGA_WIDTH )	W = VGA_WIDTH - DstX;
	if( DstY + H > VGA_HEIGHT )	H = VGA_HEIGHT - DstY;


	src = gVGA_Framebuffer + SrcY*VGA_WIDTH + SrcX;
	dst = gVGA_Framebuffer + DstY*VGA_WIDTH + DstX;

	if( src > dst )
	{
		// Simple copy
		while( H-- ) {
			memcpy(dst, src, W*2);
			dst += VGA_WIDTH;
			src += VGA_WIDTH;
		}
	}
	else
	{
		dst += H*VGA_WIDTH;
		src += H*VGA_WIDTH;
		while( H -- ) {
			 int	i;
			dst -= VGA_WIDTH-W;
			src -= VGA_WIDTH-W;
			for( i = W; i --; )	*--dst = *--src;
		}
	}
}
