#include "Level.h"
#include "Unit.h"
#include "Player.h"
#include "Cloud.h"
#include "Particle.h"
#include "LD.h"

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



Level::Level() :
  FixedTimeStepPos( 0.0f ),
  Score( 0 ),
  NumEmeralds( 0 ),
  pPlayer( NULL )
{
  for ( int i = 0; i < 32; ++i )
  {
    for ( int j = 0; j < 24; ++j )
    {
      Grid[i][j] = false;
    }
  }
  for ( int i = 0; i < 33; ++i )
  {
    for ( int j = 0; j < 25; ++j )
    {
      CloudUnits[i][j] = NULL;
    }
  }
}



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



void Level::ClearUnits()
{
  NumEmeralds = 0;

  std::list<Unit*>::iterator    itUL( Units.begin() );
  while ( itUL != Units.end() )
  {
    Unit*   pUnit( *itUL );

    delete pUnit;

    ++itUL;
  }
  Units.clear();

  
  std::list<Unit*>::iterator    itUL2( DecoUnits.begin() );
  while ( itUL2 != DecoUnits.end() )
  {
    Unit*   pUnit( *itUL2 );

    delete pUnit;

    ++itUL2;
  }
  DecoUnits.clear();

  // start growing from outside the borders
  for ( int i = 0; i < 32; ++i )
  {
    for ( int j = 0; j < 24; ++j )
    {
      Grid[i][j] = false;
    }
  }
  for ( int i = 0; i < 33; ++i )
  {
    for ( int j = 0; j < 25; ++j )
    {
      CloudUnits[i][j] = NULL;
    }
  }
  GrowingTiles.clear();
  for ( int i = 0; i < 32; ++i )
  {
    GrowingTiles.push_back( LD::Point( i, -1 ) );
    GrowingTiles.push_back( LD::Point( i, 24 ) );
  }
  for ( int i = 0; i < 24; ++i )
  {
    GrowingTiles.push_back( LD::Point( -1, i ) );
    GrowingTiles.push_back( LD::Point( 32, i ) );
  }
}



void Level::GrowTile()
{
  // find a tile to grow
  std::map<int, std::list<LD::Point> >     growingChances;


  std::list<LD::Point>    chances;

  for ( int i = 0; i < 32; ++i )
  {
    for ( int j = 0; j < 24; ++j )
    {
      if ( !Grid[i][j] )
      {
        int   numNeighbours = 0;

        if ( ( i == 0 )
        ||   ( ( i > 0 )
        &&     ( Grid[i - 1][j] ) ) )
        {
          ++numNeighbours;
        }
        if ( ( i == 31 )
        ||   ( ( i < 31 )
        &&     ( Grid[i + 1][j] ) ) )
        {
          ++numNeighbours;
        }
        if ( ( j == 0 )
        ||   ( ( j > 0 )
        &&     ( Grid[i][j - 1] ) ) )
        {
          ++numNeighbours;
        }
        if ( ( j == 23 )
        ||   ( ( j < 23 )
        &&     ( Grid[i][j + 1] ) ) )
        {
          ++numNeighbours;
        }
        if ( numNeighbours > 0 )
        {
          // this one has growing tiles as neighbours
          growingChances[numNeighbours].push_back( LD::Point( i, j ) );

          chances.push_back( LD::Point( i, j ) );
        }
      }
    }
  }
  if ( growingChances.empty() )
  {
    return;
  }

  //auto lastEntry = *growingChances.rbegin();

  //int   randomEntry = rand() % lastEntry.second.size();
  int   randomEntry = rand() % chances.size();

  //auto itTile = lastEntry.second.begin();
  auto itTile = chances.begin();
  std::advance( itTile, randomEntry );

  if ( Grid[itTile->x][itTile->y] == 0 )
  {
    // grow here
    //theApp.PlaySoundA( LD::SOUND_LOST_TILE );
    SetTile( itTile->x, itTile->y, 5 );
  }
}



void Level::SetTile( int X, int Y, int Value )
{
  if ( ( X < 0 )
  ||   ( X >= 32 )
  ||   ( Y < 0 )
  ||   ( Y >= 24 ) )
  {
    return;
  }
  Grid[X][Y] = 5;

  // move cloud units
  CheckCloud( X - 1,  Y - 1, X,     Y );
  CheckCloud( X,      Y - 1, X + 1, Y );
  CheckCloud( X - 1,  Y,     X,     Y + 1 );
  CheckCloud( X,      Y,     X + 1, Y + 1 );
}



bool Level::TileSet( int X, int Y )
{
  if ( ( X < 0 )
  ||   ( X >= 32 )
  ||   ( Y < 0 )
  ||   ( Y >= 24 ) )
  {
    return true;
  }
  return ( Grid[X][Y] > 0 );
}



void Level::CheckCloud( int X, int Y, int CloudX, int CloudY )
{
  bool    tile1Set = TileSet( X, Y );
  bool    tile2Set = TileSet( X + 1, Y );
  bool    tile3Set = TileSet( X, Y + 1 );
  bool    tile4Set = TileSet( X + 1, Y + 1 );

  if ( ( CloudX < 0 )
  ||   ( CloudX > 32 )
  ||   ( CloudY < 0 )
  ||   ( CloudY > 24 ) )
  {
    return;
  }
  if ( ( tile1Set )
  &&   ( tile2Set )
  &&   ( tile3Set )
  &&   ( tile4Set ) )
  {
    // all set, cloud not required anymore
    if ( CloudUnits[CloudX][CloudY] != NULL )
    {
      CloudUnits[CloudX][CloudY]->RemoveMe = true;
      CloudUnits[CloudX][CloudY] = NULL;
    }
  }
  else if ( ( !tile1Set )
  &&        ( !tile2Set )
  &&        ( !tile3Set )
  &&        ( !tile4Set ) )
  {
    // all set, cloud not required anymore
    if ( CloudUnits[CloudX][CloudY] != NULL )
    {
      CloudUnits[CloudX][CloudY]->RemoveMe = true;
      CloudUnits[CloudX][CloudY] = NULL;
    }
  }
  else
  {
    // any other combination needs a cloud
    if ( CloudUnits[CloudX][CloudY] == NULL )
    {
      Cloud*    pCloud = new Cloud();

      pCloud->ZoomFactor = 0.9f + ( rand() % 100 ) * 0.012f;
      pCloud->RotationDelta = -90.0f + ( rand() % 181 );
      pCloud->Position.set( CloudX * 32, CloudY * 32 );
      AddDecoUnit( pCloud );
      CloudUnits[CloudX][CloudY] = pCloud;
    }
  }
}



void Level::Display( IDirect3DDevice8* pDevice )
{
  for ( int i = 0; i < 32; ++i )
  {
    for ( int j = 0; j < 24; ++j )
    {
      if ( Grid[i][j] )
      {
        theApp.RenderTextureSection2d( i * 32, j * 32, theApp.m_TexSec[LD::TEX_GRID_FILLED] );
      }
    }
  }

  std::list<Unit*>::iterator    itUL( DecoUnits.begin() );
  while ( itUL != DecoUnits.end() )
  {
    Unit*   pUnit( *itUL );

    pUnit->Display( pDevice );

    ++itUL;
  }


  std::list<Unit*>::iterator    itUL2( Units.begin() );
  while ( itUL2 != Units.end() )
  {
    Unit*   pUnit( *itUL2 );

    pUnit->Display( pDevice );

    ++itUL2;
  }
}



void Level::Update( float ElapsedTime )
{
  float timeToUse = ElapsedTime;

  FixedTimeStepPos += ElapsedTime;

  while ( timeToUse > 0.0f )
  {
    if ( FixedTimeStepPos >= 0.02f )
    {
      UpdateUnits( FixedTimeStepPos - 0.02f );
      UpdateFixedTimeStep();

      timeToUse -= FixedTimeStepPos - 0.02f;
      FixedTimeStepPos -= 0.02f;
    }
    else
    {
      UpdateUnits( timeToUse );
      timeToUse = 0.0f;
    }
  }

  std::list<Unit*>::iterator    itUL( DecoUnits.begin() );
  while ( itUL != DecoUnits.end() )
  {
    Unit*   pUnit( *itUL );

    if ( pUnit->RemoveMe )
    {
      if ( pUnit == pPlayer )
      {
        pPlayer = NULL;
      }

      itUL = DecoUnits.erase( itUL );
      delete pUnit;
    }
    else
    {
      pUnit->Update( *this, ElapsedTime );
      ++itUL;
    }
  }
}



void Level::UpdateUnits( float ElapsedTime )
{
  std::list<Unit*>::iterator    itUL( Units.begin() );
  while ( itUL != Units.end() )
  {
    Unit*   pUnit( *itUL );

    if ( pUnit->RemoveMe )
    {
      if ( pUnit == pPlayer )
      {
        pPlayer = NULL;
      }
      itUL = Units.erase( itUL );
      delete pUnit;
    }
    else
    {
      pUnit->Update( *this, ElapsedTime );
      ++itUL;
    }
  }
}



void Level::UpdateFixedTimeStep()
{
  CheckCollisions();
  std::list<Unit*>::iterator    itU( Units.begin() );
  while ( itU != Units.end() )
  {
    Unit*   pUnit( *itU );

    if ( pUnit->RemoveMe )
    {
      if ( pUnit == pPlayer )
      {
        pPlayer = NULL;
      }

      itU = Units.erase( itU );
      delete pUnit;
    }
    else
    {
      pUnit->UpdateFixedTimeStep( *this );
      ++itU;
    }
  }

  std::list<Unit*>::iterator    itUL( DecoUnits.begin() );
  while ( itUL != DecoUnits.end() )
  {
    Unit*   pUnit( *itUL );

    if ( pUnit->RemoveMe )
    {
      if ( pUnit == pPlayer )
      {
        pPlayer = NULL;
      }

      itUL = DecoUnits.erase( itUL );
      delete pUnit;
    }
    else
    {
      pUnit->UpdateFixedTimeStep( *this );
      ++itUL;
    }
  }
}



void Level::AddDecoUnit( Unit* pUnit )
{
  DecoUnits.push_back( pUnit );
}



void Level::AddDecoUnit( Unit* pUnit, const LD::Point& Pos )
{
  pUnit->Position = Pos;

  DecoUnits.push_back( pUnit );
}



void Level::AddUnit( Unit* pUnit, int X, int Y )
{
  pUnit->Position.set( X, Y );

  Units.push_back( pUnit );
}



void Level::RemoveUnit( Unit* pRemoveUnit )
{
  std::list<Unit*>::iterator    itUL( Units.begin() );
  while ( itUL != Units.end() )
  {
    Unit*   pUnit( *itUL );

    if ( pUnit == pRemoveUnit )
    {
      Units.erase( itUL );
      delete pRemoveUnit;
      return;
    }
    ++itUL;
  }
}



bool PolygonsIntersect( const std::vector<LD::PointF>& Points1, const LD::PointF& Center1,
                        const std::vector<LD::PointF>& Points2, const LD::PointF& Center2 )
{

  LD::PointF axis;

  float       tmp, 
              minA, 
              maxA, 
              minB, 
              maxB;

	// test polygon A's sides
  for ( size_t side = 0; side < Points1.size(); ++side )
	{
		// get the axis that we will project onto
		if ( side == 0 )
		{
      axis.x = Points1[Points1.size() - 1].y - Points1[0].y;
			axis.y = Points1[0].x - Points1[Points1.size() - 1].x;
		}
		else
		{
			axis.x = Points1[side - 1].y - Points1[side].y;
			axis.y = Points1[side].x - Points1[side - 1].x;
		}

		// normalize the axis
		tmp = sqrt( axis.x * axis.x + axis.y * axis.y );
		axis.x /= tmp;
		axis.y /= tmp;

		// project polygon A onto axis to determine the min/max
		minA = maxA = Points1[0].x * axis.x + Points1[0].y * axis.y;
		for ( size_t i = 1; i < Points1.size(); i++ )
		{
			tmp = Points1[i].x * axis.x + Points1[i].y * axis.y;
			if ( tmp > maxA )
      {
				maxA = tmp;
      }
			else if ( tmp < minA )
      {
				minA = tmp;
      }
		}
		// correct for offset
		tmp = Center1.x * axis.x + Center1.y * axis.y;
		minA += tmp;
		maxA += tmp;

		// project polygon B onto axis to determine the min/max
		minB = maxB = Points2[0].x * axis.x + Points2[0].y * axis.y;
		for ( size_t i = 1; i < Points2.size(); i++ )
		{
			tmp = Points2[i].x * axis.x + Points2[i].y * axis.y;
			if ( tmp > maxB )
      {
				maxB = tmp;
      }
			else if ( tmp < minB )
      {
				minB = tmp;
      }
		}
		// correct for offset
		tmp = Center2.x * axis.x + Center2.y * axis.y;
		minB += tmp;
		maxB += tmp;

		// test if lines intersect, if not, return false
		if ( ( maxA < minB )
    ||   ( minA > maxB ) )
    {
			return false;
    }
	}

	// test polygon B's sides
	for ( size_t side = 0; side < Points2.size(); side++ )
	{
		// get the axis that we will project onto
		if ( side == 0 )
		{
			axis.x = Points2[Points2.size() - 1].y - Points2[0].y;
			axis.y = Points2[0].x - Points2[Points2.size() - 1].x;
		}
		else
		{
			axis.x = Points2[side - 1].y - Points2[side].y;
			axis.y = Points2[side].x - Points2[side - 1].x;
		}

		// normalize the axis
		tmp = sqrt( axis.x * axis.x + axis.y * axis.y );
		axis.x /= tmp;
		axis.y /= tmp;

		// project polygon A onto axis to determine the min/max
		minA = maxA = Points1[0].x * axis.x + Points1[0].y * axis.y;
		for ( size_t i = 1; i < Points1.size(); i++ )
		{
			tmp = Points1[i].x * axis.x + Points1[i].y * axis.y;
			if ( tmp > maxA )
      {
				maxA = tmp;
      }
			else if ( tmp < minA )
      {
				minA = tmp;
      }
		}
		// correct for offset
		tmp = Center1.x * axis.x + Center1.y * axis.y;
		minA += tmp;
		maxA += tmp;

		// project polygon B onto axis to determine the min/max
		minB = maxB = Points2[0].x * axis.x + Points2[0].y * axis.y;
		for ( size_t i = 1; i < Points2.size(); i++ )
		{
			tmp = Points2[i].x * axis.x + Points2[i].y * axis.y;
			if ( tmp > maxB )
      {
				maxB = tmp;
      }
			else if ( tmp < minB )
      {
				minB = tmp;
      }
		}
		// correct for offset
		tmp = Center2.x * axis.x + Center2.y * axis.y;
		minB += tmp;
		maxB += tmp;

		// test if lines intersect, if not, return false
		if ( ( maxA < minB )
    ||   ( minA > maxB ) )
    {
			return false;
    }
	}
  return true;

}



bool PointInLineSegment2d( const LD::PointF& ptPoint,
                           const LD::PointF& ptSegmentA, const LD::PointF& ptSegmentB )
{

  // TODO - das kann ja nicht wirklich richtig sein??
  // nur richtig, wenn der Punkt bereits sicher auf der Linie liegt, prüft nur auf Segmentränder!!
  if ( ptSegmentA.x != ptSegmentB.x ) 
  {
    // S is not vertical
    if ( ( ptSegmentA.x <= ptPoint.x )
    &&   ( ptPoint.x <= ptSegmentB.x ) )
    {
      return true;
    }
    if ( ( ptSegmentA.x >= ptPoint.x )
    &&   ( ptPoint.x >= ptSegmentB.x ) )
    {
      return true;
    }
  }
  else 
  {
    // S is vertical, so test y coordinate
    if ( ( ptSegmentA.y <= ptPoint.y )
    &&   ( ptPoint.y <= ptSegmentB.y ) )
    {
      return true;
    }
    if ( ( ptSegmentA.y >= ptPoint.y )
    &&   ( ptPoint.y >= ptSegmentB.y ) )
    {
      return true;
    }
  }
  return false;
}



int IntersectLineSegmentWithLineSegment2d( const LD::PointF& ptSegment1A, const LD::PointF& ptSegment1B,
                                           const LD::PointF& ptSegment2A, const LD::PointF& ptSegment2B )
{

  LD::PointF   u = ptSegment1B - ptSegment1A;
  LD::PointF   v = ptSegment2B - ptSegment2A;
  LD::PointF   w = ptSegment1A - ptSegment2A;

  float         D = u.perpendicular( v );

  // test if they are parallel (includes either being a point)
  if ( fabs( D ) < 0.000001f ) 
  {          
    // S1 and S2 are parallel
    if ( ( u.perpendicular( w ) != 0 )
    ||   ( v.perpendicular( w ) != 0 ) ) 
    {
      // they are NOT collinear
      return 0;                   
    }

    // they are collinear or degenerate
    // check if they are degenerate points
    float du = u.dot( u );
    float dv = v.dot( v );

    if ( ( du == 0 )
    &&   ( dv == 0 ) ) 
    {
      // both segments are points

      if ( ptSegment1A != ptSegment2A )
      {
        // they are distinct points
        return 0;
      }
      // they are the same point
      return 1;
    }
    if ( du == 0 ) 
    {
      // S1 is a single point
      if ( PointInLineSegment2d( ptSegment1A, ptSegment2A, ptSegment2B ) == 0 )  
      {
        // but is not in S2
        return 0;
      }
      return 1;
    }
    if ( dv == 0 ) 
    {
      // S2 a single point
      if ( PointInLineSegment2d( ptSegment2A, ptSegment1A, ptSegment1B ) == 0 )
      {
        // but is not in S1
        return 0;
      }
      return 1;
    }

    // they are collinear segments - get overlap (or not)
    float t0, t1;                   // endpoints of S1 in eqn for S2

    LD::PointF w2 = ptSegment1B - ptSegment2A;

    if ( v.x != 0 ) 
    {
      t0 = w.x / v.x;
      t1 = w2.x / v.x;
    }
    else 
    {
      t0 = w.y / v.y;
      t1 = w2.y / v.y;
    }
    if ( t0 > t1 ) 
    {
      // must have t0 smaller than t1
      float t = t0; 
      t0 = t1; 
      t1 = t;    // swap if not
    }
    if ( ( t0 > 1 )
    ||   ( t1 < 0 ) ) 
    {
      return 0;     // NO overlap
    }
    t0 = t0 < 0 ? 0 : t0;              // clip to min 0
    t1 = t1 > 1 ? 1 : t1;              // clip to max 1
    if ( t0 == t1 ) 
    {                 
      // intersect is a point
      return 1;
    }

    // they overlap in a valid subsegment
    return 2;
  }

  // the segments are skew and may intersect in a point
  // get the intersect parameter for S1
  float     sI = v.perpendicular( w ) / D;

  if ( ( sI < 0 )
  ||   ( sI > 1 ) )
  {
    // no intersect with S1
    return 0;
  }

  // get the intersect parameter for S2
  float     tI = u.perpendicular( w ) / D;

  if ( ( tI < 0 )
  ||   ( tI > 1 ) )
  {
    // no intersect with S2
    return 0;
  }
  return 1;
}



void Level::CheckCollisions()
{
  std::list<Unit*>::iterator    itUL( Units.begin() );
  while ( itUL != Units.end() )
  {
    Unit*   pUnit( *itUL );

    if ( pUnit->RemoveMe )
    {
      ++itUL;
      continue;
    }

    if ( ( pUnit->Type == LD::GO_PLAYER_SHOT )
    ||   ( pUnit->Type == LD::GO_PLAYER ) )
    {
      std::list<Unit*>::iterator    itUL2( Units.begin() );
      while ( itUL2 != Units.end() )
      {
        Unit*   pUnit2( *itUL2 );

        if ( pUnit->RemoveMe )
        {
          break;
        }
        if ( ( pUnit2->RemoveMe )
        ||   ( pUnit2 == pUnit ) )
        {
          ++itUL2;
          continue;
        }
        if ( ( pUnit2->Type == LD::GO_EMERALD )
        ||   ( pUnit2->Type == LD::GO_UFO ) )
        {
          if ( ( pUnit->Position - pUnit2->Position ).length() < ( pUnit->Size + pUnit2->Size ) / 2 )
          {
            // collision!
            if ( pUnit->Type == LD::GO_PLAYER_SHOT )
            {
              if ( pUnit2->Type == LD::GO_UFO )
              {
                pUnit->RemoveMe = true;

                --pUnit2->HP;
                if ( pUnit2->HP <= 0 )
                {
                  pUnit2->RemoveMe = true;

                  Score += 250;

                  for ( int i = 0; i < 100; ++i )
                  {
                    Particle*  pPart = new Particle();
                    pPart->Position = pUnit2->Position;
                    pPart->Speed.set( 50.0f + rand() % 100, 0.0f );
                    pPart->Speed.RotateZ( (float)( rand() % 360 ) );
                    pPart->LifeTime = ( rand() % 100 ) * 0.01f + 0.75f;

                    AddDecoUnit( pPart );

                    theApp.PlaySoundA( LD::SOUND_EXPLODE );
                  }
                }
                else
                {
                  theApp.PlaySoundA( LD::SOUND_SHOT_HIT );

                  ++Score;

                  Particle*  pPart = new Particle();
                  pPart->Position = pUnit2->Position;
                  pPart->Speed.set( 50.0f + rand() % 100, 0.0f );
                  pPart->Speed.RotateZ( (float)( rand() % 360 ) );
                  pPart->LifeTime = ( rand() % 100 ) * 0.01f + 0.75f;

                  AddDecoUnit( pPart );
                }
                break;
              }
            }
            else if ( pUnit->Type == LD::GO_PLAYER )
            {
              if ( pUnit2->Type == LD::GO_UFO )
              {
                pUnit->RemoveMe = true;
                pUnit2->RemoveMe = true;

                for ( int i = 0; i < 100; ++i )
                {
                  Particle*  pPart = new Particle();
                  pPart->Position = pUnit2->Position;
                  pPart->Speed.set( 50.0f + rand() % 100, 0.0f );
                  pPart->Speed.RotateZ( (float)( rand() % 360 ) );
                  pPart->LifeTime = ( rand() % 100 ) * 0.01f + 0.75f;

                  AddDecoUnit( pPart );

                  theApp.PlaySoundA( LD::SOUND_EXPLODE );
                }
                break;
              }
              else if ( pUnit2->Type == LD::GO_EMERALD )
              {
                theApp.PlaySoundA( LD::SOUND_COLLECT_EMERALD );
                
                ++NumEmeralds;
                pUnit2->RemoveMe = true;
                Score += 500;
              }
            }
          }
        }

        ++itUL2;
      }
    }

    /*
    if ( pUnit->Type == LD::GO_OBSTACLE )
    {
    Obstacle*  pObst = (Obstacle*)pUnit;

    std::vector<LD::PointF>     obstPoly;

    LD::PointF    obstPos( (float)pObst->Position.x, (float)pObst->Position.y );

    obstPoly.push_back( LD::PointF( -pObst->Size.x * 0.5f, -pObst->Size.y * 0.5f ).RotateZ( pObst->Angle ) );
    obstPoly.push_back( LD::PointF( pObst->Size.x * 0.5f, -pObst->Size.y * 0.5f ).RotateZ( pObst->Angle ) );
    obstPoly.push_back( LD::PointF( pObst->Size.x * 0.5f, pObst->Size.y * 0.5f ).RotateZ( pObst->Angle ) );
    obstPoly.push_back( LD::PointF( -pObst->Size.x * 0.5f, pObst->Size.y * 0.5f ).RotateZ( pObst->Angle ) );


    if ( PolygonsIntersect( playerPoly, playerPos, obstPoly, obstPos ) )
    {
    return true;
    }

    }*/

    ++itUL;
  }
}



