#include ".\unit.h"
#include ".\Level.h"
#include ".\GetEmGood.h"
#include "PlayState.h"
#include "Player.h"
#include "PlayerShot.h"
#include "GreenMask.h"
#include "RedMask.h"
#include "Bomb.h"
#include "Explosion.h"
#include "Spawn.h"
#include "Caterpillar.h"
#include "Wild.h"

#include <math/Iterator/NullIterator.h>
#include <math/Iterator/SingleSineSwing.h>



Unit::Unit() :
  m_Type( Pac::UT_INVALID ),
  m_RemoveMe( false ),
  m_Jumping( false ),
  m_OnGround( false ),
  m_FaceLeft( false ),
  m_JumpSpeed( 0 ),
  m_FallSpeed( 0 ),
  m_FallSpeedDelay( 0 ),
  m_ExtraData( 0 ),
  m_ExtraData2( 0 ),
  m_Floating( true ),
  m_LifeTime( 0.0f ),
  m_pOnUnit( NULL ),
  m_IsPlatform( false ),
  m_IsNasty( false ),
  m_NoAutoAdjustFacing( false ),
  m_HP( 1 ),
  m_HitDisplayTime( 0.0f ),
  m_DisplayHit( false ),
  m_CoordinatesWrapAround( true ),
  m_TileOffset( 0 ),
  m_MoveDir( Pac::DIR_NONE ),
  m_CollisionBounds( 0, 0, 16, 16 ),
  m_PlayerIndex( -1 ),
  m_DisplayBlinking( false ),
  m_DisplayBlinkingFast( false ),
  m_HasKey( false ),
  m_Invincibility( 0 ),
  m_MoveSpeed( 4 ),
  m_MoveSpeedFract( 0 )
{
  m_DisplayBlinkingChain.AddIterator( new math::Iterator::NullIterator( 1.0f, 1.0f ) );
  m_DisplayBlinkingChain.AddIterator( new math::Iterator::SingleSineSwing( -0.5f, 0.25f, 1.0f ) );
  m_DisplayBlinkingChainFast.AddIterator( new math::Iterator::SingleSineSwing( -0.5f, 0.25f, 1.0f ) );
}



Unit::~Unit()
{
}



void Unit::Display( const GR::tPoint& ptOffset, XRenderer& Renderer )
{
  if ( m_DisplayHit )
  {
    Renderer.SetShader( XRenderer::ST_ALPHA_TEST_COLOR_FROM_DIFFUSE );
  }
  else
  {
    Renderer.SetShader( XRenderer::ST_ALPHA_TEST );
  }

  GR::u32     color = 0xffffffff;
  if ( m_DisplayBlinkingFast )
  {
    GR::u8    byteValue = ( GR::u8 )( 0xff * m_DisplayBlinkingChainFast.CurrentValue( m_DisplayBlinkingChainFastPos ) );
    color = 0xff000000 | ( byteValue << 16 ) | ( byteValue << 8 ) | byteValue;
  }
  else if ( m_DisplayBlinking )
  {
    GR::u8    byteValue = ( GR::u8 )( 0xff * m_DisplayBlinkingChain.CurrentValue( m_DisplayBlinkingChainPos ) );
    color = 0xff000000 | ( byteValue << 16 ) | ( byteValue << 8 ) | byteValue;
  }

  if ( m_Animation.empty() )
  {
    XTextureSection     tsImage( theApp.Section( m_Image ) );
    Renderer.RenderTextureSection2d( m_Position.x - ptOffset.x + m_DisplayOffset.x, m_Position.y - ptOffset.y + m_DisplayOffset.y, tsImage, color );
  }
  else
  {
    Renderer.RenderTextureSection2d( m_Position.x - ptOffset.x + m_DisplayOffset.x, m_Position.y - ptOffset.y + m_DisplayOffset.y, theApp.AnimationFrame( m_AnimPos ), color );
  }
}



void Unit::UpdateFixed( PlayState& State )
{
  if ( m_Invincibility > 0 )
  {
    --m_Invincibility;
    if ( m_Invincibility == 0 )
    {
      m_DisplayBlinkingFast = false;
    }
  }

  if ( m_Jumping )
  {
    m_JumpSpeed -= 1;
    if ( m_JumpSpeed <= 0 )
    {
      m_Jumping = false;
      m_Speed.y = 0;

      ProcessEvent( State, ET_FALL_START );
    }
    else
    {
      m_Speed.y = -m_JumpSpeed;
    }
  }
  if ( ( !m_Jumping )
  &&   ( !m_Floating ) )
  {
    if ( m_OnGround )
    {
      m_FallSpeed = 0;
    }
    ++m_FallSpeedDelay;
    if ( m_FallSpeedDelay >= 2 )
    {
      m_FallSpeedDelay = 0;
      m_FallSpeed += 1;
    }
    m_Speed.y = m_FallSpeed;
  }
  Move( State, m_Speed );

  if ( m_CoordinatesWrapAround )
  {
    if ( m_Position.y > 620 )
    {
      m_Position.y = -40;
    }
    if ( m_Position.y < -40 )
    {
      m_Position.y = 620;
    }
  }
}



void Unit::SetHit()
{
  m_HitDisplayTime  = 0.15f;
  m_DisplayHit      = true;
}



void Unit::Update( PlayState& State, const GR::f32 ElapsedTime )
{
  if ( m_DisplayBlinking )
  {
    m_DisplayBlinkingChain.UpdatePos( m_DisplayBlinkingChainPos, ElapsedTime, true );
  }
  if ( m_DisplayBlinkingFast )
  {
    m_DisplayBlinkingChainFast.UpdatePos( m_DisplayBlinkingChainFastPos, ElapsedTime, true );
  }
  if ( m_HitDisplayTime > 0.0f )
  {
    m_HitDisplayTime -= ElapsedTime;
    if ( m_HitDisplayTime <= 0.0f )
    {
      m_DisplayHit = false;
    }
  }
  if ( m_LifeTime > 0.0f )
  {
    m_LifeTime -= ElapsedTime;
    if ( m_LifeTime <= 0.0f )
    {
      m_RemoveMe = true;
      return;
    }
  }
  if ( !m_Animation.empty() )
  {
    theApp.m_AnimationManager.AdvanceAnimFrame( m_AnimPos, ElapsedTime * 1000.0f );
  }
}



GR::tRect Unit::MovementBounds()
{
  GR::tRect    rcBounds( m_MovementBounds );

  rcBounds.offset( m_Position );

  return rcBounds;
}



GR::tRect Unit::CollisionBounds()
{
  GR::tRect    rcBounds( m_CollisionBounds );

  rcBounds.offset( m_Position );

  return rcBounds;
}



void Unit::SetImage( const std::string& Image )
{
  m_Image = Image;
  m_Animation.clear();
}



void Unit::SetAnimation( const std::string& Animation )
{
  m_Image.clear();
  m_Animation = Animation;

  tAnimationPos   Anim = theApp.AnimationPos( Animation );

  if ( Anim.m_dwAnimationId != m_AnimPos.m_dwAnimationId )
  {
    m_AnimPos = Anim;
  }
}



void Unit::ProcessEvent( PlayState& State, const eUnitEvent& Event )
{
  switch ( Event )
  {
    case ET_BLOCKED_UP:
      m_Jumping = false;
      break;
    case ET_FALL_START:
      m_FallSpeed = 0;
      break;
    case ET_FALL_END:
      m_FallSpeed = 0;
      break;
    case ET_ENTER_PLATFORM:
      break;
    case ET_LEAVE_PLATFORM:
      break;
    case ET_DIE:
      {
        if ( m_pOnUnit )
        {
          m_pOnUnit->m_CarriedUnits.remove( this );
          m_pOnUnit = NULL;
        }
        std::list<Unit*>::iterator   it( m_CarriedUnits.begin() );
        while ( it != m_CarriedUnits.end() )
        {
          Unit*    pUnit( *it );

          pUnit->m_pOnUnit = NULL;

          ++it;
        }
        m_CarriedUnits.clear();
      }
      break;
  }
}



bool Unit::Jump( int Power )
{
  if ( m_OnGround )
  {
    if ( !m_Jumping )
    {
      m_Jumping = true;
      m_JumpSpeed = Power;
      m_Speed.y = -m_JumpSpeed;
      m_OnGround = false;
      return true;
    }
  }
  return false;
}



Unit* Unit::FromType( const Pac::eUnitTypes UnitType )
{
  switch ( UnitType )
  {
    case Pac::UT_PLAYER:
      return new Player();
    case Pac::UT_GREEN_MASK:
      return new GreenMask();
    case Pac::UT_RED_MASK:
      return new RedMask();
    case Pac::UT_BOMB:
      return new Bomb();
    case Pac::UT_PLAYER_SHOT:
      return new PlayerShot();
    case Pac::UT_EXPLOSION:
      return new Explosion();
    case Pac::UT_SPAWN:
      return new Spawn();
    case Pac::UT_CATERPILLAR:
      return new Caterpillar();
    case Pac::UT_WILD:
      return new Wild();
  }
  dh::Error( "Unit::FromType Unknown Type %d", UnitType );

  return NULL;
}



bool Unit::Move( PlayState& State, const GR::tPoint& DeltaArg, bool bMovedByPlatform )
{
  bool            bBlocked = false;
  GR::tPoint      ptDelta( DeltaArg );

  while ( ( ptDelta.x != 0 )
  ||      ( ptDelta.y != 0 ) )
  {
    if ( ptDelta.x > 0 )
    {
      if ( !m_NoAutoAdjustFacing )
      {
        m_FaceLeft = false;
      }
      if ( State.Level.CanMove( this, m_Position, Pac::DIR_E ) )
      {
        m_Position.x++;
        ptDelta.x -= 1;

        ProcessEvent( State, ET_MOVED_RIGHT );

        if ( m_MoveDir != Pac::DIR_E )
        {
          ptDelta.clear();
        }
      }
      else
      {
        // blockiert
        ProcessEvent( State, ET_BLOCKED_RIGHT );
        if ( m_Speed.y == 0 )
        {
          ptDelta.y = 0;
        }
        ptDelta.x = 0;
        bBlocked = true;
      }
    }
    else if ( ptDelta.x < 0 )
    {
      if ( !m_NoAutoAdjustFacing )
      {
        m_FaceLeft = true;
      }
      if ( State.Level.CanMove( this, m_Position, Pac::DIR_W ) )
      {
        m_Position.x--;
        ++ptDelta.x;

        ProcessEvent( State, ET_MOVED_LEFT );
        if ( m_MoveDir != Pac::DIR_W )
        {
          ptDelta.clear();
        }
      }
      else
      {
        // blockiert
        ProcessEvent( State, ET_BLOCKED_LEFT );
        if ( m_Speed.y == 0 )
        {
          ptDelta.y = 0;
        }
        ptDelta.x = 0;
        bBlocked = true;
      }
    }

    // auf/ab
    if ( ptDelta.y > 0 )
    {
      if ( State.Level.CanMove( this, m_Position, Pac::DIR_S ) )
      {
        m_Position.y++;
        --ptDelta.y;

        ProcessEvent( State, ET_MOVED_DOWN );

        if ( m_MoveDir != Pac::DIR_S )
        {
          ptDelta.clear();
        }
        if ( m_OnGround )
        {
          ProcessEvent( State, ET_FALL_START );
          m_OnGround = false;
        }
      }
      else
      {
        // blockiert
        ProcessEvent( State, ET_BLOCKED_DOWN );
        if ( m_Speed.x == 0 )
        {
          ptDelta.x = 0;
        }
        ptDelta.y = 0;
        bBlocked = true;

        if ( !m_OnGround )
        {
          ProcessEvent( State, ET_FALL_END );
          m_OnGround = true;
        }
      }
    }
    else if ( ptDelta.y < 0 )
    {
      if ( State.Level.CanMove( this, m_Position, Pac::DIR_N ) )
      {
        m_Position.y--;
        ++ptDelta.y;

        ProcessEvent( State, ET_MOVED_UP );
        if ( m_MoveDir != Pac::DIR_N )
        {
          ptDelta.clear();
        }
      }
      else
      {
        // blockiert
        ProcessEvent( State, ET_BLOCKED_UP );
        if ( m_Speed.x == 0 )
        {
          ptDelta.x = 0;
        }

        ptDelta.y = 0;
        bBlocked = true;
      }
    }
  }
  return !bBlocked;
}



void Unit::MoveUnblocked( PlayState& State, int DX, int DY )
{
  m_Position.offset( DX, DY );

  if ( DX > 0 )
  {
    ProcessEvent( State, ET_MOVED_RIGHT );
  }
  if ( DX < 0 )
  {
    ProcessEvent( State, ET_MOVED_LEFT );
  }
  if ( DY > 0 )
  {
    ProcessEvent( State, ET_MOVED_DOWN );
  }
  if ( DY > 0 )
  {
    ProcessEvent( State, ET_MOVED_UP );
  }
}



void Unit::MoveCarriedUnits( PlayState& State, int iDX, int iDY )
{
  GR::tPoint    ptDir( iDX, iDY );

  Pac::eDir Dir = Pac::DIR_NONE;

  if ( iDX == 1 )
  {
    Dir = Pac::DIR_E;
  }
  else if ( iDX == -1 )
  {
    Dir = Pac::DIR_W;
  }
  else if ( iDY == -1 )
  {
    Dir = Pac::DIR_N;
  }
  else if ( iDY == 1 )
  {
    Dir = Pac::DIR_S;
  }

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

    pUnit->Move( State, ptDir, true );

    ++it;
  }
}



void Unit::OnSpawned( PlayState& State )
{
  m_StartPosition = m_Position;
}