#include "main.h"
#include "gfx.h"

#include "icon.xpm"

#ifndef COLORBITS
#define COLORBITS 32
#endif

#if COLORBITS == 8
#error "Paletted screen format not supported. (Sorry.)"
#endif

static SDL_Surface *actual_screen = NULL;
Uint8 scale = 1;

SDL_Surface *screen = NULL;
Uint8 gfx_flags = 0;

// Low(er) level screen resolution function.
static inline void set_resolution(int width, int height)
{
	int videoflags;
	const SDL_VideoInfo *videoinfo;

	videoinfo = SDL_GetVideoInfo();
	if(!videoinfo)
		fatal_error("SDL video error: %s\n", SDL_GetError());

#if COLORBITS == 8
	videoflags = SDL_HWPALETTE;
#else
	videoflags  = 0;
#endif
	if (gfx_flags & GFX_FULLSCREEN)
		videoflags |= SDL_FULLSCREEN;
	//if (scale == 1 && videoflags & SDL_FULLSCREEN)
		videoflags |= SDL_DOUBLEBUF;
	if(videoinfo->hw_available)
		videoflags |= SDL_HWSURFACE;
	else
		videoflags |= SDL_SWSURFACE;
	if(videoinfo->blit_hw)
		videoflags |= SDL_HWACCEL;

	actual_screen = SDL_SetVideoMode(width,height,COLORBITS,videoflags);
	if(!actual_screen)
		fatal_error("SDL video error: %s\n", SDL_GetError());
}

// Loads an image by name and returns its surface.
SDL_Surface *Gfx_Load(const char *name, bool *wii, bool allow_wii)
{
	bool wiidata = false;
	char *path = find_image(name,allow_wii && gfx_flags & GFX_WIIDATA);
	if (!path)
		fatal_error("Could not find image '%s'\n",name);
	if (!strncmp(path,"wiidata/",8))
		wiidata = true;
	if (wii)
		*wii = wiidata;
	SDL_Surface *surf = SDL_LoadBMP(path);
	free(path);
	//if (!surf->format->palette) // I forget why.
		//fatal_error("The image '%s' MUST be paletted.\n(8-bit color depth)",name);
	SDL_SetColorKey(surf,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(surf->format,0,0,0));
	/*if (wiidata || !surf->format->palette)
		SDL_SetColorKey(surf,SDL_SRCCOLORKEY|SDL_RLEACCEL,SDL_MapRGB(surf->format,0,0,0));
	else
		SDL_SetColorKey(surf,SDL_SRCCOLORKEY|SDL_RLEACCEL,0);*/
	return surf;
}

// Loads the loading image and blits it to the screen.
static inline void make_load_screen(void)
{
	bool wiidata;
	gfx_flags |= GFX_WIIDATA;
	SDL_Surface *load_sign = Gfx_Load("Loading",&wiidata,true);
	gfx_flags &= ~GFX_WIIDATA;
	if (!wiidata)
		load_sign = Gfx_Scale(load_sign,2);

	// Now draw it to the screen.
	//SDL_Color bg = {0x00,0x00,0x00,0xFF};
	SDL_Color bg = load_sign->format->palette->colors[0];
	SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,bg.r,bg.g,bg.b));
	SDL_Rect pos = {screen->w/2-load_sign->w/2,screen->h/2-load_sign->h/2,0,0};
	SDL_BlitSurface(load_sign,NULL,screen,&pos);

	// All done!
	SDL_FreeSurface(load_sign);
}

// Scales a surface up to 2x, 3x, etc.
// A very basic and speedy function.
SDL_Surface *Gfx_Scale(SDL_Surface *surf, Uint8 scale)
{
	if (!surf)
		return NULL;
	SDL_Surface *orig = SDL_ConvertSurface(surf, surf->format, SDL_SWSURFACE);
	SDL_Surface *scalesurf = SDL_CreateRGBSurface(SDL_SWSURFACE,surf->w*scale,surf->h*scale,surf->format->BitsPerPixel,surf->format->Rmask,surf->format->Gmask,surf->format->Bmask,surf->format->Amask);
	if (surf->format->palette)
		SDL_SetColors(scalesurf,surf->format->palette->colors,0,surf->format->palette->ncolors);
	if (surf->flags & SDL_SRCCOLORKEY)
		SDL_SetColorKey(scalesurf,SDL_SRCCOLORKEY|SDL_RLEACCEL,surf->format->colorkey);

	SDL_LockSurface(orig);
	SDL_LockSurface(scalesurf);

	// Draw the pixels from the original surface to the new one, adding in copies where necessary.
	Uint16 x,y,v,h,b;
	Uint8 *src = orig->pixels;
	Uint8 *dst = scalesurf->pixels;
	Uint8 bpp = orig->format->BytesPerPixel;
	for(y = 0; y < (Uint32)orig->h; y++) // rows
		for(v = 0; v < scale; v++) // vertical scale
			for(x = 0; x < (Uint32)orig->w; x++) // columns
				for(h = 0; h < scale; h++) // horizontal scale
					for (b = 0; b < bpp; b++) // bytes per pixel
						dst[y*scalesurf->pitch*scale+v*scalesurf->pitch+x*bpp*scale+h*bpp+b] = src[y*orig->pitch+x*bpp+b];
	// TODO: Insert "Holy fuck that's complex!" here. Maybe 'fix' it. Maybe.
	// It does its job effeciently enough.

	SDL_UnlockSurface(scalesurf);
	SDL_UnlockSurface(orig);

	SDL_FreeSurface(orig);
	return scalesurf;
}

// Horizontally flip any surface
SDL_Surface *Gfx_HFlip(SDL_Surface *surf)
{
	if (!surf)
		return NULL;
	SDL_Surface *orig = SDL_ConvertSurface(surf, surf->format, SDL_SWSURFACE);
	SDL_Surface *flip = SDL_ConvertSurface(surf, surf->format, SDL_SWSURFACE);
	SDL_FillRect(flip,NULL,0);
	SDL_LockSurface(orig);
	SDL_LockSurface(flip);

	Uint32 x,y,i;
	Uint8 *src = (Uint8 *)orig->pixels;
	Uint8 *dst = (Uint8 *)flip->pixels;
	Uint8 bpp = orig->format->BytesPerPixel;
	for (y = 0; y < (Uint32)surf->h; y++)
		for (x = 0; x < (Uint32)surf->w; x++)
			for (i = 0; i < bpp; i++)
				dst[y*orig->pitch+(orig->w-1-x)*bpp+i] = src[y*orig->pitch+x*bpp+i];

	SDL_UnlockSurface(flip);
	SDL_UnlockSurface(orig);

	SDL_FreeSurface(orig);
	return flip;
}

// As a final step, turn a surface into a hardware display surface.
SDL_Surface *Gfx_HWSurf(SDL_Surface *surf)
{
	if (!surf)
		return NULL;
	SDL_Surface *hwsurf = SDL_DisplayFormat(surf);
	if (hwsurf) {
		SDL_FreeSurface(surf);
		surf = hwsurf;
	}
	// Assumptions which must be enforced regarding HW surfaces:
	// HW surfaces do not retain their palette.
	// You cannot regain the original palette by trying to go back to a SW surface from a HW surface.
	// Blitting a HW surface to a paletted one which should contain all of the necessary colors is not, in fact, a solution.
	// You CAN convert a HW surface back to a SW surface to do pixel manipulations; In fact, it is MANDATORY, for speed.
	return surf;
}

// Basic XPM loading routine.
// Loads the given XPM array into an SDL_Surface and returns it.
// WARNING: Clears the xpm from memory while reading it! The array will no longer be valid afterwards!
SDL_Surface *Gfx_LoadXPM(char *xpm[])
{
	if (!xpm[0])
		return NULL;

	// Read the headers and allocate the surface
	unsigned int width,height,color_count,symbol_count;
	sscanf(xpm[0],"%u %u %u %u",&width,&height,&color_count,&symbol_count);
	assert(color_count <= 0xFF);
	xpm[0] = NULL;
	SDL_Surface *surf = SDL_CreateRGBSurface(SDL_SWSURFACE,width,height,8,0,0,0,0);

	// Read the palette from the XPM
	SDL_Color colors[color_count];
	char symbols[color_count][symbol_count];
	Uint16 i;
	for (i=0; i < color_count; i++) {
		char value[8];
		sscanf(xpm[i+1],"%s\tc %7s",symbols[i],value);
		xpm[i+1] = NULL;
		if (value[0] == '#') {
			colors[i].r = axtob(&value[1]);
			colors[i].g = axtob(&value[3]);
			colors[i].b = axtob(&value[5]);
			colors[i].unused = 0xFF;
		} else
			colors[i].unused = 0x00;
	}
	SDL_SetColors(surf,colors,0,color_count);
	SDL_SetColorKey(surf,SDL_SRCCOLORKEY|SDL_RLEACCEL,0);

	// Copy the pixels
	SDL_LockSurface(surf);

	Uint16 x,y;
	Uint8 *pix = surf->pixels;
	for(y = 0; y < height; y++) {
		for(x = 0; x < width; x++) {
			for (i = 0; i < color_count; i++)
				if (!strncmp(symbols[i],&xpm[1+color_count+y][x*symbol_count],symbol_count))
					break;
			if (i == color_count)
				i = 0;
			pix[y*surf->pitch+x] = i;
		}
		xpm[1+color_count+y] = NULL;
	}

	SDL_UnlockSurface(surf);
	return surf;
}

void Gfx_Init(void)
{
	{
		SDL_Surface *icon = Gfx_Scale(Gfx_LoadXPM(icon_xpm),2);
		SDL_WM_SetIcon(icon,NULL);
		SDL_FreeSurface(icon);
	}
	SDL_WM_SetCaption("Agora's Legends",NULL);
	screen = NULL;
	Gfx_ReInit();
	SDL_ShowCursor(SDL_FALSE);
	gfx_flags |= GFX_CANDRAW;
	make_load_screen();
	Gfx_Update();
}

// Changes screen resolution
void Gfx_ReInit(void)
{
	if (screen && screen != actual_screen)
		SDL_FreeSurface(screen);
	set_resolution(640*scale,480*scale);
	if (scale > 1)
		screen = Gfx_HWSurf(SDL_CreateRGBSurface(SDL_HWSURFACE,640,480,actual_screen->format->BitsPerPixel,actual_screen->format->Rmask,actual_screen->format->Gmask,actual_screen->format->Bmask,actual_screen->format->Amask));
	else
		screen = actual_screen;
	//Gfx_ClearPalette();
}

// Flips the screen
void Gfx_Update(void)
{
	if (scale > 1) {
		SDL_Surface *scalescreen = Gfx_HWSurf(Gfx_Scale(screen,scale));
		SDL_BlitSurface(scalescreen,NULL,actual_screen,NULL);
		SDL_FreeSurface(scalescreen);
	}
	SDL_Flip(actual_screen);
	//SDL_FillRect(screen,NULL,SDL_MapRGB(screen->format,0x00,0x00,0x00));
}
