/*
    Copyright (C) 1999 by Seth Galbraith

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "sysdef.h"
#include "squawk.h"
#include "csengine/world.h"
#include "csengine/sector.h"
#include "csengine/camera.h"
#include "csengine/csview.h"
#include "csengine/light.h"
#include "csengine/polygon.h"

#include "csobject/nameobj.h"
#include "csparser/csloader.h"
#include "cssys/common/system.h"
#include "cstools/simpcons.h"
#include "csutil/inifile.h"
#include "csutil/archive.h"

#include "igraph2d.h"
#include "igraph3d.h"
#include "itxtmgr.h"

#define SQK_VERSION 11
#define SQK_ROOM_SIZE 2

SqkSystem* Sys = NULL;

SqkSystem::SqkSystem ()
{
  linkedViews = true;

  world = NULL;
  view[0] = NULL;
  view[1] = NULL;
  view[2] = NULL;
  view[3] = NULL;
}

SqkSystem::~SqkSystem ()
{
  delete world;
  delete view[0];
  delete view[1];
  delete view[2];
  delete view[3];
}

void cleanup ()
{
  pprintf ("Cleaning up...\n");
  delete Sys;
  delete config;
  pprintf_close();
}

void debug_dump ()
{
}

void SqkSystem::InitApp ()
{
  if (!Open ("Squawk Crystal Space Application"))
  {
    Printf (MSG_FATAL_ERROR, "Error opening system!\n");
    cleanup ();
    exit (1);
  }

  ITextureManager* txtmgr;
  piG3D->GetTextureManager (&txtmgr);
  txtmgr->Initialize ();
  txtmgr->Prepare ();
  txtmgr->AllocPalette ();

  csSimpleConsole* con = new csSimpleConsole();
  con->SetupColors (txtmgr);
  con->SetTransparent (0);
  con->SetMaxLines (1000);       // Some arbitrarily high value.
  Sys->Console = con;

  Sys->DemoReady = true;

  Printf (MSG_INITIALIZATION, "Squawk Crystal Space Application version %d\n",
    SQK_VERSION);

  world->EnableLightingCache (false);
  world->Initialize (GetISystemFromSystem (this), piG3D, NULL);

  char const* path = config->GetStr ("Squawk", "DATA", "data/squawk.zip");
  Archive* archive = new Archive (path, true);

  // load sky box textures

  csTextureHandle* tm_up    = CSLoader::LoadTexture (world, "up",    "terr_u.jpg", archive);
  csTextureHandle* tm_down  = CSLoader::LoadTexture (world, "down",  "terr_d.jpg", archive);
  csTextureHandle* tm_left  = CSLoader::LoadTexture (world, "left",  "terr_l.jpg", archive);
  csTextureHandle* tm_right = CSLoader::LoadTexture (world, "right", "terr_r.jpg", archive);
  csTextureHandle* tm_front = CSLoader::LoadTexture (world, "front", "terr_f.jpg", archive);
  csTextureHandle* tm_back  = CSLoader::LoadTexture (world, "back",  "terr_b.jpg", archive);

  // create the sky box room

  csSector* room = world->NewSector ();
  csNameObject::AddName (*room, "room");

  csPolygon3D* p;

  // (up) ceiling
  p = room->NewPolygon (tm_up);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  // (down) floor
  p = room->NewPolygon (tm_down);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  // (left) negative x wall
  p = room->NewPolygon (tm_left);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  // (right) positive x wall 
  p = room->NewPolygon (tm_right);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  // (front) positive z wall
  p = room->NewPolygon (tm_front);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE,  SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  // (back) negative z wall
  p = room->NewPolygon (tm_back);
  p->AddVertex ( SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE,  SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex (-SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->AddVertex ( SQK_ROOM_SIZE, -SQK_ROOM_SIZE, -SQK_ROOM_SIZE);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), SQK_ROOM_SIZE * 2);

  csStatLight* light;
  light = new csStatLight ( 0, 0,  0, SQK_ROOM_SIZE * 2, 1, 1, 1, false);
  room->AddLight (light);

  Printf (MSG_INITIALIZATION, "Preparing world ... ");
  world->Prepare (piG3D);
  Printf (MSG_INITIALIZATION, "DONE!\n");

  txtmgr->AllocPalette ();
  con->SetupColors (txtmgr);

  if (SUCCEEDED (piG2D->BeginDraw()))
  {
    piG2D->ClearAll (0);
    piG2D->FinishDraw ();
  }

  view[0] = new csView (world, piG3D);
  view[0]->SetSector (room);
  view[0]->GetCamera ()->SetPosition (csVector3 (0, 0, 0));
  view[0]->SetRectangle
  (
    2,
    FrameHeight / 2 + 2,
    FrameWidth  / 2 - 4,
    FrameHeight / 2 - 4
  );

  view[1] = new csView (world, piG3D);
  view[1]->SetSector (room);
  view[1]->GetCamera ()->SetPosition (csVector3 (0, 0, 0));
  view[1]->SetRectangle
  (
    FrameWidth  / 2 + 2,
    FrameHeight / 2 + 2,
    FrameWidth  / 2 - 4,
    FrameHeight / 2 - 4
  );

  view[2] = new csView (world, piG3D);
  view[2]->SetSector (room);
  view[2]->GetCamera ()->SetPosition (csVector3 (0, 0, 0));
  view[2]->SetRectangle
  (
    2,
    2,
    FrameWidth  / 2 - 4,
    FrameHeight / 2 - 4
  );

  view[3] = new csView (world, piG3D);
  view[3]->SetSector (room);
  view[3]->GetCamera ()->SetPosition (csVector3 (0, 0, 0));
  view[3]->SetRectangle
  (
    FrameWidth  / 2 + 2,
    2,
    FrameWidth  / 2 - 4,
    FrameHeight / 2 - 4
  );
}

void SqkSystem::RotateView (int i, const csVector3& v, float speed)
{
  if (view[i]->GetCamera()->GetW2C().Col2().y < 0) // upside down (-:
  {
    if (v == VEC_ROT_LEFT)
      {view[i]->GetCamera()->RotateWorld(VEC_ROT_RIGHT,speed); return;}
    if (v == VEC_ROT_RIGHT)
      {view[i]->GetCamera()->RotateWorld(VEC_ROT_LEFT,speed); return;}
  }
  if (v == VEC_ROT_LEFT || v == VEC_ROT_RIGHT)
    view[i]->GetCamera()->RotateWorld(v,speed);
  else
    view[i]->GetCamera()->Rotate(v,speed);
}

void SqkSystem::eatkeypress (int key, bool shift, bool alt, bool ctrl)
{
  (void)shift; (void)alt; (void)ctrl;

  if (linkedViews)
  {
    switch (key)
    {
      case CSKEY_ESC : Shutdown = true; break;
      case ' ' : linkedViews = false; break;

      case CSKEY_RIGHT :
        RotateView (0,VEC_ROT_RIGHT,0.03);
        RotateView (1,VEC_ROT_RIGHT,0.03);
        RotateView (2,VEC_ROT_RIGHT,0.03);
        RotateView (3,VEC_ROT_RIGHT,0.03);
        break;
      case CSKEY_LEFT  :
        RotateView (0,VEC_ROT_LEFT,0.03);
        RotateView (1,VEC_ROT_LEFT,0.03);
        RotateView (2,VEC_ROT_LEFT,0.03);
        RotateView (3,VEC_ROT_LEFT,0.03);
        break;
      case CSKEY_UP    :
        RotateView (0,VEC_TILT_UP,0.03);
        RotateView (1,VEC_TILT_UP,0.03);
        RotateView (2,VEC_TILT_UP,0.03);
        RotateView (3,VEC_TILT_UP,0.03);
        break;
      case CSKEY_DOWN  :
        RotateView (0,VEC_TILT_DOWN,0.03);
        RotateView (1,VEC_TILT_DOWN,0.03);
        RotateView (2,VEC_TILT_DOWN,0.03);
        RotateView (3,VEC_TILT_DOWN,0.03);
        break;
    }
  }
  else // !linkedViews
  {
    switch (key)
    {
      case CSKEY_ESC : Shutdown = true; break;
      case ' ' : linkedViews = true; break;

      case CSKEY_RIGHT : RotateView (0,VEC_ROT_RIGHT,0.03); break;
      case CSKEY_LEFT  : RotateView (0,VEC_ROT_LEFT,0.03);  break;
      case CSKEY_UP    : RotateView (0,VEC_TILT_UP,0.03);   break;
      case CSKEY_DOWN  : RotateView (0,VEC_TILT_DOWN,0.03); break;

      case 'd'  : RotateView (1,VEC_ROT_RIGHT,0.03); break;
      case 'a'  : RotateView (1,VEC_ROT_LEFT,0.03);  break;
      case 'w'  : RotateView (1,VEC_TILT_UP,0.03);   break;
      case 's'  : RotateView (1,VEC_TILT_DOWN,0.03); break;

      case '\'' : RotateView (2,VEC_ROT_RIGHT,0.03); break;
      case 'l'  : RotateView (2,VEC_ROT_LEFT,0.03);  break;
      case 'p'  : RotateView (2,VEC_TILT_UP,0.03);   break;
      case ';'  : RotateView (2,VEC_TILT_DOWN,0.03); break;

      case 'j'  : RotateView (3,VEC_ROT_RIGHT,0.03); break;
      case 'g'  : RotateView (3,VEC_ROT_LEFT,0.03);  break;
      case 'y'  : RotateView (3,VEC_TILT_UP,0.03);   break;
      case 'h'  : RotateView (3,VEC_TILT_DOWN,0.03); break;
    }
  }
}

void SqkSystem::NextFrame (long elapsed_time, long current_time)
{
  SysSystemDriver::NextFrame (elapsed_time, current_time);

  // Handle all events.
  csEvent *Event;
  while ((Event = EventQueue->Get ()))
  {
    switch (Event->Type)
    {
      case csevKeyDown:
        eatkeypress (Event->Key.Code,
          Event->Key.ShiftKeys & CSMASK_SHIFT,
          Event->Key.ShiftKeys & CSMASK_ALT,
          Event->Key.ShiftKeys & CSMASK_CTRL);
        break;
      case csevMouseMove: break;
      case csevMouseDown: break;
      case csevMouseUp:   break;
    }
    delete Event;
  }

  // Draw the views
  if (piG3D->BeginDraw (CSDRAW_3DGRAPHICS) != S_OK) return;
  view[0]->Draw ();
  view[1]->Draw ();
  view[2]->Draw ();
  view[3]->Draw ();
  piG3D->FinishDraw ();
  piG3D->Print (NULL);
}

  /////////////
 // M A I N //
/////////////

int main (int argc, char* argv[])
{
  Sys = new SqkSystem ();
  config = new csIniFile ("squawk.cfg");
  Sys->world = new csWorld ();
  if (!Sys->Initialize (argc, argv, Sys->world->GetEngineConfigCOM ()))
  {
    Sys->Printf (MSG_FATAL_ERROR, "Error initializing system!\n");
    cleanup ();
    exit (1);
  }

  Sys->InitApp ();
  Sys->Loop ();
  cleanup ();
  return 0;
}
