//
// main.cpp
//
// main() function of the program
//
//
// Author: Tomi Belan <tomi.belan@gmail.com>, (C) 2006
//
// Copyright: See COPYING file that comes with this distribution
//
//

#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <time.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <SDL_ttf.h>

#include "main.h"
#include "music.h"
#include "keyboard.h"
#include "programmode.h"
#include "introscreen.h"

SDL_Surface* screen;

std::string programDataPath;
std::map<std::string, SDL_Surface*> imagePool;
TTF_Font *papyrusFont;

ProgramMode *activeProgramMode;
ProgramMode *newProgramMode;
static bool running;

bool haveSound;

#define INIT_SDL_INIT   1
#define INIT_VIDEOMODE  2
#define INIT_TTF_INIT   3
static int initializationProgress;

/**
* Terminates program's main loop.
*/
void stopProgram()
{
  running = false;
}

std::string tr(std::string str)
{
  // when I thought we'd translate the game I made this function.
  // it's supposed to return translated version of str.
  return str;
}

bool rectanglesCollide(SDL_Rect rect1, SDL_Rect rect2)
{
  if(rect1.y+rect1.h <= rect2.y ||
     rect1.y >= rect2.y+rect2.h ||
     rect1.x+rect1.w <= rect2.x ||
     rect1.x >= rect2.x+rect2.w)
    return false;
  return true;
}

void simpleText(const char *str, int line)
{
  SDL_Rect offset; SDL_Color white = {255, 255, 255};
  SDL_Surface *message = TTF_RenderText_Blended(papyrusFont, tr(str).c_str(), white);
  set_rect_pos(offset, (screen->w-message->w)/2, (screen->h-message->h)/2+line*message->h);
  SDL_BlitSurface(message, NULL, screen, &offset);
}

/**
* Helper function for getDataPath().
* Returns true if the program can open 'filename' for reading. (The function
* name isn't very accurate, but for us, a file we can't read is as good as one
* that doesn't exist at all.)
*/
bool fileExists(const char* filename)
{
  std::ifstream f;
  f.open(filename);
  if(!f.is_open())
    return false;
  f.close();
  return true;
}

/**
* Searches for program's data and returns its path. It can be used as prefix
* for querying files.
*/
const char* getDataPath()
{
  // some shortcuts:
#define tmp "gulfofdeath.dat"
#define tmp2 "/share/games/gulfofdeath/"
  // universal paths using current directory; I don't know much details of
  // most architectures, so this is my best guess. However, it should work on
  // Linux and Win32.
  if(fileExists("" tmp))
    return      "";
  if(fileExists("data" DIR_SEP tmp))
    return      "data" DIR_SEP;
  if(fileExists(".." DIR_SEP tmp))
    return      ".." DIR_SEP;
  if(fileExists(".." DIR_SEP "data" DIR_SEP tmp))
    return      ".." DIR_SEP "data" DIR_SEP;
  // these paths won't work on windows anyway, so I don't bother to use DIR_SEP
  if(fileExists("/usr" tmp2 tmp))
    return      "/usr" tmp2;
  if(fileExists("/usr/local" tmp2 tmp))
    return      "/usr/local" tmp2;
  if(fileExists("/usr" tmp2 "data/" tmp))
    return      "/usr" tmp2 "data/";
  if(fileExists("/usr/local" tmp2 "data/" tmp))
    return      "/usr/local" tmp2 "data/";
  error("Can't find Gulf of Death data files.", ERR_FATAL_ERROR);
  return NULL;
#undef tmp
}

SDL_Surface *loadImage(std::string filename, bool hasAlpha)
{
//  std::string path(programDataPath + filename);
  
  SDL_Surface *loadedImage = IMG_Load((programDataPath + filename).c_str());
  
  if(!loadedImage) {
    std::string message("$Couldn't load ");
    error((message + programDataPath + filename).c_str(), ERR_ERROR);
    return NULL;
  }
  
  if(hasAlpha) {
    SDL_SetAlpha(loadedImage, SDL_SRCALPHA | SDL_RLEACCEL, SDL_ALPHA_TRANSPARENT);
  }
  else {
    Uint32 colorkey = SDL_MapRGB(loadedImage->format, 0xFF, 0, 0xFF);
    SDL_SetColorKey(loadedImage, SDL_RLEACCEL | SDL_SRCCOLORKEY, colorkey);
  }
  
  SDL_Surface* image = SDL_DisplayFormatAlpha(loadedImage);
  SDL_FreeSurface(loadedImage);
//SDL_Surface* image = loadedImage;
  
  if(!image) {
    std::string message("$Couldn't convert ");
    error((message + programDataPath + filename).c_str(), ERR_ERROR);
    return NULL;
  }
  
  // cache the image in image pool
  imagePool[filename] = image;
  
  return image;
}

TTF_Font *loadFont(std::string filename, int size)
{
  TTF_Font *font = TTF_OpenFont((programDataPath + filename).c_str(), size);
  
  if(!font) {
    std::string message("$Couldn't load ");
    error((message + programDataPath + filename).c_str(), ERR_ERROR);
    return NULL;
  }
  
  return font;
}

/**
* Creates a new RGB surface with specified dimensions, with color depth of screen.
*/
SDL_Surface* createSurface(int w, int h)
{
  SDL_PixelFormat* f = screen->format;
  return SDL_CreateRGBSurface(SDL_SWSURFACE, w, h, f->BitsPerPixel, f->Rmask, f->Gmask, f->Bmask, 0);
}

/**
* This only cleans up program components that were already initialized. Useful
* when dealing with errors that occured during initialization.
*/
static void shutdown()
{
  // free surfaces from imagePool
  std::map<std::string,SDL_Surface*>::iterator iter;
  for(iter = imagePool.begin(); iter != imagePool.end(); iter++) {
    if(iter->second)
      SDL_FreeSurface(iter->second);
  }
  
  if(haveSound)
    Mix_CloseAudio();
  Music::unload();
  
  switch(initializationProgress) {
    // no breaks!
    case INIT_TTF_INIT: if(papyrusFont)TTF_CloseFont(papyrusFont); TTF_Quit();
    case INIT_VIDEOMODE:
    case INIT_SDL_INIT: SDL_Quit();
  }
}

/**
* Prints an error 'str2' of severity 'type' to cerr. Fatal errors are errors
* that must inevitably lead to program termination. (Note: only errors with
* severity at least DEBUG_LEVEL are printed.)
*/
int error(const char *str2, int type)
{
  std::string str(str2);

  std::stringstream buf;
  if(str[0] == '$')
    str = str.substr(1, str.length()-1) + "\n" + SDL_GetError();
  if(type == ERR_NOTICE && DEBUG_LEVEL <= ERR_NOTICE)
    buf << str << std::endl;
  if(type == ERR_WARNING && DEBUG_LEVEL <= ERR_WARNING)
    buf << "WARNING: " << str << std::endl;
  if(type == ERR_ERROR && DEBUG_LEVEL <= ERR_ERROR)
    buf << "ERROR: " << str << std::endl;
  if(type == ERR_FATAL_ERROR && DEBUG_LEVEL <= ERR_FATAL_ERROR)
    buf << "FATAL ERROR: " << str << std::endl;
  std::cerr << buf.str();
  if(type == ERR_FATAL_ERROR) {
    shutdown();
    exit(1);
  }
  return 0;
}

int main(int argc, char *argv[])
{
  // Program initialization
  
  if(SDL_Init(SDL_INIT_EVERYTHING))
    error("$Could not initialize SDL", ERR_FATAL_ERROR);
  initializationProgress = INIT_SDL_INIT;
  
  screen = SDL_SetVideoMode(800, 600, 32, SDL_SWSURFACE | SDL_FULLSCREEN);
  if(!screen) error("$Could not initialize graphics", ERR_FATAL_ERROR);
  initializationProgress = INIT_VIDEOMODE;
  
  if(TTF_Init() == -1)
    error("$Could not initialize TTF", ERR_FATAL_ERROR);
  initializationProgress = INIT_TTF_INIT;
  
  if(Mix_OpenAudio( 22050, MIX_DEFAULT_FORMAT, 2, 4096 ))
    error("$Could not initialize mixer, will start w/o sound", ERR_ERROR);
  else
    haveSound = true;
  
  SDL_WM_SetCaption("Gulf of Death", NULL);
  
  srand(time(NULL));
  
  // Data initialization
  
  programDataPath = getDataPath();
  if( !loadImage("logo.png", true) ||
      !loadImage("menu.png", true) ||
      !loadImage("tlacitka.png", true) ||
      !loadImage(MENU_BACKGROUND) ||
      !loadImage("ad-victum4f.png") ||
      !loadImage("presentsscreen1.png") ||
      !loadImage("bar1.png") ||
      !loadImage("bar2.png") ||
      !loadImage("kruh.png") ||
      !loadImage("voda.png") ||
      !loadImage("lod1.png", true) ||
      !loadImage("strela2.png", true) ||
      !loadImage("cball.png", true) ||
      !loadImage("prekazka1.png", true) ||
      !loadImage("prekazka2.png", true) ||
      !loadImage("prekazka3.png", true))
    error("Data loading failed", ERR_FATAL_ERROR);
  papyrusFont = loadFont("papyrus.ttf", 29);
  if(!papyrusFont)
    error("Data loading failed", ERR_FATAL_ERROR);
  if(!Music::load())
    error("Data loading failed", ERR_FATAL_ERROR);
  
  // Event loop
  
  activeProgramMode = new IntroScreen();
  Music::play(1);
  
  SDL_Event event;
  
  running = true;
  int fpsTimer;
  while(running) {
    fpsTimer = SDL_GetTicks();
    
    while(SDL_PollEvent(&event)) {
      if(event.type == SDL_QUIT)
        stopProgram();
      if(event.type == SDL_KEYDOWN)
        Keyboard::keyPress(&event);
      if(event.type == SDL_KEYUP)
        Keyboard::keyRelease(&event);
      activeProgramMode->event(&event);
    }
    
    activeProgramMode->update();
    
    if(newProgramMode) {
      delete activeProgramMode;
      activeProgramMode = newProgramMode;
      newProgramMode = NULL;
      activeProgramMode->activate();
    }
    
    Keyboard::releaseKeys();
    while(SDL_GetTicks() - fpsTimer < 1000 / FRAMES_PER_SECOND)
      SDL_Delay(1);
  }
  
  // Program shutdown
  
  delete activeProgramMode;
  activeProgramMode = NULL;
  
  shutdown();
  return EXIT_SUCCESS;
}
