/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "JSDL.h"
#include "Globals.h"
#include "GFX.h"

#include <SDL_image.h>
#include <SDL_mixer.h>

#include <cstdlib>
#include <fstream>
#include <stdexcept>

using std::runtime_error;
using std::memcpy;

//defines from SDL_gfx
#define clip_xmin(surface) surface->clip_rect.x
#define clip_xmax(surface) surface->clip_rect.x+surface->clip_rect.w-1
#define clip_ymin(surface) surface->clip_rect.y
#define clip_ymax(surface) surface->clip_rect.y+surface->clip_rect.h-1

typedef struct tColorRGBA {
	Uint8 r;
	Uint8 g;
	Uint8 b;
	Uint8 a;
} tColorRGBA;

typedef struct tColorY {
	Uint8 y;
} tColorY;

void JamesSDL::Init() {	
	//it seems like really you should init the timer system even if in batch mode, but nevermind it seems to work
	//I guess just calling GetTicks() doesn't really require any initialization
	if (!globalSettings.batch) {
		if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO) == -1) {
			char output[120];
			sprintf(output, SDL_GetError());
			throw runtime_error(output);
		}
		
		if (!globalSettings.disableSound) {
			if (SDL_InitSubSystem(SDL_INIT_AUDIO) == -1) {
				char error[120];
				sprintf(error, SDL_GetError());
				string errorStr = error;
				errorStr += ", continuing without initialising sound.";
				WriteLog(errorStr);
				//set to false in SettingsStruct constructor
				globalSettings.dontWriteSound = true;
				globalSettings.disableSound = true;
			}
		}

		ResetVideo();
	}

	SDL_WM_SetCaption("GalaxyHack", 0);

	//keyboard
	SDL_EnableUNICODE(1);
	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
	keyboard = SDL_GetKeyState(0);

	//no mouse cursor with DGA so we may as well always do it ourself
	SDL_ShowCursor(SDL_DISABLE);

	if (!globalSettings.disableSound) {
		//4096 is rather a large chunksize, should be smaller if I add sound effects
		if (Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 4096) == -1) {
			char output[100];
			sprintf(output, Mix_GetError());
			throw runtime_error(output);
		}
	}
}

void JamesSDL::ResetVideo() {
	screenRect.x = 0;
	screenRect.y = 0;
	screenRect.w = globalSettings.screenWidth;
	screenRect.h = globalSettings.screenHeight;

	if (globalSettings.fullScreen)
		screen = SDL_SetVideoMode(globalSettings.screenWidth, globalSettings.screenHeight, screenBPP, SDL_HWSURFACE | SDL_DOUBLEBUF | SDL_FULLSCREEN);
	else
		screen = SDL_SetVideoMode(globalSettings.screenWidth, globalSettings.screenHeight, screenBPP, SDL_HWSURFACE | SDL_DOUBLEBUF);

	if (screen == NULL) {
		SDL_Quit();
		char output[120];
		sprintf(output, SDL_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::BitmapHDtoFile(const string& filename) {
	if (globalSettings.batch)
		return;
		
	loadingSurface = IMG_Load(filename.c_str());

	if (loadingSurface == NULL) {
		char output[100];
		sprintf(output, IMG_GetError());
		throw runtime_error(output);
	}

	SDL_Surface* tmp = loadingSurface;
	if (loadingSurface->format->BitsPerPixel != screenBPP)
		loadingSurface = SDL_ConvertSurface(loadingSurface, screen->format, SDL_SWSURFACE);
	SDL_FreeSurface(tmp);
}

void JamesSDL::BitmapColorConvert(Uint16 newColor) {
	if (globalSettings.batch)
		return;
		
	Uint16 pink = MapRGB(255, 0, 255);

	SDL_LockSurface(loadingSurface);

	Uint16* castedBuffer = reinterpret_cast<Uint16*>(loadingSurface->pixels);

	for (int i = 0; i != (loadingSurface->pitch / 2) * loadingSurface->h; ++i) {
		if (castedBuffer[i] == pink)
			castedBuffer[i] = newColor;
	}

	SDL_UnlockSurface(loadingSurface);
}

void JamesSDL::FlipHorizontal() {
	if (globalSettings.batch)
		return;
		
	int nBytes = loadingSurface->pitch * loadingSurface->h * 2;
	char* buffer = new char[nBytes];

	SDL_LockSurface(loadingSurface);

	//copy image to work area
	//we rely here on the fact that memCpy increments the pointer it uses for copying
	//using ++ or some such rather than +ing it by 1 byte, even though it expects char
	//pointers
	//this means that we must give it the number of pixels rather than the number of bytes
	memcpy(buffer, loadingSurface->pixels, nBytes / 2);

	Uint16* pSource = reinterpret_cast<Uint16*>(buffer);
	Uint16* pDest = reinterpret_cast<Uint16*>(loadingSurface->pixels);

	//slightly strange indices
	//flip horizontally
	for (int i = 1; i != loadingSurface->h + 1; ++i) {
		for (int j = 1; j != loadingSurface->w + 1; ++j) {
			*pDest = pSource[i * loadingSurface->w - j];
			++pDest;
		}
	}

	SDL_UnlockSurface(loadingSurface);

	// release the memory
	delete[] buffer;
}

void JamesSDL::BitmapCloseFile() {
	if (globalSettings.batch)
		return;
		
	SDL_FreeSurface(loadingSurface);
	loadingSurface = 0;
}

SDL_Surface* JamesSDL::BitmapFiletoSurface(int x, int y, int width, int height, Uint16 transparency) {
	SDL_Surface* ret = SDL_CreateRGBSurface(SDL_HWSURFACE, width, height, screenBPP,  0, 0, 0, 0);
	
	if (globalSettings.batch)
		return ret;

	SDL_Rect tempRect = {x, y, width, height};

	if (width - x != loadingSurface->w || height - y != loadingSurface->h) {
		double zoomx = static_cast<double>(width - x) / loadingSurface->w;
		double zoomy = static_cast<double>(height - y) / loadingSurface->h;

		//FIXME FIXME FIXME utter hackery of the highest order, without this
		//medium capital ships end up being 511*111
		zoomx+= 0.00000000005;
		zoomy+= 0.00000000005;

		SDL_Surface* tempSurface = gfx::zoomSurface (loadingSurface, zoomx, zoomy, 1);

		SDL_BlitSurface(tempSurface, &tempRect, ret, 0);

		SDL_FreeSurface(tempSurface);
	} else
		//Just blit across
		SDL_BlitSurface(loadingSurface, &tempRect, ret, 0);


	SDL_SetColorKey(ret, SDL_SRCCOLORKEY | SDL_RLEACCEL, transparency);

	return ret;
}

SDL_Surface* JamesSDL::BitmapHDtoSurface(const string& filename, int x, int y, int w, int h, Uint16 transparency) {
	BitmapHDtoFile(filename);
	SDL_Surface* ret = BitmapFiletoSurface(x, y, w, h, transparency);
	BitmapCloseFile();

	return ret;
}

//////

void JamesSDL::BltFill(SDL_Rect destRect, Uint16 fillColor) {
	if (skipDisplayFrame || globalSettings.batch)
		return;		
	if (SDL_FillRect(screen, &destRect, fillColor) == -1)
		throw runtime_error("Failed to BltFill");
}

void JamesSDL::Blt(SDL_Surface* src, SDL_Rect destRect) {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	if (SDL_BlitSurface(src, 0, screen, &destRect) == -1)
		throw runtime_error("Failed to blit");
}


void JamesSDL::BltPart(SDL_Surface* src, SDL_Rect& srcRect, SDL_Rect destRect) {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	if (SDL_BlitSurface(src, &srcRect, screen, &destRect) == -1)
		throw runtime_error("BltPart fails");
}

bool JamesSDL::DrawCircle(Sint16 x, Sint16 y, Sint16 r, Uint32 color) {
	return gfx::circleColor(screen, x, y, r, color);
}

void JamesSDL::Flip() {
	if (skipDisplayFrame || globalSettings.batch)
		return;	
	SDL_Flip(screen);
}

void JamesSDL::ForceFlip() {
	if (globalSettings.batch)
		return;	
	SDL_Flip(screen);
}

void JamesSDL::LockBack() {
	if (globalSettings.batch)
		return;	
	if (SDL_LockSurface(screen) == -1)
		throw runtime_error("Failed to lock back surface");
}

void JamesSDL::UnlockBack() {
	if (globalSettings.batch)
		return;	
	SDL_UnlockSurface(screen);
}

void JamesSDL::Shutdown() {
	if (theMusic)
		Mix_FreeMusic(theMusic);

	Mix_CloseAudio();

	//this automatically frees the screen
	SDL_Quit();
}

Uint16 JamesSDL::MapRGB(int r, int g, int b) {
	return SDL_MapRGB(screen->format, r, g, b);
}

void JamesSDL::UshortToRGB(Uint16 theColor, Uint8& giveR, Uint8& giveG, Uint8& giveB) {
	SDL_GetRGB(theColor, screen->format, &giveR, &giveG, &giveB);
}

void JamesSDL::LoadMusic(const string& filename) {
	if (globalSettings.disableSound)
		return;

	musicName = filename;

	if (theMusic)
		Mix_FreeMusic(theMusic);

	theMusic = Mix_LoadMUS(filename.c_str());

	if (!theMusic) {
		char output[100];
		sprintf(output, Mix_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::PlayMusic() {
	if (!globalSettings.bMusic || globalSettings.disableSound)
		return;

	if (Mix_PlayMusic(theMusic, -1) == -1) {
		char output[100];
		sprintf(output, Mix_GetError());
		throw runtime_error(output);
	}
}

void JamesSDL::StopMusic() {
	Mix_HaltMusic();
}
