#include "libs.h"
#include "l3d.h"
#include "gui.h"
#include "glfreetype.h"
#include "objimport.h"
#include "player.h"
#include "space.h"
#include "planet.h"
#include "star.h"
#include "frame.h"
#include "ship_cpanel.h"
#include "sector_view.h"
#include "system_view.h"
#include "system_info_view.h"
#include "world_view.h"
#include "object_viewer_view.h"
#include "star_system.h"
#include "space_station.h"
#include "space_station_view.h"
#include "info_view.h"

float             L3D::timeAccel = 1.0f;
int               L3D::scrWidth;
int               L3D::scrHeight;
float             L3D::scrAspect;
SDL_Surface*      L3D::scrSurface;
sigc::signal<void, SDL_keysym*> L3D::onKeyPress;
sigc::signal<void, SDL_keysym*> L3D::onKeyRelease;
sigc::signal<void, int, int, int> L3D::onMouseButtonUp;
sigc::signal<void, int, int, int> L3D::onMouseButtonDown;
char              L3D::keyState[SDLK_LAST];
char              L3D::mouseButton[5];
int               L3D::mouseMotion[2];
enum L3D::CamType  L3D::cam_type;
enum L3D::MapView  L3D::map_view;
Player*           L3D::player;
View*             L3D::current_view;
WorldView*        L3D::world_view;
ObjectViewerView* L3D::objectViewerView;
SpaceStationView* L3D::spaceStationView;
InfoView*         L3D::infoView;
SectorView*       L3D::sector_view;
SystemView*       L3D::system_view;
SystemInfoView*   L3D::system_info_view;
ShipCpanel*       L3D::cpan;
StarSystem*       L3D::selected_system;
MTRand            L3D::rng;
double            L3D::gameTime;
float             L3D::frameTime;
GLUquadric*       L3D::gluQuadric;
systemloc_t       L3D::playerLoc;

void L3D::Init(IniConfig& config) {
  int width  = config.Int("ScrWidth");
  int height = config.Int("ScrHeight");
  const SDL_VideoInfo* info = NULL;
  if(SDL_Init(SDL_INIT_VIDEO) < 0) {
    fprintf(stderr, "Video initialization failed: %s\n", SDL_GetError());
    exit(-1);
  }

  info = SDL_GetVideoInfo();
  
  switch(config.Int("ScrDepth")) {
  case 16:
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,  5);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,6);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
    break;
  case 32:
    SDL_GL_SetAttribute(SDL_GL_RED_SIZE,  8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    break;
  default:
    fprintf(stderr, "Fatal error: Invalid screen depth in config.ini.\n");
    L3D::Quit();
  }
  SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,  24);
  SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

  Uint32 flags = SDL_OPENGL;
  if(config.Int("StartFullscreen")) flags |= SDL_FULLSCREEN;

  if((L3D::scrSurface = SDL_SetVideoMode(width, height, info->vfmt->BitsPerPixel, flags)) == 0) {
    /* Fall back to 16-bit depth buffer.. */
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
    fprintf(stderr,"Failed to set video mode. (%s). Re-trying with 16 bit depth buffer.\n",SDL_GetError());
    if((L3D::scrSurface = SDL_SetVideoMode(width, height, info->vfmt->BitsPerPixel, flags)) == 0) {
      fprintf(stderr, "video mode set failed: %s\n", SDL_GetError());
      exit(-1);
    }
  }

  L3D::scrWidth = width;
  L3D::scrHeight = height;
  L3D::scrAspect = width / (float)height;

  InitOpenGL();

  dInitODE();
  GLFTInit();
  Space::Init();
}

void L3D::InitOpenGL() {
  glShadeModel(GL_SMOOTH);
  glCullFace(GL_BACK);
  glFrontFace(GL_CCW);
  glEnable(GL_CULL_FACE);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_LIGHTING);
  glEnable(GL_LIGHT0);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glClearColor(0, 0, 0, 0);
  glViewport(0, 0, scrWidth, scrHeight);

  gluQuadric = gluNewQuadric();
}

void L3D::Quit(void) {
  SDL_Quit();
  exit(0);
}

void L3D::SetCamType(enum CamType c) {
  cam_type = c;
  map_view = MAP_NOMAP;
  SetView(world_view);
}

void L3D::SetMapView(enum MapView v) {
  map_view = v;
  if(v == MAP_SECTOR)
    SetView(sector_view);
  else
    SetView(system_view);
}

void L3D::SetView(View* v) {
  if(current_view) current_view->HideAll();
  current_view = v;
  current_view->ShowAll();
}

void L3D::HandleEvents(void) {
  SDL_Event event;

  L3D::mouseMotion[0] = L3D::mouseMotion[1] = 0;
  while(SDL_PollEvent(&event)) {
    Gui::HandleSDLEvent(&event);
    switch(event.type) {
    case SDL_KEYDOWN:
      if(event.key.keysym.sym == SDLK_q) L3D::Quit();
      if(event.key.keysym.sym == SDLK_F11) SDL_WM_ToggleFullScreen(L3D::scrSurface);
      if(event.key.keysym.sym == SDLK_F12) L3D::SetView(L3D::objectViewerView);
      L3D::keyState[event.key.keysym.sym] = 1;
      L3D::onKeyPress.emit(&event.key.keysym);
      break;
    case SDL_KEYUP:
      L3D::keyState[event.key.keysym.sym] = 0;
      L3D::onKeyRelease.emit(&event.key.keysym);
      break;
    case SDL_MOUSEBUTTONDOWN:
      L3D::mouseButton[event.button.button] = 1;
      L3D::onMouseButtonDown.emit(event.button.button,
                                 event.button.x, event.button.y);
      break;
    case SDL_MOUSEBUTTONUP:
      L3D::mouseButton[event.button.button] = 1;
      L3D::onMouseButtonUp.emit(event.button.button,
                               event.button.x, event.button.y);
      break;
    case SDL_MOUSEMOTION:
      L3D::mouseMotion[0] += event.motion.xrel;
      L3D::mouseMotion[1] += event.motion.yrel;
      //SDL_GetRelativeMouseState(&L3D::mouseMotion[0], &L3D::mouseMotion[1]);
      break;
    case SDL_QUIT:
      L3D::Quit();
      break;
    }
  }
}

void L3D::MainLoop(void) {
  player = new Player(ShipType::SLEEK);
  player->SetLabel("Me");
  Space::AddBody(player);

  StarSystem s(0, 0, 0);
  HyperspaceTo(&s);

  /* Linked list eh... Put player at planet f. */
  Frame* pframe = *++(++(++(++(Space::rootFrame->m_children.begin()))));
  player->SetFrame(pframe);
  player->SetPosition(vector3d(0, 100000, 10000000.0));

  for(int i = 0; i < 4; i++) {
    Ship* body = new Ship(ShipType::SLEEK/*LADYBIRD*/);
    char buf[64];
    snprintf(buf, sizeof(buf), "X%c-0%02d", "A"+i, i);
    body->SetLabel(buf);
    body->SetFrame(pframe);
    body->SetPosition(vector3d(i*2000, 100000, 10001000));
    Space::AddBody(body);
  }

  {
    SpaceStation* body = new SpaceStation();
    body->SetLabel("Poemi-chan's Folly");
    body->SetFrame(pframe);
    body->SetPosition(vector3d(5000, 100000, 9990000.0));
    Space::AddBody(body);
  }

  Gui::Init(scrWidth, scrHeight, 640, 480);

  cpan = new ShipCpanel();
  cpan->ShowAll();

  sector_view       = new SectorView();
  system_view       = new SystemView();
  system_info_view  = new SystemInfoView();
  world_view        = new WorldView();
  objectViewerView  = new ObjectViewerView();
  spaceStationView  = new SpaceStationView();
  infoView          = new InfoView();

  SetView(world_view);

  Uint32 last_stats = SDL_GetTicks();
  int frame_stat = 0;
  char fps_readout[32];
  Uint32 time_before_frame = SDL_GetTicks();

  for(;;) {
    frame_stat++;
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    current_view->Draw3D();
    /*
     * TODO: HandleEvents at the moment must be after view->Draw3D and before
     * Gui::Draw so that labels drawn to screen can have mouse events correctly
     * detected. Gui::Draw wipes memory of label positions.
     */
    L3D::HandleEvents();
    /* Hide cursor for ship control. */
    if(L3D::MouseButtonState(3)) {
      SDL_ShowCursor(0);
      SDL_WM_GrabInput(SDL_GRAB_ON);
    } else {
      SDL_ShowCursor(1);
      SDL_WM_GrabInput(SDL_GRAB_OFF);
    }

    Gui::Draw();
#ifdef DEBUG
    {
      Gui::Screen::EnterOrtho();
      glColor3f(1, 1, 1);
      glTranslatef(0, Gui::Screen::GetHeight()-20, 0);
      Gui::Screen::RenderString(fps_readout);
      Gui::Screen::LeaveOrtho();
    }
#endif

    glFlush();
    SDL_GL_SwapBuffers();
    //if(glGetError()) printf("GL: %s\n", gluErrorString(glGetError()));

    L3D::frameTime = 0.001*(SDL_GetTicks() - time_before_frame);
    float step = L3D::timeAccel * L3D::frameTime;

    time_before_frame = SDL_GetTicks();
    /* Game state update stuff. */
    if(step) {
      Space::TimeStep(step);
      gameTime += step;
    }
    current_view->Update();

    if(SDL_GetTicks() - last_stats > 1000) {
      snprintf(fps_readout, sizeof(fps_readout), "%d fps", frame_stat);
      frame_stat = 0;
      last_stats += 1000;
    }
  }
}

StarSystem* L3D::GetSelectedSystem(void) {
  int sector_x, sector_y, system_idx;
  L3D::sector_view->GetSelectedSystem(&sector_x, &sector_y, &system_idx);
  if(system_idx == -1) {
    selected_system = 0;
    return NULL;
  }
  if(selected_system) {
    if(!selected_system->IsSystem(sector_x, sector_y, system_idx)) {
      delete selected_system;
      selected_system = 0;
    }
  }
  if(!selected_system) {
    selected_system = new StarSystem(sector_x, sector_y, system_idx);
  }
  return selected_system;
}

void L3D::HyperspaceTo(StarSystem* dest) {
  Space::Clear();
  Space::BuildSystem(dest);
  float ang = rng(M_PI);
  L3D::player->SetPosition(vector3d(sin(ang)*8*AU, cos(ang)*8*AU, 0));
  dest->GetPos(&L3D::playerLoc);
}

IniConfig::IniConfig(const char* filename) {
  FILE* f = fopen(filename, "r");
  if(!f) {
    fprintf(stderr, "Could not open '%s'.\n", filename);
    L3D::Quit();
  }
  char buf[1024];
  while(fgets(buf, sizeof(buf), f)) {
    if(buf[0] == '#') continue;
    char* sep = strchr(buf, '=');
    char* kend = sep;
    if(!sep) continue;
    *sep = 0;
    /* Strip whitespace. */
    while(isspace(*(--kend))) *kend = 0;
    while(isspace(*(++sep)))  *sep  = 0;
    /* Snip \r, \n. */
    char* vend = sep;
    while(*(++vend)) if((*vend == '\r') || (*vend == '\n')) { *vend = 0; break; }
    std::string key = std::string(buf);
    std::string val = std::string(sep);
    (*this)[key] = val;
  }
  fclose(f);
}

int main(int argc, char** argv) {
  printf("Lephisto3D's super high tech demo!\n");
  
  IniConfig cfg("config.ini");

  L3D::Init(cfg);
  L3D::MainLoop();
  return 0;
}