#include <d3d8.h>

#include ".\Level.h"
#include ".\LD.h"

#include ".\Player.h"
#include ".\Plankton.h"
#include ".\HomingEnemy.h"
#include ".\SittingEnemy.h"
#include ".\Obstacle.h"
#include ".\Particle.h"



Level::Level() :
  pPlayer( NULL )
{

}



Level::~Level()
{
  ClearObjects();
}



GameObject* Level::SpawnObject( const std::string& Type, int X, int Y, float Size )
{
  GameObject*   pObj = SpawnObject( Type, X, Y );
  if ( pObj == NULL )
  {
    return pObj;
  }

  if ( pObj->m_Type == LD::GO_SITTING_ENEMY )
  {
    ( (SittingEnemy*)pObj )->SizeFactor = Size;
  }
  else if ( pObj->m_Type == LD::GO_HOMING_ENEMY )
  {
    ( (HomingEnemy*)pObj )->SizeFactor = Size;
  }
  return pObj;
}



GameObject* Level::SpawnObject( const std::string& Type, int X, int Y, int Width, int Height, float Angle )
{
  GameObject*   pObj = NULL;

  if ( Type == "player" )
  {
    pObj = new Player();
    pPlayer = (Player*)pObj;
  }
  else if ( Type == "plankton" )
  {
    pObj = new Plankton();
  }
  else if ( Type == "obstacle" )
  {
    pObj = new Obstacle();
    // level borders are always hidde
    if ( Objects.size() < 4 )
    {
      pObj->Hidden = true;
    }
  }
  else if ( Type == "particle" )
  {
    pObj = new Particle();
  }
  else if ( Type == "homingenemy" )
  {
    pObj = new HomingEnemy();
  }
  else if ( Type == "sittingenemy" )
  {
    pObj = new SittingEnemy();
  }
  else
  {
    return NULL;
  }
  pObj->X = X;
  pObj->Y = Y;
  if ( Width )
  {
    pObj->Width = Width;
  }
  if ( Height )
  {
    pObj->Height = Height;
  }
  if ( Angle != 0.0f )
  {
    pObj->Angle = Angle;
  }
  Objects.push_back( pObj );

  return pObj;
}



void Level::ClearObjects()
{
  std::list<GameObject*>::iterator    itO( Objects.begin() );
  while ( itO != Objects.end() )
  {
    GameObject*   pObj( *itO );

    delete pObj;

    ++itO;
  }
  Objects.clear();
  pPlayer = NULL;
}



void Level::Display()
{
  theApp.m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );

  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
  theApp.m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );

  theApp.RenderTextureSection2d( 0, 0, theApp.m_TexSec[LD::TEX_METAL_BACKGROUND], 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 640, 480 );

  theApp.RenderTextureSection2d( 0, 470, theApp.m_TexSec[LD::TEX_BORDER_EDGE], 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 10, 10, tTextureSection::TSF_V_MIRROR );
  theApp.RenderTextureSection2d( 630, 0, theApp.m_TexSec[LD::TEX_BORDER_EDGE], 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff, 10, 10, tTextureSection::TSF_V_MIRROR );

  theApp.RenderTextureSection2d( 0, 0, tTextureSection( NULL ), 0xff6F6F6F, 0xff6F6F6F, 0xff6F6F6F, 0xff6F6F6F, 630, 10 );
  theApp.RenderTextureSection2d( 0, 0, tTextureSection( NULL ), 0xff6F6F6F, 0xff6F6F6F, 0xff6F6F6F, 0xff6F6F6F, 10, 470 );

  theApp.RenderTextureSection2d( 10, 470, tTextureSection( NULL ), 0xffDEDEDE, 0xffDEDEDE, 0xffDEDEDE, 0xffDEDEDE, 620, 10 );
  theApp.RenderTextureSection2d( 630, 10, tTextureSection( NULL ), 0xffDEDEDE, 0xffDEDEDE, 0xffDEDEDE, 0xffDEDEDE, 10, 470 );

  std::list<GameObject*>::iterator    itO( Objects.begin() );
  while ( itO != Objects.end() )
  {
    GameObject*   pObj( *itO );

    if ( !pObj->Hidden )
    {
      pObj->Display();
    }

    ++itO;
  }
}



void Level::UpdateFrame( const float ElapsedTime )
{
  std::list<GameObject*>::iterator    itO( Objects.begin() );
  while ( itO != Objects.end() )
  {
    GameObject*   pObj( *itO );

    if ( pObj->RemoveMe )
    {
      if ( pObj == pPlayer )
      {
        pPlayer = NULL;
      }
      itO = Objects.erase( itO );

      pObj->OnObjectRemoved( *this, pObj );

      // notify all other objects
      std::list<GameObject*>::iterator    itO2( Objects.begin() );
      while ( itO2 != Objects.end() )
      {
        GameObject*   pObj2( *itO2 );

        pObj2->OnObjectRemoved( *this, pObj );
        ++itO2;
      }

      delete pObj;
    }
    else
    {
      pObj->Update( *this, ElapsedTime );
      ++itO;
    }
  }
}



bool Level::IsAreaBlocked( GameObject* pObj, RECT& Bounds )
{
  std::list<GameObject*>::iterator    itO( Objects.begin() );
  while ( itO != Objects.end() )
  {
    GameObject*   pOtherObj( *itO );

    if ( pObj == pOtherObj )
    {
      ++itO;
      continue;
    }

    RECT    rcDummy;

    bool    intersecting = !!IntersectRect( &rcDummy, &Bounds, &pOtherObj->Bounds() );

    if ( intersecting )
    {
      if ( ( pObj->m_Type == LD::GO_PLAYER )
      &&   ( pOtherObj->m_Type == LD::GO_SITTING_ENEMY ) )
      {
        Player*         pPlayer = (Player*)pObj;
        SittingEnemy*   pSitting = (SittingEnemy*)pOtherObj;
        if ( pPlayer->SizeFactor <= pSitting->SizeFactor )
        {
          return true;
        }
      }

      if ( ( ( pObj->m_Type == LD::GO_HOMING_ENEMY )
      ||     ( pObj->m_Type == LD::GO_SITTING_ENEMY ) )
      &&   ( ( pOtherObj->m_Type == LD::GO_HOMING_ENEMY )
      ||     ( pOtherObj->m_Type == LD::GO_SITTING_ENEMY ) ) )
      {
        return true;
      }
      if ( pOtherObj->m_Type == LD::GO_OBSTACLE )
      {
        return true;
      }
    }
    ++itO;
  }
  return false;
}



bool Level::CanMove( GameObject* pObj, int DX, int DY )
{
  RECT  bounds = pObj->Bounds();

  OffsetRect( &bounds, DX, DY );

  return !IsAreaBlocked( pObj, bounds );
}



bool Level::MoveObject( GameObject* pObj, float DX, float DY )
{
  pObj->XFract += DX;
  pObj->YFract += DY;


  while ( ( pObj->XFract >= 1.0f )
  ||      ( pObj->XFract < 0.0f )
  ||      ( pObj->YFract >= 1.0f )
  ||      ( pObj->YFract < 0.0f ) )
  {
    if ( pObj->XFract >= 1.0f )
    {
      if ( CanMove( pObj, 1, 0 ) )
      {
        pObj->XFract -= 1.0f;
        ++pObj->X;
      }
      else
      {
        pObj->XFract = 0.9999f ;
      }
    }
    if ( pObj->XFract < 0.0f )
    {
      if ( CanMove( pObj, -1, 0 ) )
      {
        pObj->XFract += 1.0f;
        --pObj->X;
      }
      else
      {
        pObj->XFract = 0.0f;
      }
    }
    if ( pObj->YFract >= 1.0f )
    {
      if ( CanMove( pObj, 0, 1 ) )
      {
        pObj->YFract -= 1.0f;
        ++pObj->Y;
      }
      else
      {
        pObj->YFract = 0.9999f;
      }
    }
    if ( pObj->YFract < 0.0f )
    {
      if ( CanMove( pObj, 0, -1 ) )
      {
        pObj->YFract += 1.0f;
        --pObj->Y;
      }
      else
      {
        pObj->YFract = 0.0f;
      }
    }
  }
  return true;
}



bool Level::ReadLine( FILE* f, std::string& Line )
{
  Line.clear();
  while ( true )
  {
    int c = fgetc( f );
    if ( c == EOF )
    {
      return !Line.empty();
    }

    if ( c == '\r' )
    {
      continue;
    }
    else if ( c == '\n' )
    {
      return true;
    }
    else
    {
      Line += (char)c;
    }
  }
}



void Level::SplitString( const std::string& Source, char Separator, std::vector<std::string>& Values )
{
  size_t      pos = 0;
  size_t      startPos = 0;

  while ( ( pos = Source.find( Separator, startPos ) ) != std::string::npos )
  {
    Values.push_back( Source.substr( startPos, pos - startPos ) );
    startPos = pos + 1;
  }
  if ( ( Source.length() >= startPos )
    && ( !Source.empty() ) )
  {
    Values.push_back( Source.substr( startPos, Source.length() - startPos ) );
  }
}



int Level::IntValue( const std::string& Text, int Min, int Max )
{
  if ( Text == "r" )
  {
    if ( Min == Max )
    {
      return Min;
    }
    return Min + rand() % ( Max - Min );
  }
  return atoi( Text.c_str() );
}



bool Level::Load( int Nr )
{
  ClearObjects();
  Info.clear();

  char    Temp[260];

  wsprintf( Temp, "data\\level%d.txt", Nr );

  FILE*   handle = fopen( Temp, "r" );
  if ( handle == NULL )
  {
    return false;
  }
  std::string     line;
  int             curLine = 0;
  int             curFloor = 0;
  while ( ReadLine( handle, line ) )
  {
    if ( line.empty() )
    {
      continue;
    }
    if ( line[0] == '#' )
    {
      // next plane of level
      continue;
    }
    else
    {
      size_t    equPos = line.find( '=' );
      if ( equPos != std::string::npos )
      {
        std::string     paramName = line.substr( 0, equPos );
        std::string     paramValue = line.substr( equPos + 1 );

        std::vector<std::string>    paramValues;

        SplitString( paramValue, ',', paramValues );

        if ( paramName == "i" )
        {
          Info = paramValue;
        }
        if ( paramName == "o" )
        {
          if ( paramValues.size() == 3 )
          {
            while ( true )
            {
              GameObject*   pObj = SpawnObject( paramValues[0],
                IntValue( paramValues[1], 10, 620 ),
                IntValue( paramValues[2], 10, 460 ) );
              if ( ( ( paramValues[1] == "r" )
              ||     ( paramValues[2] == "r" ) )
              &&   ( IsAreaBlocked( pObj, pObj->Bounds() ) ) )
              {
                Objects.remove( pObj );
              }
              else
              {
                break;
              }
            }
          }
          else if ( paramValues.size() == 4 )
          {
            while ( true )
            {
              GameObject*   pObj = SpawnObject( paramValues[0],
                IntValue( paramValues[1], 10, 620 ),
                IntValue( paramValues[2], 10, 460 ),
                (float)atof( paramValues[3].c_str() ) );
              if ( ( ( paramValues[1] == "r" )
              ||     ( paramValues[2] == "r" ) )
              &&   ( IsAreaBlocked( pObj, pObj->Bounds() ) ) )
              {
                Objects.remove( pObj );
              }
              else
              {
                break;
              }
            }
          }
          else if ( paramValues.size() == 5 )
          {
            while ( true )
            {
              GameObject*   pObj = SpawnObject( paramValues[0],
                IntValue( paramValues[1], 10, 620 ),
                IntValue( paramValues[2], 10, 460 ),
                atoi( paramValues[3].c_str() ),
                atoi( paramValues[4].c_str() ) );
              if ( ( ( paramValues[1] == "r" )
              ||     ( paramValues[2] == "r" ) )
              &&   ( IsAreaBlocked( pObj, pObj->Bounds() ) ) )
              {
                Objects.remove( pObj );
              }
              else
              {
                break;
              }
            }
          }
          else if ( paramValues.size() == 6 )
          {
            while ( true )
            {
              GameObject*   pObj = SpawnObject( paramValues[0],
                IntValue( paramValues[1], 10, 620 ),
                IntValue( paramValues[2], 10, 460 ),
                atoi( paramValues[3].c_str() ),
                atoi( paramValues[4].c_str() ),
                (float)atof( paramValues[5].c_str() ) );
              if ( ( ( paramValues[1] == "r" )
              ||     ( paramValues[2] == "r" ) )
              &&   ( IsAreaBlocked( pObj, pObj->Bounds() ) ) )
              {
                Objects.remove( pObj );
              }
              else
              {
                break;
              }
            }
          }
        }
      }
    }
  }
  fclose( handle );

  return true;
}



void Level::SpawnParticles( int Num, int X, int Y, unsigned int Color )
{
  for ( int i = 0; i < Num; ++i )
  {
    Particle* pParticle = (Particle*)SpawnObject( "particle", X, Y );

    pParticle->DX = 0.1f * ( ( rand() % 330 ) - 165.0f );
    pParticle->DY = 0.1f * ( ( rand() % 330 ) - 165.0f );
    pParticle->LifeTime = 2.0f + ( rand() % 10 ) * 0.1f;
    pParticle->Color = Color;
  }
}