#include "FallingMenu.h"
#include "GetEmGood.h"
#include "PacTypes.h"

#include <Xtreme/XBasicFont.h>

#include <math/MathUtil.h>



FallingMenu::FallingMenu() :
  m_CenterPos( 400, 300 ),
  m_CurrentItem( 0 ),
  m_MouseOverItem( -1 ),
  m_ItemDelta( 0 ),
  m_ItemHeight( 40 ),
  m_Done( false ),
  m_MouseButtonReleased( false ),
  m_Angle( 0.0f ),
  m_ZPos( 0.0f ),
  m_Alpha( 255.0f )
{
  theApp.InputClass()->ReleasedVKeyPressed( theApp.GetVarI( "Player.1.SpeedUp" ) );
  theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_ENTER );
}



void FallingMenu::DisplayBackground( XRenderer& Renderer, 
                                     int X,
                                     int Y,
                                     int Width,
                                     int Height )
{
  Renderer.SetShader( XRenderer::ST_FLAT );

  int     deltaMissingX = 0;
  int     deltaMissingY = 0;

  Height += 20;
  if ( Width % 20 )
  {
    deltaMissingX = 20 - ( Width % 20 );
  }
  if ( Height % 20 )
  {
    deltaMissingY = 20 - ( Height % 20 );
  }
  X -= deltaMissingX / 2;
  Y -= deltaMissingY / 2;

  Width += deltaMissingX;
  Height += deltaMissingY;

  /*
  RenderTextureSection( Renderer, X, Y, theApp.Section( "Menu.Border.TL" ) );
  RenderTextureSection( Renderer, X + 20 + ( Width / 20 - 2 ) * 20, Y, theApp.Section( "Menu.Border.TR" ) );
  RenderTextureSection( Renderer, X, Y + 20 + ( Height / 20 - 2 ) * 20, theApp.Section( "Menu.Border.BL" ) );
  RenderTextureSection( Renderer, X + 20 + ( Width / 20 - 2 ) * 20, Y + 20 + ( Height / 20 - 2 ) * 20, theApp.Section( "Menu.Border.BR" ) );
  for ( int i = 0; i < Width / 20 - 2; ++i )
  {
    RenderTextureSection( Renderer, X + 20 + i * 20, Y, theApp.Section( "Menu.Border.T" ) );
    RenderTextureSection( Renderer, X + 20 + i * 20, Y + 20 + ( Height / 20 - 2 ) * 20, theApp.Section( "Menu.Border.B" ) );
  }
  for ( int i = 0; i < Height / 20 - 2; ++i )
  {
    RenderTextureSection( Renderer, X, Y + 20 + i * 20, theApp.Section( "Menu.Border.L" ) );
    RenderTextureSection( Renderer, X + 20 + ( Width / 20 - 2 ) * 20, Y + 20 + i * 20, theApp.Section( "Menu.Border.R" ) );
  }
  for ( int i = 0; i < Width / 20 - 2; ++i )
  {
    for ( int j = 0; j < Height / 20 - 2; ++j )
    {
      RenderTextureSection( Renderer, X + 20 + i * 20, Y + 20 + j * 20, theApp.Section( "Menu.Border.Center" ) );
    }
  }*/
  //RenderTextureSection( Renderer, X + 20, Y + 20, theApp.Section( "Menu.Border.Center" ), -1, Width - 40, Height - 40 );
}



void FallingMenu::RenderTextureSection( XRenderer& Renderer, int X, int Y, XTextureSection& Section, GR::u32 Color, int AlternateW, int AlternateH )
{
  if ( AlternateW == -1 )
  {
    AlternateW = Section.m_Width;
  }
  if ( AlternateH == -1 )
  {
    AlternateH = Section.m_Height;
  }

  GR::tFPoint   deltaTexelMapping = Renderer.DirectTexelMappingOffset();

  GR::tVector   v1( ( GR::f32 )X + deltaTexelMapping.x, ( GR::f32 )Y + deltaTexelMapping.y, m_ZPos );
  GR::tVector   v2( v1.x + AlternateW, v1.y, m_ZPos );
  GR::tVector   v3( v1.x,              v1.y + AlternateH, m_ZPos );
  GR::tVector   v4( v1.x + AlternateW, v1.y + AlternateH, m_ZPos );

  GR::u32     color = CGFXHelper::AlphaFade( Color, Color & 0xffffff, (int)m_Alpha );

  Renderer.RenderTextureSection( v1, v2, v3, v4, Section, color );
}



void FallingMenu::Display( XRenderer& Renderer, int CenterX, int CenterY )
{
  if ( m_Entries.empty() )
  {
    return;
  }
  m_CenterPos.set( CenterX, CenterY );

  Renderer.SetState( XRenderer::RS_ZBUFFER, XRenderer::RSV_DISABLE );

  XCamera           Camera;

  math::matrix4     MatProj;

  MatProj.OrthoOffCenterLH( 0.0f, 800.0f, 0.0f, 600.0f, 0.0f, 100.0f );
  Camera.SetValues( GR::tVector( 0.0f,  600.0f, 10.0f ),
                    GR::tVector( 0.0f,  600.0f, 0.0f ),
                    GR::tVector( 0.0f, -5.0f, 0.0f ) );

  Renderer.SetTransform( XRenderer::TT_VIEW,        Camera.GetViewMatrix() );
  Renderer.SetTransform( XRenderer::TT_PROJECTION,  MatProj );

  math::matrix4     matRot;

  matRot.RotationZ( m_Angle );

  GR::tFPoint deltaTexelMapping = Renderer.DirectTexelMappingOffset(); // 0.0f;   // 0.5f für pre-DX11

  matRot = math::matrix4().Translation( (GR::f32)-m_CenterPos.x, (GR::f32)-m_CenterPos.y, -m_ZPos )
           * matRot
           * math::matrix4().Translation( (GR::f32)m_CenterPos.x, (GR::f32)m_CenterPos.y, m_ZPos );
  Renderer.SetTransform( XRenderer::TT_WORLD, matRot );

  int         ItemIndex = 0;
  int         displayHeight = DisplayHeight();
  int         Y = (int)m_CenterPos.y - displayHeight / 2 + 50 / 2;

  int         displayWidth = DisplayWidth();
  int         columnWidth = MaxTextWidth();

  DisplayBackground( Renderer, 
                     ( Renderer.Width() - displayWidth ) / 2 + (int)m_CenterPos.x - Renderer.Width() / 2, 
                     ( Renderer.Height() - displayHeight ) / 2 + (int)m_CenterPos.y - Renderer.Height() / 2, 
                     displayWidth,
                     displayHeight );

  Renderer.SetShader( XRenderer::ST_ALPHA_BLEND_AND_TEST );
  const GR::tVector   fontScale( 2.0f, 2.0f, 0.0f );

  Renderer.SetState( XRenderer::RS_MINFILTER, XRenderer::RSV_FILTER_POINT );
  Renderer.SetState( XRenderer::RS_MAGFILTER, XRenderer::RSV_FILTER_POINT );
  Renderer.SetState( XRenderer::RS_MIPFILTER, XRenderer::RSV_FILTER_POINT );

  int     menuIndex = 0;
  tMenu::iterator   itE( m_Entries.begin() );
  while ( itE != m_Entries.end() )
  {
    GR::u32   color = ( ( (int)( itE->ActiveState * m_Alpha ) ) << 24 ) | 0x00ffffff;
    //GR::u32   color = ( menuIndex == m_CurrentItem ) ? 0xffffffff : 0xffB4BDC5;
    int       dy = ( m_ItemHeight - 16 ) / 2;

    if ( itE->SubText.empty() )
    {
      int       textWidth = itE->Text.length() * 16;

      theApp.RenderText( Renderer, ( Renderer.Width() - textWidth ) / 2, Y + dy, itE->Text, color );
      //Renderer.RenderText2d( pFont, ( Renderer.Width() - textWidth ) / 2, Y + dy, itE->Text.c_str(), 2.0f, 2.0f, color );
      //Renderer.RenderText( pFont, GR::tVector( ( Renderer.Width() - textWidth ) / 2 + deltaTexelMapping, ( GR::f32 )Y + dy + deltaTexelMapping, m_ZPos ), itE->Text.c_str(), fontScale, color );
    }
    else
    {
      // two columns
      int       textWidth = ( itE->Text.length() + itE->SubText.length() + 1 ) * 16;
      int       offsetX = 30;
      if ( itE->LeftRightActive )
      {
        //offsetX += 40;
      }
      textWidth += offsetX;

      theApp.RenderText( Renderer, ( Renderer.Width() - textWidth ) / 2, Y + dy, itE->Text, color );
      theApp.RenderText( Renderer, ( Renderer.Width() - textWidth ) / 2 + offsetX + itE->SubText.length() * 16, Y + dy, itE->SubText, color );
    }

    if ( itE->LeftRightActive )
    {
      //RenderTextureSection( Renderer, ( Renderer.Width() - displayWidth ) / 2 + 30, Y + dy + 3, theApp.Section( "GUI.Cursor.Arrow.Left" ), color );
      //RenderTextureSection( Renderer, ( Renderer.Width() + displayWidth ) / 2 - 40, Y + dy + 3, theApp.Section( "GUI.Cursor.Arrow.Right" ), color );
    }

    Y += m_ItemHeight;
    ++itE;
    ++menuIndex;
  }
  Renderer.SetTransform( XRenderer::TT_WORLD, math::matrix4().Identity() );
}



void FallingMenu::Update( const GR::f32 ElapsedTime )
{
  size_t    menuIndex = 0;
  tMenu::iterator   it( m_Entries.begin() );
  while ( it != m_Entries.end() )
  {
    if ( ( menuIndex == m_CurrentItem )
    ||   ( menuIndex == m_MouseOverItem ) )
    {
      it->ActiveState = math::clamp( it->ActiveState + 3.0f * ElapsedTime, 0.5f, 1.0f );
    }
    else
    {
      it->ActiveState = math::clamp( it->ActiveState - 3.0f * ElapsedTime, 0.5f, 1.0f );
    }
    ++menuIndex;
    ++it;
  }

  // mouse control
  GR::u32     buttons = theApp.InputClass()->MouseButton();
  int         mouseX = theApp.InputClass()->MouseX();
  int         mouseY = theApp.InputClass()->MouseY();


  XRenderer&    Renderer( *theApp.Renderer() );

  XCamera           Camera;

  math::matrix4     MatProj;

  MatProj.OrthoOffCenterLH( 0.0f, 800.0f, 0.0f, 600.0f, 0.0f, 100.0f );
  Camera.SetValues( GR::tVector( 0.0f,  600.0f, 10.0f ),
                    GR::tVector( 0.0f,  600.0f, 0.0f ),
                    GR::tVector( 0.0f, -5.0f, 0.0f ) );

  math::matrix4     matRot;

  matRot.RotationZ( m_Angle );
  matRot = math::matrix4().Translation( (GR::f32)-m_CenterPos.x, (GR::f32)-m_CenterPos.y, 0.0f )
           * matRot
           * math::matrix4().Translation( (GR::f32)m_CenterPos.x, (GR::f32)m_CenterPos.y, 0.0f );

  GR::tVector   mousePos( (GR::f32)mouseX, (GR::f32)mouseY, 0.0f );
  Renderer.Unproject( mousePos, Renderer.Viewport(), MatProj, Camera.GetViewMatrix(), matRot );

  mouseX = (int)mousePos.x;
  mouseY = (int)mousePos.y;


  int           Y = (int)m_CenterPos.y - DisplayHeight() / 2 + 50 / 2;
  int           entryIndex = 0;
  int           mouseOverItem = -1;
  int           displayWidth = DisplayWidth();

  tMenu::iterator   itE( m_Entries.begin() );
  while ( itE != m_Entries.end() )
  {
    int       dy = ( m_ItemHeight - 16 ) / 2;
    if ( ( itE->SubText.empty() )
    &&   ( !itE->LeftRightActive ) )
    {
      int       textWidth = 2 * itE->Text.length() * 16;

      if ( ( mouseX >= ( 800 - textWidth ) / 2 )
      &&   ( mouseX < ( 800 + textWidth ) / 2 )
      &&   ( mouseY >= Y + dy )
      &&   ( mouseY < Y + dy + m_ItemHeight - 1 ) )
      {
        mouseOverItem = entryIndex;
        break;
      }
    }
    else
    {
      if ( ( mouseX >= ( 800 - displayWidth ) / 2 )
      &&   ( mouseX < ( 800 + displayWidth ) / 2 )
      &&   ( mouseY >= Y + dy )
      &&   ( mouseY < Y + dy + m_ItemHeight - 1 ) )
      {
        mouseOverItem = entryIndex;
        break;
      }
    }

    Y += m_ItemHeight;
    ++entryIndex;
    ++itE;
  }
  if ( m_MouseOverItem != mouseOverItem )
  {
    theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
    m_MouseOverItem = mouseOverItem;
  }

  if ( ( buttons & 1 ) == 0 )
  {
    m_MouseButtonReleased = true;
  }
  else if ( m_MouseButtonReleased )
  {
    m_MouseButtonReleased = false;
    if ( mouseOverItem != -1 )
    {
      m_CurrentItem = mouseOverItem;

      if ( m_Entries[m_CurrentItem].LeftRightActive )
      {
        if ( mouseX < m_CenterPos.x )
        {
          theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
          if ( m_MenuHandler )
          {
            m_MenuHandler( MEA_LEFT_PRESSED, m_Entries[m_CurrentItem] );
          }
        }
        else
        {
          theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
          if ( m_MenuHandler )
          {
            m_MenuHandler( MEA_RIGHT_PRESSED, m_Entries[m_CurrentItem] );
          }
        }
      }
      else
      {
        m_Done = true;
        theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
        if ( m_MenuHandler )
        {
          m_MenuHandler( MEA_SELECTED, m_Entries[m_CurrentItem] );
        }
      }
    }
  }


  // key controls
  if ( ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_NUMPAD_2 ) )
  ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_DOWN ) ) )
  {
    theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
    Move( 1 );
  }
  if ( ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_NUMPAD_8 ) )
  ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_UP ) ) )
  {
    theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
    Move( -1 );
  }

  if ( m_CurrentItem != -1 )
  {
    if ( m_Entries[m_CurrentItem].LeftRightActive )
    {
      if ( ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_NUMPAD_4 ) )
      ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_LEFT ) ) )
      {
        theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
        if ( m_MenuHandler )
        {
          m_MenuHandler( MEA_LEFT_PRESSED, m_Entries[m_CurrentItem] );
          return;
        }
      }
      if ( ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_NUMPAD_6 ) )
      ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_RIGHT ) ) )
      {
        theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
        if ( m_MenuHandler )
        {
          m_MenuHandler( MEA_RIGHT_PRESSED, m_Entries[m_CurrentItem] );
          return;
        }
      }
    }
  }

  if ( ( theApp.InputClass()->ReleasedVKeyPressed( theApp.BoundKey( Pac::ControlKey::PLAYER_1_ACTION ) ) )
  ||   ( theApp.InputClass()->ReleasedVKeyPressed( theApp.BoundKey( Pac::ControlKey::PLAYER_2_ACTION ) ) )
  ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_NUMPAD_ENTER ) )
  ||   ( theApp.InputClass()->ReleasedKeyPressed( Xtreme::KEY_ENTER ) ) )
  {
    if ( !m_Done )
    {
      m_Done = true;
      theApp.SoundClass()->Play( theApp.Sound( "Button" ) );
      if ( m_MenuHandler )
      {
        m_MenuHandler( MEA_SELECTED, m_Entries[m_CurrentItem] );
      }
    }
  }
}



void FallingMenu::FillTags( std::string& Caption )
{
  size_t    BracketOpenPos = Caption.find( '[' );
  while ( BracketOpenPos != -1 )
  {
    size_t    BracketEndPos = Caption.find( ']', BracketOpenPos );
    if ( BracketEndPos == -1 )
    {
      break;
    }
    std::string     Var = Caption.substr( BracketOpenPos + 1, BracketEndPos - BracketOpenPos - 1 );

    Var = theApp.GetVar( Var );

    std::string     NewCaption;

    if ( BracketOpenPos > 0 )
    {
      NewCaption = Caption.substr( 0, BracketOpenPos );
    }
    NewCaption += Var;
    NewCaption += Caption.substr( BracketEndPos + 1 );
    BracketOpenPos = Caption.find( '[' );

    Caption = NewCaption;
  }
}



void FallingMenu::Add( int Entry, int SubEntry, int MenuParam )
{
  GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );

  std::string             entryText = pTable->Entry( Entry );
  std::string             subEntryText;

  if ( SubEntry != -1 )
  {
    subEntryText = pTable->Entry( SubEntry );
  }
  FillTags( entryText );
  FillTags( subEntryText );
  m_Entries.push_back( tMenuEntry( entryText, subEntryText, Entry, MenuParam ) );
  m_Entries[m_Entries.size() - 1].TextID = Entry;
  m_Entries[m_Entries.size() - 1].SubTextID = SubEntry;
  if ( m_CurrentItem == -1 )
  {
    m_CurrentItem = 0;
  }
}



void FallingMenu::Add( int Entry, const std::string& SubText, int MenuParam )
{
  GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );
  m_Entries.push_back( tMenuEntry( pTable->Entry( Entry ), SubText, Entry, MenuParam ) );
  m_Entries[m_Entries.size() - 1].TextID = Entry;

  if ( m_CurrentItem == -1 )
  {
    m_CurrentItem = 0;
  }
}



void FallingMenu::SetValues( const GR::tPoint& Center, const GR::tPoint& FixedSize )
{
  m_CenterPos = Center;
  m_FixedSize = FixedSize;
}



int FallingMenu::DisplayWidth() const
{
  if ( m_FixedSize.x )
  {
    return m_FixedSize.x;
  }
  return MaxTextWidth();
}



int FallingMenu::MaxTextWidth() const
{
  int     displayWidth = 0;
  tMenu::const_iterator   it( m_Entries.begin() );
  while ( it != m_Entries.end() )
  {
    int       textWidth = it->Text.length() * 16;
    if ( !it->SubText.empty() )
    {
      textWidth += it->SubText.length() * 16;
    }

    displayWidth = math::maxValue( textWidth, displayWidth );

    ++it;
  }
  displayWidth += 50;

  return displayWidth;
}



int FallingMenu::DisplayHeight() const
{
  if ( m_FixedSize.y )
  {
    return m_FixedSize.y;
  }
  return m_Entries.size() * m_ItemHeight + m_ItemHeight - 10;
}



void FallingMenu::Move( int Delta )
{
  if ( m_Entries.empty() )
  {
    return;
  }
  if ( Delta < 0 )
  {
    m_CurrentItem = ( m_CurrentItem + m_Entries.size() + Delta ) % m_Entries.size();
  }
  else
  {
    m_CurrentItem = ( m_CurrentItem + Delta ) % m_Entries.size();
  }
}



int FallingMenu::SelectedItem() const
{
  if ( m_Entries[m_CurrentItem].MenuItemValue == -1 )
  {
    return m_CurrentItem;
  }
  return m_Entries[m_CurrentItem].MenuItemValue;
}



int FallingMenu::SelectedItemParam() const
{
  return m_Entries[m_CurrentItem].MenuItemParam;
}



bool FallingMenu::Done() const
{
  return m_Done;
}



void FallingMenu::SetUndone()
{
  m_Done = false;
  m_MouseButtonReleased = false;
}



int FallingMenu::EntryCount() const
{
  return m_Entries.size();
}



void FallingMenu::Clear()
{
  m_Entries.clear();
  m_CurrentItem = -1;
  m_Done = false;
  m_MouseButtonReleased = false;
  m_ItemHeight = 40;
}



void FallingMenu::UpdateItemText( int ID )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].TextID == ID )
    {
      GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );
      m_Entries[i].Text = pTable->Entry( ID );
      FillTags( m_Entries[i].Text );
      return;
    }
  }
}



void FallingMenu::SetItemText( int Item, int Value )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == Item )
    {
      GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );
      m_Entries[i].Text = pTable->Entry( Value );
      m_Entries[i].TextID = Value;
      FillTags( m_Entries[i].Text );
      return;
    }
  }
}



void FallingMenu::SetItemText( int Item, const std::string& Text )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == Item )
    {
      m_Entries[i].Text = Text;
      m_Entries[i].TextID = -1;
      FillTags( m_Entries[i].Text );
      return;
    }
  }
}



void FallingMenu::SetItemSubText( int Item, int SubText )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == Item )
    {
      GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );
      m_Entries[i].SubText = pTable->Entry( SubText );
      m_Entries[i].SubTextID = SubText;
      FillTags( m_Entries[i].SubText );
      return;
    }
  }
}



void FallingMenu::SetItemSubText( int Item, const std::string& SubText )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == Item )
    {
      m_Entries[i].SubText = SubText;
      FillTags( m_Entries[i].SubText );
      return;
    }
  }
}



void FallingMenu::SetItemLeftRightActive( int Item, bool Active )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == Item )
    {
      m_Entries[i].LeftRightActive = Active;
      return;
    }
  }
}



void FallingMenu::SetMenuHandler( tMenuHandlerFunction Handler )
{
  m_MenuHandler = Handler;
}



int FallingMenu::GetItemParamByID( int ID )
{
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    if ( m_Entries[i].MenuItemValue == ID )
    {
      return m_Entries[i].MenuItemParam;
    }
  }
  return 0;
}



void FallingMenu::ReloadTexts()
{
  GR::Database::Table*    pTable = theApp.m_TextDB.GetTable( "GUI" );
  for ( size_t i = 0; i < m_Entries.size(); ++i )
  {
    m_Entries[i].Text = pTable->Entry( m_Entries[i].TextID );
    FillTags( m_Entries[i].Text );
    if ( m_Entries[i].SubTextID != -1 )
    {
      m_Entries[i].SubText = pTable->Entry( m_Entries[i].SubTextID );
      FillTags( m_Entries[i].SubText );
    }
  }
}