#include "stdafx.h"

#include <vector>
#include <math.h>

#include "Logging.h"
#include "DataOutputStream.h"
#include "DataInputStream.h"
#include "TArc.h"
#include "Vec.h"
#include "Matrix.h"
#include "Tile.h"
#include "Map.h"
#include "Zone.h"
#include "Animation.h"
#include "AnimationManager.h"

#include "Camera.h"

// MFC - Assists in finding memory leaks:
#ifdef MFC_DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//int Camera::HOG = 64; //128; // Hoehe ueber Grund (typ TexturHeight / 2)

Camera* Camera::_instance = 0;

/****************************************************************************/
/** \brief Liefert die Singleton-Instanz
    \return Pointer auf die Instanz
*****************************************************************************/
Camera* Camera::getInstance()
{
 if (_instance == 0) 
   _instance = new Camera();
 return _instance;
}

/****************************************************************************/
/** \brief Destruktor
*****************************************************************************/
Camera::~Camera()
{
_instance = 0; // Singleton !!!
}

/******************************************************************************/
/** \brief Konstruktor 
*******************************************************************************/
Camera::Camera()
  {
  m_moving   = Camera::STOP;
  m_lifting  = Camera::STOP;  
  m_strafing = Camera::STOP;
  m_rotating = Camera::STOP;
  
  m_moverCount  = 0;
  m_moverDeltaY = 0;
  
  m_zone   = 0;
  m_vecPos; 
  m_vecDir; 

  m_vecPosNext; 
  m_vecDirNext; 
  
  m_vecPosColl; 
  m_vecDirColl; 
  
  m_vecPos.set(0, 0, 0);
  m_vecDir.set(0, 0, 1);   // Normiert auf 1

  m_stepColl = 30; 
  m_stepAnim = 200; 

  m_step = 20; // Einheiten (Pixel) vorwaerts/rueckwaerts
  m_turn =  2; // Grad nach rechts/links drehen

  float x=0;
  float y=0.0;
  float z=0;
  
  float Dirx=-1.0;
  float Diry=0;
  float Dirz=0;
  
  this->setZone(2);
  
  m_vecPos.set(x, y, z);
  m_vecDir.set(Dirx, Diry, Dirz);   // Normiert auf 1
  }

/******************************************************************************/
/** \brief Wird in der Gameloop vor jedem Frame aufgerufen um die Kameraposition
           neu zu berechnen
*******************************************************************************/
void Camera::action()
{
if (m_moving == Camera::STEP_FORWARD)
   stepForward();
else if (m_moving == Camera::STEP_BACKWARD)
   stepBackward();

if (m_rotating == Camera::ROTATE_LEFT)
   stepTurnLeft();
else if (m_rotating == Camera::ROTATE_RIGHT)
   stepTurnRight();
   
if (m_lifting == Camera::STEP_UP)
   stepUp();
else if (m_lifting == Camera::STEP_DOWN)
   stepDown();

// TODO: Strafe implementieren
}

/******************************************************************************/
/** \brief Setter fuer die auszufuehrende Vorwaerts-/Rueckwaertsbewegeung 
*   \param step STOP, STEP_FORWARD, STEP_BACKWARD 
*******************************************************************************/
void Camera::move(STEP_DIR step)
{
if ((step == STOP) || (step == STEP_FORWARD) || (step==STEP_BACKWARD))
  m_moving=step;
}

/******************************************************************************/
/** \brief Getter fuer die auszufuehrende Vorwaerts-/Rueckwaertsbewegeung
*   \return STOP, STEP_FORWARD, STEP_BACKWARD
*******************************************************************************/
Camera::STEP_DIR Camera::isMoving() {return (m_moving);}

/******************************************************************************/
/** \brief Setter fuer die auszufuehrende Hoch-/Runterbewegeung 
*   \param step STOP, STEP_UP, STEP_DOWN 
*******************************************************************************/
void Camera::lift(STEP_DIR step)
{
if ((step == STOP) || (step == STEP_UP) || (step==STEP_DOWN))
  m_lifting=step;
}

/******************************************************************************/
/** \brief Getter fuer die auszufuehrende Hoch-/Runterbewegeung
*   \return STOP, STEP_UP, STEP_DOWN
*******************************************************************************/
Camera::STEP_DIR Camera::isLifting() {return (m_lifting);}

/******************************************************************************/
/** \brief Setter fuer die auszufuehrende Seitwaertsbewegeung 
*   \param step STOP, STRAFE_LEFT, STRAFE_RIGHT 
*******************************************************************************/
void Camera::strafe(STEP_DIR step)
{
if ((step == STOP) || (step == STRAFE_LEFT) || (step==STRAFE_RIGHT))
  m_strafing=step;
}

/******************************************************************************/
/** \brief Getter fuer die auszufuehrende Seitwaertsbewegeung
*   \return STOP, STRAFE_LEFT, STRAFE_RIGHT
*******************************************************************************/
Camera::STEP_DIR Camera::isStrafing(){ return (m_strafing);}

/******************************************************************************/
/** \brief Setter fuer die auszufuehrende Drehbewegung 
*   \param step STOP, ROTATE_LEFT, ROTATE_RIGHT 
*******************************************************************************/
void Camera::rotate(Camera::STEP_DIR step)
{
if ((step == STOP) || (step == ROTATE_LEFT) || (step==ROTATE_RIGHT))
  m_rotating=step;
}

/******************************************************************************/
/** \brief Getter fuer die auszufuehrende Drehbewegung
*   \return STOP, ROTATE_LEFT, ROTATE_RIGHT
*******************************************************************************/
Camera::STEP_DIR Camera::isRotating() {return (m_rotating);}

/******************************************************************************/
/** \brief  Liefert die aktuelle Zone zurueck, in der sich die Camera befindet
*   \return zone
*******************************************************************************/
int Camera::getZone()
{
return (m_zone);
}

/******************************************************************************/
/** \brief Setzt die Zone in der sich die Camera befindet
*   \param zone
*******************************************************************************/
void Camera::setZone(int zone)
{
m_zone = zone;
}

/******************************************************************************/
/** \brief Setter fuer die Position der Camera im 3D Raum
*   \param x
*   \param y
*   \param z
*******************************************************************************/
void Camera::setPos(float x, float y, float z)
{
m_moverCount  = 0;
m_moverDeltaY = 0;
m_vecPos.set(x, y, z);
}

/******************************************************************************/
/** \brief Setter fuer die Blickrichtung der Camera im 3D Raum
*   \param x
*   \param y
*   \param z
*******************************************************************************/
void Camera::setDir(float x, float y, float z)
{
m_vecDir.set(x, y, z);
}

/******************************************************************************/
/** \brief Getter fuer die Position der Camera im 3D Raum
*   \return Referenz auf den Positionsvektor
*******************************************************************************/
Vec& Camera::getPos() {return (m_vecPos);}

/******************************************************************************/
/** \brief Getter fuer die Blickrichtung der Camera im 3D Raum
*   \return Referenz auf den Richtungsvektor
*******************************************************************************/
Vec& Camera::getDir() { return (m_vecDir);}

/******************************************************************************/
/** \brief Ein Step nach vorne (inkl. Kollisionserkennung)
*******************************************************************************/
void Camera::stepForward()
   {
   m_vecDirNext.set(m_vecDir);
   m_vecDirNext.mul(m_step);
    
   m_vecPosNext.set(m_vecPos);
   m_vecPosNext.add(m_vecDirNext);
    
    
   m_vecDirColl.set(m_vecDir);
   m_vecDirColl.mul(m_stepColl);

   m_vecPosColl.set(m_vecPos);
   m_vecPosColl.add(m_vecDirColl);
    
// TRACE ("Zone=%d CameraPos x=%.0f z=%.0f\n", m_zone, m_vecPos.m_x, m_vecPos.m_z);
   if (collision(Tile::WALL, STEP_FORWARD) == false)
      {
      m_vecPos.set(m_vecPosNext); // Neue Position
      this->getLevel();
      incMove();
      }
// TRACE ("Zone=%d CameraPos x=%.0f z=%.0f\n", m_zone, m_vecPos.m_x, m_vecPos.m_z);
   }
 
/******************************************************************************/
/** \brief Ein Step zurueck (inkl. Kollisionserkennung)
*******************************************************************************/
void Camera::stepBackward()
   {
   m_vecDirNext.set(m_vecDir);
   m_vecDirNext.mul(-m_step);
    
   m_vecPosNext.set(m_vecPos);
   m_vecPosNext.add(m_vecDirNext);
    
   m_vecDirColl.set(m_vecDir);
   m_vecDirColl.mul(-m_stepColl);

   m_vecPosColl.set(m_vecPos);
   m_vecPosColl.add(m_vecDirColl);
    
// TRACE ("Zone=%d CameraPos x=%.0f z=%.0f\n", m_zone, m_vecPos.m_x, m_vecPos.m_z);
   if (collision(Tile::WALL, STEP_BACKWARD) == false)
      {
      m_vecPos.set(m_vecPosNext);
      this->getLevel();
      }
// TRACE ("Zone=%d CameraPos x=%.0f z=%.0f\n", m_zone, m_vecPos.m_x, m_vecPos.m_z);
   }

/******************************************************************************/
/** \brief Ein Step nach Rechts (inkl. Kollisionserkennung)
*******************************************************************************/
void Camera::stepStrafeRight()
   {
   m_vecDirNext.set(m_vecDir);
       
   m_vecDirNext.m_x =  m_vecDir.m_z;
   m_vecDirNext.m_z = -m_vecDir.m_x;
       
   m_vecDirNext.mul(m_step);
       
   m_vecPosNext.set(m_vecPos); 
   m_vecPosNext.add(m_vecDirNext);

   m_vecDirColl.set(m_vecDir);
   m_vecDirColl.m_x =  m_vecDir.m_z;
   m_vecDirColl.m_z = -m_vecDir.m_x;
   m_vecDirColl.mul(m_stepColl);
       
   m_vecPosColl.set(m_vecPos);
   m_vecPosColl.add(m_vecDirColl);
       
   if (collision(Tile::WALL, STEP_RIGHT) == false)
      {
      m_vecPos.set(m_vecPosNext);
      this->getLevel();
      }
   }

/******************************************************************************/
/** \brief Ein Step nach Links (inkl. Kollisionserkennung)
*******************************************************************************/
void Camera::stepStrafeLeft()
   {
   m_vecDirNext.set(m_vecDir);
   m_vecDirNext.m_x = -m_vecDir.m_z;
   m_vecDirNext.m_z = m_vecDir.m_x;
   m_vecDirNext.mul(m_step);
   m_vecPosNext.set(m_vecPos); 
   m_vecPosNext.add(m_vecDirNext);
      
   m_vecDirColl.set(m_vecDir);
   m_vecDirColl.m_x = -m_vecDir.m_z;
   m_vecDirColl.m_z =  m_vecDir.m_x;
   m_vecDirColl.mul(m_stepColl);
   m_vecPosColl.set(m_vecPos);
   m_vecPosColl.add(m_vecDirColl);
      
   if (collision(Tile::WALL, STEP_LEFT) == false)
      {
      m_vecPos.set(m_vecPosNext);
      this->getLevel();
      }
   }
  
/******************************************************************************/
/** \brief Ein Step nach Oben 
*******************************************************************************/
void Camera::stepUp()
   {
   m_vecPosNext.set(m_vecPos); 
   m_vecPosNext.m_y += m_step;
    
   m_vecPosColl.set(m_vecPos); 
   m_vecPosColl.m_y += m_stepColl;
    
// if (collision(Tile.FLOOR) == false) // TODO: ggf. CEILING einfuehren
   m_vecPos.set(m_vecPosNext);
   }

/******************************************************************************/
/** \brief Ein Step nach Unten 
*******************************************************************************/
void Camera::stepDown()
   {
   m_vecPosNext.set(m_vecPos); 
   m_vecPosNext.m_y -= m_step;
    
   m_vecPosColl.set(m_vecPos); 
   m_vecPosColl.m_y -= m_stepColl;
    
// if (collision(Tile.FLOOR) == false)
   m_vecPos.set(m_vecPosNext);
   }

/******************************************************************************/
/** \brief Nach rechts drehen 
*******************************************************************************/
void Camera::stepTurnRight()
   {
   TArc* arc = TArc::getInstance();
   int w = arc->norm(m_turn);
   m_vecDir.rotateY(w);
   }

/******************************************************************************/
/** \brief Nach links drehen 
*******************************************************************************/
void Camera::stepTurnLeft()
   {
   TArc* arc = TArc::getInstance();
   int w = arc->norm(-m_turn);
   m_vecDir.rotateY(w);
   }

/******************************************************************************/
/** \brief Nach oben blicken 
*******************************************************************************/
void Camera::aimUp()
   {
   TArc* arc = TArc::getInstance();
   int w = arc->norm(m_turn);
   m_vecDir.rotateX(w);
   }

/******************************************************************************/
/** \brief Nach unten blicken 
*******************************************************************************/
void Camera::aimDown()
   {
   TArc* arc = TArc::getInstance();
   int w = arc->norm(-m_turn);
   m_vecDir.rotateX(w);
   }

/******************************************************************************/
/** \brief Pendelbewegung beim laufen simulieren:
           Beim gehen und laufen bewegt sich auch das Auge des Spielers/Camera 
           auf und ab. Dies wird hier durch eine Sinusfunktion simuliert.
           Die Wahl der Parameter steps und alti sind durch ausprobieren festgelegt
           worden. Alles andere sieht wie das Gehen mit einem Holzbein aus... 
*******************************************************************************/
void Camera::incMove()
{
int   steps = 20;
float alti  = 1;

m_vecPos.m_y -=m_moverDeltaY; // altes Move entfernen

float intervall = (float)(2.0 * 3.14) / (float)steps; 
float a = (intervall * (float) m_moverCount);
m_moverDeltaY = sin(a) * alti;

m_vecPos.m_y +=m_moverDeltaY; // neues move hinzufuegen

m_moverCount++;
if (m_moverCount > steps)
   m_moverCount=0;
}

/******************************************************************************/
/** \brief Testet auf eine Kollision
    \param typ forward, backward, ...
    \param dir Bewegungsrichtung
    \return true-->Kollision, false-->keine Kollision 
*******************************************************************************/
bool Camera::collision(int typ, int dir)
{
Map* map = Map::getInstance();

int i;
Zone* zone = 0;

Vec vstart, vendW, vendP, vendA;

vstart.set(0,0,0);
vendW.set(0,0,0);
vendP.set(0,0,0);
vendA.set(0,0,0);

// Suche die Zone aus der Map
for (i=0; i < (int)map->zoneList.size(); i++)
   {
   if (map->zoneList[i]->m_zone == m_zone)
      {
      zone = map->zoneList[i];
      break;
      }
   }

// Falls es keine Zone zu dieser ZoneNr gibt -->zurueck
if (zone == 0)
  {
  // TODO: Fehlerbehandlung einpflegen
  return (false);
  }

// Die Camera hat eine Position und Ausrichtung relativ zur 
// untransformierten Map. Die Kollisionsabfrage erfolgt aber
// gegen eine (in das Frustum) transformierte Map
// Deshalb folgende Methodik:
switch (dir)
   {
   case STEP_FORWARD:
   vendW.m_z = m_stepColl;
   vendP.m_z = m_step;
   vendA.m_z = m_stepAnim;
   break;

   case STEP_BACKWARD:
   vendW.m_z = -m_stepColl;
   vendP.m_z = -m_step;
   vendA.m_z = -m_stepAnim;
   break;

   case STEP_LEFT:
   vendW.m_x = -m_stepColl;
   vendP.m_x = -m_step;
   vendA.m_x = m_stepAnim;
   break;

   case STEP_RIGHT:
   vendW.m_x = m_stepColl;
   vendP.m_x = m_step;
   vendA.m_x = m_stepAnim;
   break;

   case STEP_UP:
   vendW.m_y = m_stepColl;
   vendP.m_y = m_step;
   vendA.m_y = m_stepAnim;
   break;

   case STEP_DOWN:
   vendW.m_y = -m_stepColl;
   vendP.m_y = -m_step;
   vendA.m_y = -m_stepAnim;
   break;

   default:
   return (false);
   break;
   }

// Gehe ueber alle Tiles dieser Zone
for (i=0; i < (int)zone->tileList.size(); i++)
   {
   Tile* tile = (Tile*) zone->tileList[i];
   if (tile->getTyp() == typ) // Tile.WALL)
      {

      if (tile->m_animationID != -1)
         {
         if (tile->intersect(vstart, vendA) == true)
            {
            AnimationManager* ap = AnimationManager::getInstance();
            Animation* animation = ap->get(tile->m_animationID);
            animation->runForwardWaitBackwardStop();
            int id2 = animation->getID2();
            if (id2 != -1)
               { 
               animation = ap->get(id2);            
               animation->runForwardWaitBackwardStop();
               }
            }
         }

      if (tile->intersect(vstart, vendW) == true)
         {
         return (true);
         }
      }
   else if (tile->getTyp() == Tile::PORTAL)
      {
      if (tile->intersect(vstart, vendP) == true)
         {
         m_zone = tile->getZoneOTHER();
         return (false);
         }
      }
   }
   
 return (false);
 }

/******************************************************************************/
/** \brief getLevel - Keine Ahnung mehr, was das macht...
    \return level 
*******************************************************************************/
float Camera::getLevel()
 {
 Vec vStart, vEnd; 
 
 vStart.set(0,0,0);
 vStart.m_y -= m_moverDeltaY;
 
 vEnd.set(vStart);
 vEnd.m_y -= (float) (m_HOG * 2);
 
 float len = 0;
 float height = 0;
 float delta = 0;
 
 Map* map = Map::getInstance();

 for (int n=0; n < (int)map->zoneList.size(); n++)
    {
    Zone* zone = map->zoneList[n];

    for (int i=0; i < (int)zone->tileList.size(); i++)
       {
       Tile* tile = (Tile*) zone->tileList[i];
       if (m_zone == tile->getZoneTHIS())
          {
          if (tile->getTyp() == Tile::FLOOR) 
             {
             if (tile->intersect(vStart, vEnd) == true)
                {
                len = tile->getInterLegth();
                height = len * (float)(m_HOG * 2);  
                float h = (float)m_HOG; 
                delta = h - height;
                m_vecPos.m_y += delta;
                return (len);
                }
             }
          }
       }
   }
 return (len);
 }

/******************************************************************************/
/** \brief Obligatorische Dump-Methode fuer debuging
*******************************************************************************/
void Camera::dump()
  {
  // TODO: dump ins Logging schreiben...
/*
  System.out.println("camera " + m_zone + 
                           " " + m_vecPos.m_x +
                           " " + m_vecPos.m_y +
                           " " + m_vecPos.m_z +
                           " " + m_vecDir.m_x +
                           " " + m_vecDir.m_y +
                           " " + m_vecDir.m_z);
*/
  }
    
/****************************************************************************/
/** \brief Liest das Camera-Objekt aus dem Inputstream
*   \param dataIn DataInputStream
*******************************************************************************/
void Camera::load(DataInputStream& dataIn)
   {
   float x=0, y=0, z=0;
   try
      {
      m_zone = dataIn.readInt();
      m_HOG  = dataIn.readInt();

      x = (float)dataIn.readDouble();
      y = (float)dataIn.readDouble();
      z = (float)dataIn.readDouble();
      m_vecPos.set(x, y, z);

      x = (float)dataIn.readDouble();
      y = (float)dataIn.readDouble();
      z = (float)dataIn.readDouble();
      m_vecDir.set(x, y, z);
      }
   catch (std::exception eof)
      {
      // TODO: ins Logfile protokollieren
      }
  }

/******************************************************************************/
/** \brief Serialisiert das Camera-Objekt in den Outputstream
    \param dataOut DataOutputStream
    \throws IOException 
*******************************************************************************/
void Camera::save(DataOutputStream& dataOut) 
  {
  dataOut.writeInt(m_zone);
  dataOut.writeInt(m_HOG);

  dataOut.writeDouble(m_vecPos.m_x);
  dataOut.writeDouble(m_vecPos.m_y);
  dataOut.writeDouble(m_vecPos.m_z);

  dataOut.writeDouble(m_vecDir.m_x);
  dataOut.writeDouble(m_vecDir.m_y);
  dataOut.writeDouble(m_vecDir.m_z);
  }
