#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "libsys.h"
#include "mips_gfx.h"

#define USE_HW_BLITTER

// -------------------------------------------------------------
// Functions
// -------------------------------------------------------------
void __gfx_block_set_8(UINT32* pDst, UINT32 value)
{
	__asm__ volatile
	(
		"sw   %[value], 0(%[pDst])\n"
		"sw   %[value], 4(%[pDst])\n"
		"sw   %[value], 8(%[pDst])\n"
		"sw   %[value], 12(%[pDst])\n"
		"sw   %[value], 16(%[pDst])\n"
		"sw   %[value], 20(%[pDst])\n"
		"sw   %[value], 24(%[pDst])\n"
		"sw   %[value], 28(%[pDst])\n"
		:
		: [pDst] "r" (pDst), [value] "r" (value)
	);
}

void __gfx_block_copy_8(UINT32* pDst, UINT32* pSrc)
{
	 __asm__ volatile
	(
		".set noreorder\n"
		".set nomacro\n"
		"lw   $s0, 0(%[pSrc])\n"
		"lw   $s1, 4(%[pSrc])\n"
		"sw   $s0, 0(%[pDst])\n"
		"lw   $s0, 8(%[pSrc])\n"
		"sw   $s1, 4(%[pDst])\n"
		"lw   $s1, 12(%[pSrc])\n"
		"sw   $s0, 8(%[pDst])\n"
		"lw   $s0, 16(%[pSrc])\n"
		"sw   $s1, 12(%[pDst])\n"
		"lw   $s1, 20(%[pSrc])\n"
		"sw   $s0, 16(%[pDst])\n"
		"lw   $s0, 24(%[pSrc])\n"
		"sw   $s1, 20(%[pDst])\n"
		"lw   $s1, 28(%[pSrc])\n"
		"sw   $s0, 24(%[pDst])\n"
		"sw   $s1, 28(%[pDst])\n"
		".set macro\n"
		".set reorder\n"
		:
		: [pDst] "r" (pDst), [pSrc] "r" (pSrc)
		: "s0", "s1"
	);
}

void __gfx_copy_32(UINT32* pDst, UINT32* pSrc, UINT32 nblocks)
{
	while (nblocks)
	{
		__gfx_block_copy_8(pDst, pSrc);
		pDst += 8;
		pSrc += 8;
		nblocks--;
	}

}

void GFX_DirtyRectSetClean(buf_t *pBuf)
{
	pBuf->rect.xmin = pBuf->w;
	pBuf->rect.xmax = 0;
	pBuf->rect.ymin = pBuf->h;
	pBuf->rect.ymax = 0;
	pBuf->is_dirty = 0;
}

void GFX_DirtyRectInit(buf_t *pBuf, UINT32 w, UINT32 h)
{
	pBuf->w = w;
	pBuf->h = h;
	pBuf->regs = (vga_hw_t*)SYS_VGA_CTRL;
	GFX_DirtyRectSetClean(pBuf);
}

void GFX_DirtyRectUpdate(buf_t *pBuf, UINT32 xmin, UINT32 xmax, UINT32 ymin, UINT32 ymax)
{

	if (xmin < pBuf->rect.xmin)
	{
		pBuf->is_dirty = 1;
		pBuf->rect.xmin = xmin;
	}

	if (xmax > pBuf->rect.xmax)
	{
		pBuf->is_dirty = 1;
		pBuf->rect.xmax = xmax;
	}

	if (ymin < pBuf->rect.ymin)
	{
		pBuf->is_dirty = 1;
		pBuf->rect.ymin = ymin;
	}

	if (ymax > pBuf->rect.ymax)
	{
		pBuf->is_dirty = 1;
		pBuf->rect.ymax = ymax;
	}

	if (pBuf->is_dirty && ((pBuf->rect.xmax-pBuf->rect.xmin)%8))
		pBuf->rect.xmax = pBuf->rect.xmin + ((pBuf->rect.xmax-pBuf->rect.xmin)/8 + 1) * 8; // Make len_x multiple of 8

}

void GFX_DirtyRectCopy(buf_t *pBuf)
{
	UINT32 xmin, xmax, ymin, ymax, x, y;
	UINT32 *pDst, *pDst_end;
	UINT32 *pSrc, *pSrc_end;

	xmin = pBuf->rect.xmin;
	xmax = pBuf->rect.xmax;

	ymin = pBuf->rect.ymin;
	ymax = pBuf->rect.ymax;

	ymin *= pBuf->w;
	ymax *= pBuf->w;

#ifdef USE_HW_BLITTER
	pSrc = (UINT32*)&pBuf->pLast->pFB[xmin + ymin];
	pDst = (UINT32*)&pBuf->pFB[xmin + ymin];

	while (pBuf->regs->pVGA_ctrl & SYS_VGA_BIT_BLIT_BSY);

	pBuf->regs->pVGA_src0 = (UINT32)pSrc;
	pBuf->regs->pVGA_dst = (UINT32)pDst;
	pBuf->regs->pVGA_src0_dimx = (UINT32)4*pBuf->w;
	pBuf->regs->pVGA_dst_dimx = (UINT32)4*pBuf->w;
	pBuf->regs->pVGA_nx = (UINT32)(pBuf->rect.xmax-pBuf->rect.xmin);
	pBuf->regs->pVGA_ny = (UINT32)(pBuf->rect.ymax-pBuf->rect.ymin);
	pBuf->regs->pVGA_ctrl |= SYS_VGA_BIT_BLIT_REQ;

#else

	if (!((xmax-xmin)%8))
	{
		for (y=ymin; y < ymax; y += pBuf->w)
		{
			pSrc = &pBuf->pLast->pFB[xmin + y];
			pSrc_end = &pBuf->pLast->pFB[xmax + y];
			pDst = &pBuf->pFB[xmin + y];
			pDst_end = &pBuf->pFB[xmax + y];
			while(pDst < pDst_end)
			{
				__gfx_block_copy_8(pDst, pSrc);
				pDst += 8;
				pSrc += 8;
			}
		}
		return;
	}

//	fprintf(stderr, "GFX_DirtyRectCopy(): Slow copy!\n");
	for (y=ymin; y < ymax; y += pBuf->w)
	{
		for (x=xmin; x < xmax; x++)
		{
			pBuf->pFB[x + y] = pBuf->pLast->pFB[x + y];
		}
	}
#endif

}

void GFX_init(gfx_t *pObj)
{
	int i;
	buf_t *pBuf;
	UINT32 w, h, fps;

	pObj->regs = (vga_hw_t*)SYS_VGA_CTRL;

	pObj->regs->pVGA_ctrl &= ~SYS_VGA_BIT_MSTEN;

	w = Screen_get_resx();
	h = Screen_get_resy();
	fps = Screen_get_fps();

	printf("VGA screen = %d x %d @ %d fps\n", w, h, fps);

	pObj->w = w;
	pObj->h = h;

	// Init background buffer
	pObj->pBG = (UINT32*)calloc(w * h, sizeof(buf_t));

	// Init framebuffers
	pObj->pBuf = (buf_t*)calloc(1, sizeof(buf_t));
	pBuf = pObj->pBuf;
	pBuf->pFB = (UINT32*)calloc((w+8) * h, sizeof(UINT32));		// w+8 because of dirty rectangle 8 byte min. x-size
	for (i=1; i < GFX_NUM_FRAME_BUFFERS; i++)
	{

		pBuf->pNext = (buf_t*)calloc(1, sizeof(buf_t));
		pBuf->pNext->pLast = pBuf;
		pBuf = pBuf->pNext;
		pBuf->pFB = (UINT32*)calloc((w+8) * h, sizeof(UINT32));	// w+8 because of dirty rectangle 8 byte min. x-size
	}
	pObj->pBuf->pLast = pBuf;
	pBuf->pNext = pObj->pBuf;

	pBuf = pObj->pBuf;
	for (i=0; i < GFX_NUM_FRAME_BUFFERS; i++)
	{
		GFX_DirtyRectInit(pBuf, w, h);

//		fprintf(stderr, "pBuf = %08X, pLast = %08X, pNext = %08X\n", pBuf, pBuf->pLast, pBuf->pNext);
		pBuf = pBuf->pNext;
	}

	pObj->regs->pVGA_front = (UINT32)pObj->pBuf->pLast->pFB;
	pObj->regs->pVGA_back = (UINT32)pObj->pBuf->pLast->pFB;
	pObj->regs->pVGA_ctrl |= SYS_VGA_BIT_MSTEN;


}

void GFX_set_background(gfx_t *pObj, UINT32 *pImage, UINT32 image_nx, UINT32 image_ny, UINT32 image_scale_x, UINT32 image_scale_y)
{

	UINT32 off_x, off_y;
	UINT32 i, j;
	UINT32 *pDst, *pSrc;
	buf_t *pBuf;

	if (!pImage)
		return;

	off_x = (pObj->w-(image_scale_x*image_nx))/2;
	off_y = (pObj->h-(image_scale_y*image_ny))/2;

	pDst = ((UINT32*)pObj->pBG) + pObj->w*off_y + off_x;
	pSrc = pImage;

	for (i=0; i < image_ny*image_nx; i += 8)
	{
		__gfx_block_copy_8(pDst, pSrc);
		pDst += 8;
		pSrc += 8;
	}

	pBuf = pObj->pBuf;

	pDst = pBuf->pFB;
	pSrc = pObj->pBG;

	for (i=0; i < image_ny*image_nx; i += 8)
	{
		__gfx_block_copy_8(pDst, pSrc);
		pDst += 8;
		pSrc += 8;
	}

	do
	{
		GFX_DirtyRectUpdate(pBuf, 0, pObj->w, 0, pObj->h);
		pBuf = pBuf->pNext;

	} while(pBuf != pObj->pBuf);


}

void GFX_frame(gfx_t *pObj, UINT32 sync_mode)
{

	// Wait until back buffer becomes front
	if (sync_mode == GFX_FRAME_SYNC)
	{
		while(pObj->regs->pVGA_front == (UINT32)pObj->pBuf->pFB);
	}

	GFX_copy_front2back(pObj);

	// Invalidate D-Cache after blitting has finished
	while (pObj->regs->pVGA_ctrl & SYS_VGA_BIT_BLIT_BSY);

	DCACHE_invalidate_all();

}

void GFX_show(gfx_t *pObj)
{

	pObj->regs->pVGA_back = (UINT32)pObj->pBuf->pFB;
	pObj->pBuf = pObj->pBuf->pNext;

}

void GFX_copy_front2back(gfx_t *pObj)
{
	buf_t *pBuf;

	pBuf = pObj->pBuf;

	// Lopp over all frame buffers, except front buffer
	while(pBuf != pObj->pBuf->pLast)
	{
		if (pBuf->is_dirty)
		{
//			fprintf(stderr, "Dirty rect [%08X]: w=%d, h=%d\n", (int)pBuf, pBuf->rect.xmax - pBuf->rect.xmin, pBuf->rect.ymax - pBuf->rect.ymin);
			GFX_DirtyRectCopy(pBuf);
			GFX_DirtyRectSetClean(pBuf);
		}
		pBuf = pBuf->pNext;
	}
}

void GFX_get_FB_front(gfx_t *pObj, UINT32 **ppFB)
{
	*ppFB = pObj->pBuf->pLast->pFB;
}

void box_create(box_t *pObj, UINT32 w, UINT32 h)
{
	pObj->w = w;
	pObj->h = h;
}

void GFX_restore(gfx_t *pObj, box_t *pBox, UINT32 posx, UINT32 posy)
{
	UINT32 xmin, xmax, ymin, ymax, x, y;
	UINT32 *pDst, *pDst_end;
	UINT32 *pSrc, *pSrc_end;
	buf_t *pBuf;

	xmin = posx;
	xmax = pBox->w + posx;
	if (xmax > pObj->w)
		xmax = pObj->w;

	ymin = posy;
	ymax = pBox->h + posy;
	if (ymax > pObj->h)
		ymax = pObj->h;

	pBuf = pObj->pBuf->pNext;

	// Lopp over all frame buffers, except current back buffer
	while(pBuf != pObj->pBuf)
	{
		GFX_DirtyRectUpdate(pBuf, xmin, xmax, ymin, ymax);
		pBuf = pBuf->pNext;

	}
	pBuf = pObj->pBuf;

	x = xmax - xmin + 1;
	if (x%2)
		x++;
	y = ymax - ymin + 1;
	if (y%2)
		y++;

	ymin *= pObj->w;
	ymax *= pObj->w;

#ifdef USE_HW_BLITTER
	pSrc = (UINT32*)&pObj->pBG[xmin + ymin];
	pDst = (UINT32*)&pBuf->pFB[xmin + ymin];

	while (pObj->regs->pVGA_ctrl & SYS_VGA_BIT_BLIT_BSY);

	pObj->regs->pVGA_src0 = (UINT32)pSrc;
	pObj->regs->pVGA_dst = (UINT32)pDst;
	pObj->regs->pVGA_src0_dimx = (UINT32)4*pObj->w;
	pObj->regs->pVGA_dst_dimx = (UINT32)4*pObj->w;
	pObj->regs->pVGA_nx = (UINT32)x - 1;
	pObj->regs->pVGA_ny = (UINT32)y - 1;
	pObj->regs->pVGA_ctrl |= SYS_VGA_BIT_BLIT_REQ;

#else

	if (!((xmax-xmin)%8))
	{
		for (y=ymin; y < ymax; y += pBuf->w)
		{
			pSrc = &pObj->pBG[xmin + y];
			pSrc_end = &pObj->pBG[xmax + y];
			pDst = &pBuf->pFB[xmin + y];
			pDst_end = &pBuf->pFB[xmax + y];
			while(pDst < pDst_end)
			{
				__gfx_block_copy_8(pDst, pSrc);
				pDst += 8;
				pSrc += 8;
			}
		}
		return;
	}

//	fprintf(stderr, "GFX_DirtyRectCopy(): Slow copy!\n");
	for (y=ymin; y < ymax; y += pBuf->w)
	{
		for (x=xmin; x < xmax; x++)
		{
			pBuf->pFB[x + y] = pObj->pBG[x + y];
		}
	}
#endif

}

void GFX_box_draw(gfx_t *pObj, box_t *pBox, UINT32 posx, UINT32 posy, UINT32 color)
{
	UINT32 xmin, xmax, ymin, ymax, x, y;
	UINT32 *pFB, *pFB_end;
	buf_t *pBuf;

	xmin = posx;
	xmax = pBox->w + posx;
	if (xmax > pObj->w)
		xmax = pObj->w;

	ymin = posy;
	ymax = pBox->h + posy;
	if (ymax > pObj->h)
		ymax = pObj->h;

	pBuf = pObj->pBuf->pNext;

	// Lopp over all frame buffers, except current back buffer
	while(pBuf != pObj->pBuf)
	{
		GFX_DirtyRectUpdate(pBuf, xmin, xmax, ymin, ymax);
		pBuf = pBuf->pNext;

	}
	pBuf = pObj->pBuf;

	ymin *= pObj->w;
	ymax *= pObj->w;

	while (pObj->regs->pVGA_ctrl & SYS_VGA_BIT_BLIT_BSY);

	if (!((xmax-xmin)%8))
	{
		for (y=ymin; y < ymax; y += pObj->w)
		{
			pFB = &pBuf->pFB[xmin + y];
			pFB_end = &pBuf->pFB[xmax + y];
			while(pFB < pFB_end)
			{
				__gfx_block_set_8(pFB, color);
				pFB += 8;
			}
		}
		return;
	}

//	fprintf(stderr, "GFX_box_draw(): Slow copy!\n");
	for (y=ymin; y < ymax; y += pObj->w)
	{
		for (x=xmin; x < xmax; x++)
		{
			pBuf->pFB[x + y] = color;
		}
	}
}

void GFX_box_draw_bg(gfx_t *pObj, box_t *pBox, UINT32 posx, UINT32 posy, UINT32 color)
{
	UINT32 xmin, xmax, ymin, ymax, x, y;
	UINT32 *pFB, *pFB_end;

	xmin = posx;
	xmax = pBox->w + posx;
	if (xmax > pObj->w)
		xmax = pObj->w;

	ymin = posy;
	ymax = pBox->h + posy;
	if (ymax > pObj->h)
		ymax = pObj->h;

	ymin *= pObj->w;
	ymax *= pObj->w;

	while (pObj->regs->pVGA_ctrl & SYS_VGA_BIT_BLIT_BSY);

	if (!((xmax-xmin)%8))
	{
		for (y=ymin; y < ymax; y += pObj->w)
		{
			pFB = &pObj->pBG[xmin + y];
			pFB_end = &pObj->pBG[xmax + y];
			while(pFB < pFB_end)
			{
				__gfx_block_set_8(pFB, color);
				pFB += 8;
			}
		}
		return;
	}

//	fprintf(stderr, "GFX_box_draw_bg(): Slow copy!\n");
	for (y=ymin; y < ymax; y += pObj->w)
	{
		for (x=xmin; x < xmax; x++)
		{
			pObj->pBG[x + y] = color;
		}
	}
}

void GFX_box_draw_direct(gfx_t *pObj, box_t *pBox, UINT32 posx, UINT32 posy, UINT32 color)
{
	UINT32 xmin, xmax, ymin, ymax, x, y;
	UINT32 *pFB, *pFB_end;
	buf_t *pBuf;

	pBuf = pObj->pBuf->pLast;

	xmin = posx;
	xmax = pBox->w + posx;
	if (xmax > pObj->w)
		xmax = pObj->w;

	ymin = posy;
	ymax = pBox->h + posy;
	if (ymax > pObj->h)
		ymax = pObj->h;

	ymin *= pObj->w;
	ymax *= pObj->w;

	if (!((xmax-xmin)%8))
	{
		for (y=ymin; y < ymax; y += pObj->w)
		{
			pFB = &pBuf->pFB[xmin + y];
			pFB_end = &pBuf->pFB[xmax + y];
			while(pFB < pFB_end)
			{
				__gfx_block_set_8(pFB, color);
				pFB += 8;
			}
		}
		return;
	}

//	fprintf(stderr, "GFX_box_draw_direct(): Slow copy!\n");
	for (y=ymin; y < ymax; y += pObj->w)
	{
		for (x=xmin; x < xmax; x++)
		{
			pBuf->pFB[x + y] = color;
		}
	}
}
