#include ".\unit.h"
#include ".\Level.h"
#include ".\BirdiesRevenge.h"

#include ".\Birdie.h"
#include ".\Aufzieh.h"
#include ".\Note.h"
#include ".\Bubble.h"
#include ".\Flash.h"
#include ".\FlashBubble.h"
#include ".\Pop.h"
#include ".\Bonus.h"
#include ".\Points.h"
#include ".\Bird.h"
#include ".\Bat.h"
#include ".\Thing.h"
#include ".\Collectible.h"
#include ".\Pumpkin.h"
#include ".\BossShot.h"
#include "PumpkinPiece.h"



Unit::Unit() :  
  m_Type( Birdie2::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( false ),
  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 )
{
}



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 );
  }
  if ( m_Animation.empty() )
  {
    XTextureSection     tsImage( theApp.Section( m_Image ) );
    if ( m_FaceLeft )
    {
      tsImage.m_Flags ^= XTextureSection::TSF_H_MIRROR;
    }
    Renderer.RenderTextureSection2d( m_Position.x - ptOffset.x, m_Position.y - ptOffset.y, tsImage );
  }
  else
  {
    XTextureSection     tsImage( theApp.AnimationFrame( m_AnimPos ) );
    if ( m_FaceLeft )
    {
      tsImage.m_Flags ^= XTextureSection::TSF_H_MIRROR;
    }
    Renderer.RenderTextureSection2d( m_Position.x - ptOffset.x, m_Position.y - ptOffset.y, tsImage );
  }
}



void Unit::UpdateFixed( Level& Level )
{
  if ( m_Jumping )
  {
    m_JumpSpeed -= 1;
    if ( m_JumpSpeed <= 0 )
    {
      m_Jumping = false;
      m_Speed.y = 0;

      ProcessEvent( Level, 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( Level, 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::Update( Level& Level, const GR::f32 ElapsedTime )
{
  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( Level& Level, 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 Birdie2::eUnitTypes UnitType )
{
  switch ( UnitType )
  {
    case Birdie2::UT_BIRDIE:
      return new Birdie();
    case Birdie2::UT_AUFZIEH:
      return new Aufzieh();
    case Birdie2::UT_NOTE:
      return new Note();
    case Birdie2::UT_BUBBLE:
      return new Bubble();
    case Birdie2::UT_POP:
      return new Pop();
    case Birdie2::UT_BONUS:
      return new Bonus();
    case Birdie2::UT_BIRD:
      return new Bird();
    case Birdie2::UT_BAT:
      return new Bat();
    case Birdie2::UT_COLLECTIBLE:
      return new Collectible();
    case Birdie2::UT_POINTS:
      return new Points();
    case Birdie2::UT_THING:
      return new Thing();
    case Birdie2::UT_FLASH_BUBBLE:
      return new FlashBubble();
    case Birdie2::UT_FLASH:
      return new Flash();
    case Birdie2::UT_PUMPKIN:
      return new Pumpkin();
    case Birdie2::UT_BOSS_SHOT:
      return new BossShot();
    case Birdie2::UT_PUMPKIN_PIECE:
      return new PumpkinPiece();
  }
  dh::Error( "Unit::FromType Unknown Type %d", UnitType );

  return NULL;
}



bool Unit::Move( Level& Level, const GR::tPoint& DeltaArg, bool bMovedByPlatform )
{
  bool            bBlocked = false;
  bool            checkedForGravity = bMovedByPlatform;

  GR::tPoint      ptDelta( DeltaArg );

  while ( ( ptDelta.x != 0 )
  ||      ( ptDelta.y != 0 ) )
  {
    if ( !checkedForGravity )
    {
      int     iGravityY = 1;

      if ( ( !Level.CanMove( this, m_Position, Birdie2::DIR_S ) )
      ||   ( ( !bMovedByPlatform )
      &&     ( Level.OnTopOfPlatform( this ) ) ) )
      {
        if ( ( !m_Jumping )
        &&   ( !m_Floating ) )
        {
          if ( !m_OnGround )
          {
            bBlocked = true;
            m_OnGround = true;
            ProcessEvent( Level, ET_FALL_END );
            ptDelta.y = 0;
          }
        }
      }
      else
      {
        if ( ( m_OnGround )
        &&   ( m_pOnUnit == NULL )
        &&   ( !m_Jumping )
        &&   ( !m_Floating ) )
        {
          ProcessEvent( Level, ET_FALL_START );
          m_OnGround = false;
          checkedForGravity = true;
          ++m_Position.y;
        }
      }
    }

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

        ProcessEvent( Level, ET_MOVED_RIGHT );
      }
      else
      {
        // blockiert
        ProcessEvent( Level, 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 ( Level.CanMove( this, m_Position, Birdie2::DIR_W ) )
      {
        m_Position.x--;
        ++ptDelta.x;

        ProcessEvent( Level, ET_MOVED_LEFT );
      }
      else
      {
        // blockiert
        ProcessEvent( Level, ET_BLOCKED_LEFT );
        if ( m_Speed.y == 0 )
        {
          ptDelta.y = 0;
        }
        ptDelta.x = 0;
        bBlocked = true;
      }
    }

    // auf/ab
    if ( ptDelta.y > 0 )
    {
      if ( ( Level.CanMove( this, m_Position, Birdie2::DIR_S ) )
      &&   ( !Level.OnTopOfPlatform( this ) ) )
      {
        m_Position.y++;
        --ptDelta.y;

        ProcessEvent( Level, ET_MOVED_DOWN );

        if ( m_OnGround )
        {
          ProcessEvent( Level, ET_FALL_START );
          m_OnGround = false;
        }
      }
      else
      {
        // blockiert
        ProcessEvent( Level, ET_BLOCKED_DOWN );
        if ( m_Speed.x == 0 )
        {
          ptDelta.x = 0;
        }
        ptDelta.y = 0;
        bBlocked = true;

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

        ProcessEvent( Level, ET_MOVED_UP );
      }
      else
      {
        // blockiert
        ProcessEvent( Level, ET_BLOCKED_UP );
        if ( m_Speed.x == 0 )
        {
          ptDelta.x = 0;
        }

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



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

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



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

  Birdie2::eDir Dir = Birdie2::DIR_NONE;

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

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

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

    ++it;
  }
}



void Unit::OnLoad( Level& Level )
{
  m_StartPosition = m_Position;
}