#include "surface.h"
#include "configfile.h"
#include "font.h"
#include <string>

extern "C"
{
	#include "../minimal.h"
};

// Assumes RGB565 display mode already set, 320x240.

namespace hm 
{

	void Surface::init() 
	{
		// create two buffers
		numBuffers = 3;
		curBuffer = 0;
		lastBuffer = 0;
		buffers = new unsigned char*[numBuffers];
		
		for (int i=0; i<numBuffers; i++)
			buffers[i] = new unsigned char[width*height*3]; // 24bpp

		// load skin files
		// TODO: should be a loop, duh
		skinImages[HEADER_LEFT] = new Image("skin/header_left.png");
		skinImages[HEADER_CENTER] = new Image("skin/header_center.png");
		skinImages[HEADER_RIGHT] = new Image("skin/header_right.png");
		skinImages[WINDOW_BORDER_LEFT] = new Image("skin/window_border_left.png");
		skinImages[WINDOW_BORDER_RIGHT] = new Image("skin/window_border_right.png");
		skinImages[WINDOW_BORDER_BOTTOM_LEFT] = new Image("skin/window_border_bottom_left.png");
		skinImages[WINDOW_BORDER_BOTTOM_RIGHT] = new Image("skin/window_border_bottom_right.png");
		skinImages[WINDOW_BORDER_BOTTOM] = new Image("skin/window_border_bottom.png");
		skinImages[WINDOW_FILL] = new Image("skin/window_fill.png");
		skinImages[BUTTON] = new Image("skin/button.png");
		skinImages[BUTTON_DOWN] = new Image("skin/button_down.png");
		skinImages[BUTTON_SELECTED] = new Image("skin/button_selected.png");
		skinImages[BUTTON_SELECTED_DOWN] = new Image("skin/button_selected_down.png");
		skinImages[SCROLLBAR_UP] = new Image("skin/scrollbar_up.png");
		skinImages[SCROLLBAR_BACK] = new Image("skin/scrollbar_back.png");
		skinImages[SCROLLBAR_DOWN] = new Image("skin/scrollbar_down.png");
		skinImages[SCROLLBAR_BUTTON_TOP] = new Image("skin/scrollbar_button_top.png");
		skinImages[SCROLLBAR_BUTTON_CENTER] = new Image("skin/scrollbar_button_center.png");
		skinImages[SCROLLBAR_BUTTON_BOTTOM] = new Image("skin/scrollbar_button_bottom.png");
		skinImages[CHECKBOX] = new Image("skin/checkbox.png");
		skinImages[CHECKBOX_CHECKED] = new Image("skin/checkbox_checked.png");
		skinImages[ALERT_ICON] = new Image("skin/alert.png");
		skinImages[SUBMENU_ICON] = new Image("skin/subicon.png");

		// init fonts
		fonts[NORMAL] = new Font(new Image("skin/arial.png"));
		fonts[SELECTED] = new Font(new Image("skin/arial_white.png"));
		fonts[HEADER] = new Font(new Image("skin/tahoma_shadow.png"));
		fonts[HEADER]->setLetterSpacing(0);

		// initialize clipping rectangle
		clearClipRect();

		// load skin config file
		ConfigFile *skinConfig = new ConfigFile();
		skinConfig->load("skin/skin.cfg");
		skinColors[LISTBACKGROUND] = skinConfig->getIntValueByName("listbackgroundcolor");
		skinColors[LISTSELECTED] = skinConfig->getIntValueByName("listselectcolor");
		delete(skinConfig);
	}

	Surface::Surface(int w,int h) 
	{

		// set properties
		width = w;
		height = h;

		init();

	}

	Surface::~Surface() 
	{
		
		// free backbuffers
		for (int i=0; i<numBuffers; i++)
			delete(buffers[i]);
		delete(buffers);

		// free skin images
		for (int i=0; i<NUM_HEADER_FILES; i++)
			delete(skinImages[i]);	  

		// free fonts
		for (int i=0; i<NR_FONTS; i++)
			delete(fonts[i]);
	}

	void Surface::flip() 
	{
		// draw the current buffer to screen. could be optimized, but for a menu does it matter? I think not... 
		unsigned short *rgb = gp2x_video_RGB[0].screen16;
		unsigned char *buf = getBuffer();
		
		for (int i = 0; i < width; i ++)
			for (int j = 0; j < height; j ++)
				rgb[(j*320)+i] = (buf[(j*width+i)*3+0] >> 3) & 31 |
								(buf[(j*width+i)*3+1] & (63 << 2)) << 3 |
								(buf[(j*width+i)*3+2] & (31 << 3)) << 8;
		
		gp2x_video_RGB_flip(0);
		
		// set next buffer as active
		lastBuffer = curBuffer;
		curBuffer = (curBuffer + 1) % numBuffers;
	}

	unsigned char* Surface::getBuffer() 
	{
		return(buffers[curBuffer]);
	}

	int Surface::getWidth() 
	{
		return(width);
	}

	int Surface::getHeight() 
	{
		return(height);
	}
	
	void Surface::drawImage(Image *image,int x,int y)
	{

		// if the image is completely outside the cliprectangle, don't draw
		if (x>=(clipX+clipW)) return;
		if (y>=(clipY+clipH)) return;
		if ((x+image->getWidth())<clipX) return;
		if ((y+image->getHeight())<clipY) return;

		// check for left and upper clip borders
		int offsetX=0;
		int offsetY=0;

		if (x<clipX) offsetX = (clipX-x);
		if (y<clipY) offsetY = (clipY-y);

		// check for right and lower clip borders
		int imgPitch = image->getWidth();
		int imgWidth = image->getWidth();
		int imgHeight = image->getHeight();

		if ( (x+imgWidth) > (clipX+clipW) ) imgWidth = clipX+clipW-x;
		if ( (y+imgHeight) > (clipY+clipH) ) imgHeight = clipY+clipH-y;

		unsigned char* buffer = getBuffer();
		unsigned char* pixels = image->getPixels();

		int i,j;

		buffer += (x + y * width)*3;

		// alpha?
		if (image->hasAlpha())
		{
			for (i=offsetY; i<imgHeight; i++)
				for (j=offsetX; j<imgWidth; j++)
				{
					int imgPos = (i*imgPitch+j)*4;
					int bufPos = (i*width+j)*3;
					unsigned char alpha = pixels[imgPos+3];
					buffer[bufPos+0] = (buffer[bufPos+0]*(255-alpha) + pixels[imgPos+0]*alpha) / 255;
					buffer[bufPos+1] = (buffer[bufPos+1]*(255-alpha) + pixels[imgPos+1]*alpha) / 255;
					buffer[bufPos+2] = (buffer[bufPos+2]*(255-alpha) + pixels[imgPos+2]*alpha) / 255;
				}
		}
		else
		{
			for (i=offsetY; i<imgHeight; i++)
				for (j=offsetX; j<imgWidth; j++)
				{
					int imgPos = (i*imgPitch+j)*3;
					if ((pixels[imgPos+0]!=255)||
						(pixels[imgPos+1]!=0)||
						(pixels[imgPos+2]!=255))
						memcpy(&buffer[(i*width+j)*3], &pixels[imgPos], 3);
				}
		}

	}

	//
	//		  [@___@]
	//
	// well at least it works :)
	void Surface::drawWindow(int x,int y,int w,int h,string title) 
	{

		// draw header
		int centerWidth = w - skinImages[HEADER_LEFT]->getWidth() - skinImages[HEADER_RIGHT]->getWidth();
		int centerStart = skinImages[HEADER_LEFT]->getWidth() + x;

		for (int start = centerStart; start < centerWidth + centerStart; start += skinImages[HEADER_CENTER]->getWidth() )
			drawImage(skinImages[HEADER_CENTER],start,y);

		drawImage(skinImages[HEADER_LEFT],x,y);
		drawImage(skinImages[HEADER_RIGHT],x+w-skinImages[HEADER_RIGHT]->getWidth(),y);

		// draw header text
		  drawText(title,
			x + (w - getFont(HEADER)->getStringWidth(title)) / 2,
			y + (skinImages[HEADER_LEFT]->getHeight()-getFont(HEADER)->getHeight()) / 2,
			HEADER);

		// fill window
		int fillStartX = skinImages[WINDOW_BORDER_LEFT]->getWidth() + x;
		int fillStartY = skinImages[HEADER_LEFT]->getHeight() + y;
		int fillWidth  = w - skinImages[WINDOW_BORDER_LEFT]->getWidth() - skinImages[WINDOW_BORDER_RIGHT]->getWidth();
		int fillHeight = h - skinImages[HEADER_LEFT]->getHeight() - skinImages[WINDOW_BORDER_BOTTOM]->getHeight();

		for (int startY=fillStartY; startY<fillStartY+fillHeight; startY+=skinImages[WINDOW_FILL]->getHeight() )
			for (int startX=fillStartX; startX<fillStartX+fillWidth; startX+=skinImages[WINDOW_FILL]->getWidth() )
				drawImage(skinImages[WINDOW_FILL],startX,startY);

		// draw window borders
		int windowHeight = fillHeight; 
		int windowStart = y + skinImages[HEADER_CENTER]->getHeight();

		// left/right borders
		for (int start = windowStart; start < windowStart + windowHeight; start += skinImages[WINDOW_BORDER_LEFT]->getWidth() ) 
		{
			drawImage(skinImages[WINDOW_BORDER_LEFT],x,start);
			drawImage(skinImages[WINDOW_BORDER_RIGHT],x+w-skinImages[WINDOW_BORDER_RIGHT]->getWidth(),start);
		}

		// window bottom
		int bottomWidth = w - skinImages[WINDOW_BORDER_BOTTOM_LEFT]->getWidth() - skinImages[WINDOW_BORDER_BOTTOM_RIGHT]->getWidth();
		int bottomStart = skinImages[WINDOW_BORDER_BOTTOM_LEFT]->getWidth() + x;

		for (int start = bottomStart; start < bottomWidth + bottomStart; start += skinImages[WINDOW_BORDER_BOTTOM]->getWidth() )
			drawImage(skinImages[WINDOW_BORDER_BOTTOM],start,y+h-skinImages[WINDOW_BORDER_BOTTOM]->getHeight());

		drawImage(skinImages[WINDOW_BORDER_BOTTOM_LEFT],x,y+h-skinImages[WINDOW_BORDER_BOTTOM]->getHeight());
		drawImage(skinImages[WINDOW_BORDER_BOTTOM_RIGHT],x+w-skinImages[WINDOW_BORDER_BOTTOM_RIGHT]->getWidth(),y+h-skinImages[WINDOW_BORDER_BOTTOM]->getHeight());

	}

	void Surface::drawText(string text,int x,int y,FONT_TYPE type) 
	{
		char *txt = (char*)text.c_str();

		for (unsigned int i=0; i<strlen(txt); i++) 
		{
			Image *letter = fonts[type]->getLetter(txt[i]);
			if (letter!=NULL) {
				drawImage(letter,x,y);
				x += letter->getWidth() + fonts[type]->getLetterSpacing();
			} 
			else 
			{
				x +=  + fonts[type]->getWordSpacing();
			}
		}

	}

	void Surface::drawButton(string text,int x,int y,bool down,bool selected) 
	{

		Image *img;
		if (down)
			img = skinImages[selected?BUTTON_SELECTED_DOWN:BUTTON_DOWN]; else
			img = skinImages[selected?BUTTON_SELECTED:BUTTON];

		drawImage(img,x,y);
		  drawText(text,
			x + (img->getWidth() - fonts[NORMAL]->getStringWidth(text)) / 2,
			y + (img->getHeight()-fonts[NORMAL]->getHeight()) / 2
			);

	}

	void Surface::setClipRect(int x,int y,int w,int h) 
	{
		clipX = x;
		clipY = y;
		clipW = w;
		clipH = h;
	}

	void Surface::clearClipRect() 
	{
		clipX = 0;
		clipY = 0;
		clipW = width;
		clipH = height;
	}

	void Surface::drawRect(int x,int y,int w,int h,int color) 
	{
		// clipping
		if (x<clipX) { w -= -(x-clipX); x = clipX; }
		if (y<clipY) { h -= -(y-clipY); y = clipY; }
		if (x+w>clipX+clipW) w -= (x+w) - (clipX+clipW);
		if (y+h>clipY+clipH) h -= (y+h) - (clipY+clipH);

		unsigned char *buffer = getBuffer();

		for (int i=y; i<y+h; i++)
			for (int j=x; j<x+w; j++) {
				buffer[(i*width+j)*3+0] = color & 255;
				buffer[(i*width+j)*3+1] = (color>>8) & 255;
				buffer[(i*width+j)*3+2] = (color>>16) & 255;
			}
	}

	void Surface::drawScrollBar(int x,int y,int h,int value,int range) 
	{

		// set clipping region
		setClipRect(x,y,getScrollBarWidth(),h);

		// draw scrollbar
		drawImage(skinImages[SCROLLBAR_UP],x,y);
		drawImage(skinImages[SCROLLBAR_DOWN],x,y+h-skinImages[SCROLLBAR_DOWN]->getHeight());

		int midHeight = h - skinImages[SCROLLBAR_UP]->getHeight() - skinImages[SCROLLBAR_DOWN]->getHeight();
		setClipRect(
			x,
			y + skinImages[SCROLLBAR_UP]->getHeight(),
			getScrollBarWidth(),
			midHeight
			);

		for (int i=0; i<midHeight; i+=skinImages[SCROLLBAR_BACK]->getHeight())
			drawImage(skinImages[SCROLLBAR_BACK],x,y + skinImages[SCROLLBAR_UP]->getHeight() +i);

		// draw scrollbar button
		y += skinImages[SCROLLBAR_UP]->getHeight();

		// calculate scrollbar buttonsize
		int scrollbarButtonHeight = midHeight-range;
		int minScrollBarButtonHeight = skinImages[SCROLLBAR_BUTTON_TOP]->getHeight()+skinImages[SCROLLBAR_BUTTON_BOTTOM]->getHeight();
		if (scrollbarButtonHeight<minScrollBarButtonHeight)scrollbarButtonHeight= minScrollBarButtonHeight;
		int scrollRange = midHeight - scrollbarButtonHeight;
		y += (value * scrollRange)/range;

		// draw scrollbar button
		setClipRect(x,y,getScrollBarWidth(),h);
		drawImage(skinImages[SCROLLBAR_BUTTON_TOP],x,y);
		drawImage(skinImages[SCROLLBAR_BUTTON_BOTTOM],x,y+scrollbarButtonHeight-skinImages[SCROLLBAR_DOWN]->getHeight());

		midHeight = scrollbarButtonHeight - skinImages[SCROLLBAR_BUTTON_TOP]->getHeight() - skinImages[SCROLLBAR_BUTTON_BOTTOM]->getHeight();
		
		setClipRect(
			x,
			y + skinImages[SCROLLBAR_BUTTON_TOP]->getHeight(),
			getScrollBarWidth(),
			midHeight
			);

		for (int i=0; i<midHeight; i+=skinImages[SCROLLBAR_BUTTON_CENTER]->getHeight())
			drawImage(skinImages[SCROLLBAR_BUTTON_CENTER],x,y + skinImages[SCROLLBAR_BUTTON_TOP]->getHeight() +i);


		// un-set clipping region
		clearClipRect();

	}

	int Surface::getScrollBarWidth() 
	{
		return(skinImages[SCROLLBAR_BACK]->getWidth());
	}

	int Surface::getHeaderHeight() 
	{
		return(skinImages[HEADER_LEFT]->getHeight());
	}

	int Surface::getCheckboxSize() 
	{
		int size = skinImages[CHECKBOX]->getHeight();
		if (skinImages[CHECKBOX]->getWidth()>size)
			size = skinImages[CHECKBOX]->getWidth();
		return(size);
	}

	Image* Surface::toImage() 
	{
		Image *img = new Image(width,height);
		memcpy(img->getPixels(),buffers[lastBuffer],width*height*3); // 24bpp
		return(img);
	}

	void Surface::drawCheckbox(int x,int y,bool checked) 
	{
		drawImage(skinImages[checked?CHECKBOX_CHECKED:CHECKBOX],x,y);
	}

	Font* Surface::getFont(FONT_TYPE type) 
	{
		return(fonts[type]);
	}

	void Surface::drawAlertIcon(int x,int y) 
	{
		drawImage(skinImages[ALERT_ICON],x,y);
	}

	int Surface::getButtonHeight() 
	{
		return(skinImages[BUTTON]->getHeight());
	}

	int Surface::getButtonWidth() 
	{
		return(skinImages[BUTTON]->getWidth());
	}

	Image* Surface::getSkinElement(SKIN_ELEMENT skinElem) 
	{
		return(skinImages[skinElem]);
	}

	int Surface::getSkinColor(SKIN_COLOR color) 
	{
		return(skinColors[color]);
	}

	void Surface::setSkinColor(SKIN_COLOR color,int c) 
	{
		skinColors[color] = c;
	}


}

