/*
    Copyright (C) 1998 by Jorrit Tyberghein

    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 "cssys/common/system.h"
#include "csutil/inifile.h"
#include "apps/simple/simple.h"
#include "csengine/sector.h"
#include "csengine/world.h"
#include "csengine/csview.h"
#include "csengine/camera.h"
#include "csengine/light.h"
#include "csengine/polygon.h"
#include "csobject/nameobj.h"
#include "csparser/csloader.h"
#include "igraph3d.h"
#include "itxtmgr.h"

Simple* Sys = NULL;

#define FRAME_WIDTH Sys->FrameWidth
#define FRAME_HEIGHT Sys->FrameHeight

//-----------------------------------------------------------------------------

Simple::Simple ()
{
  view = NULL;
  world = NULL;
  dynlight = NULL;
}

Simple::~Simple ()
{
  delete view;
  delete world;
}

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

void Simple::InitApp ()
{
  // Open the main system. This will open all the previously loaded
  // COM drivers.
  if (!Open ("Simple Crystal Space Application"))
  {
    Printf (MSG_FATAL_ERROR, "Error opening system!\n");
    cleanup ();
    exit (1);
  }

  // Some commercials...
  Printf (MSG_INITIALIZATION, "Simple Crystal Space Application version 0.1.\n");
  ITextureManager* txtmgr;
  piG3D->GetTextureManager (&txtmgr);
  txtmgr->SetVerbose (true);

  // First disable the lighting cache. Our app is simple enough
  // not to need this.
  world->EnableLightingCache (false);

  // Initialize our world.
  world->Initialize (GetISystemFromSystem (this), piG3D, config);

  // Create our world.
  Printf (MSG_INITIALIZATION, "Creating world!...\n");

  CSLoader::LoadLibrary (world, "standard", "standard.zip");
  csTextureHandle* tm = CSLoader::LoadTexture (world, "stone", "stone4.gif");

  room = world->NewSector ();
  csNameObject::AddName (*room, "room"); 
  csPolygon3D* p;
  p = room->NewPolygon (tm);
  p->AddVertex (-5, 0, 5);
  p->AddVertex (5, 0, 5);
  p->AddVertex (5, 0, -5);
  p->AddVertex (-5, 0, -5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  p = room->NewPolygon (tm);
  p->AddVertex (-5, 20, -5);
  p->AddVertex (5, 20, -5);
  p->AddVertex (5, 20, 5);
  p->AddVertex (-5, 20, 5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  p = room->NewPolygon (tm);
  p->AddVertex (-5, 20, 5);
  p->AddVertex (5, 20, 5);
  p->AddVertex (5, 0, 5);
  p->AddVertex (-5, 0, 5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  p = room->NewPolygon (tm);
  p->AddVertex (5, 20, 5);
  p->AddVertex (5, 20, -5);
  p->AddVertex (5, 0, -5);
  p->AddVertex (5, 0, 5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  p = room->NewPolygon (tm);
  p->AddVertex (-5, 20, -5);
  p->AddVertex (-5, 20, 5);
  p->AddVertex (-5, 0, 5);
  p->AddVertex (-5, 0, -5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  p = room->NewPolygon (tm);
  p->AddVertex (5, 20, -5);
  p->AddVertex (-5, 20, -5);
  p->AddVertex (-5, 0, -5);
  p->AddVertex (5, 0, -5);
  p->SetTextureSpace (p->Vobj (0), p->Vobj (1), 3);

  csStatLight* light;
  light = new csStatLight (-3, 5, 0, 10, 1, 0, 0, false);
  room->AddLight (light);
  light = new csStatLight (3, 5, 0, 10, 0, 0, 1, false);
  room->AddLight (light);
  light = new csStatLight (0, 5, -3, 10, 0, 1, 0, false);
  room->AddLight (light);

  world->Prepare (piG3D);

  // Create a dynamic light.
  angle = 0;
  dynlight = new csDynLight (cos (angle)*3, 17, sin (angle)*3, 7, 1, 0, 0);
  world->AddDynLight (dynlight);
  dynlight->SetSector (room);
  dynlight->Setup ();

  Printf (MSG_INITIALIZATION, "--------------------------------------\n");

  // csView is a view encapsulating both a camera and a clipper.
  // You don't have to use csView as you can do the same by
  // manually creating a camera and a clipper but it makes things a little
  // easier.
  view = new csView (world, piG3D);
  view->SetSector (room);
  view->GetCamera ()->SetPosition (csVector3 (0, 5, 0));
  view->SetRectangle (2, 2, FRAME_WIDTH - 4, FRAME_HEIGHT - 4);

  txtmgr->AllocPalette ();
}

void Simple::eatkeypress (int key, bool shift, bool alt, bool ctrl, float elapsed_time)
{
  (void)shift; (void)alt; (void)ctrl;
  // This routine uses the elapsed time given to calculate
  // how much the camera should rotate. This means that speed
  // of rotation.
  float speed = elapsed_time * 0.05 / 1000.;
  speed = .03;
  switch (key)
  {
    case CSKEY_ESC: System->Shutdown = true; break;
    case CSKEY_RIGHT: view->GetCamera ()->Rotate (VEC_ROT_RIGHT, speed); break;
    case CSKEY_LEFT: view->GetCamera ()->Rotate (VEC_ROT_LEFT, speed); break;
    case CSKEY_UP: view->GetCamera ()->Rotate (VEC_TILT_UP, speed); break;
    case CSKEY_DOWN: view->GetCamera ()->Rotate (VEC_TILT_DOWN, speed); break;
  }
}

void Simple::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,
	    elapsed_time);
        break;
      case csevBroadcast:
        break;
      case csevMouseDown:
        break;
      case csevMouseMove:
        break;
      case csevMouseUp:
        break;
    }
    delete Event;
  }

  // Move the dynamic light around.
  angle += elapsed_time * 0.4 / 1000.;
  while (angle >= 2.*3.1415926) angle -= 2.*3.1415926;
  dynlight->Move (room, cos (angle)*3, 17, sin (angle)*3);
  dynlight->Setup ();

  // Tell 3D driver we're going to display 3D things.
  if (piG3D->BeginDraw (CSDRAW_3DGRAPHICS) != S_OK) return;

  view->Draw ();

  // Start drawing 2D graphics.
  //if (Sys->piG2D->BeginDraw (CSDRAW_2DGRAPHICS) != S_OK) return;

  // Drawing code ends here.
  piG3D->FinishDraw ();
  // Print the final output.
  piG3D->Print (NULL);
}

/*
 * This is a debug handler that is called when your
 * program crashes (only on Unix/Linux right now).
 * You can use this to perform some last minutes dumps
 * (like saving the current camera location as is done here
 * in this example).
 */
void debug_dump ()
{
  Sys->view->GetCamera ()->SaveFile ("coord.bug");
  Sys->Printf (MSG_DEBUG_0, "Camera saved in coord.bug\n");
}

/*---------------------------------------------------------------------*
 * Main function
 *---------------------------------------------------------------------*/
int main (int argc, char* argv[])
{
  srand (time (NULL));

  // Create our main class.
  Sys = new Simple ();

  // Open our configuration file.
  config = new csIniFile ("simple.cfg");

  // Create our world. The world is the representation of
  // the 3D engine.
  Sys->world = new csWorld ();

  // Initialize the main system. This will load all needed
  // COM drivers (3D, 2D, network, sound, ...) and initialize them.
  if (!Sys->Initialize (argc, argv, Sys->world->GetEngineConfigCOM ()))
  {
    Sys->Printf (MSG_FATAL_ERROR, "Error initializing system!\n");
    cleanup ();
    exit (1);
  }

  // Our own initialization routine.
  Sys->InitApp ();

  // Main loop.
  Sys->Loop ();

  // Cleanup.
  cleanup ();

  return 0;
}
