/******************************************************************

  Module: TiFont.h

  Author: Jon Wiesman

  Description:

    2D Font class

  Copyright 2004 Sony Online Entertainment.  All rights reserved.

*******************************************************************/

#include "TiFont.h"
#include "TiUI.h"
#include "SyRaster.h"
#include "t4encoding.h"

TiFont::TiFont(int32 nFontHandle)
{
  m_nFontHandle = nFontHandle;
  m_nHeight = TiUI::Instance()->GetFontHeight(nFontHandle);

  m_sprControllerChars[CC_Triangle].SetInfo("hud_controller_triangle.tga");
  m_sprControllerChars[CC_Square].SetInfo("hud_controller_square.tga");
  m_sprControllerChars[CC_Circle].SetInfo("hud_controller_circle.tga");
  m_sprControllerChars[CC_X].SetInfo("hud_controller_cross.tga");
  m_sprControllerChars[CC_Start].SetInfo("hud_controller_start.tga");
  m_sprControllerChars[CC_Select].SetInfo("hud_controller_select.tga");
  m_sprControllerChars[CC_L1].SetInfo("hud_controller_L1.tga");
  m_sprControllerChars[CC_L2].SetInfo("hud_controller_L2.tga");
  m_sprControllerChars[CC_R1].SetInfo("hud_controller_R1.tga");
  m_sprControllerChars[CC_R2].SetInfo("hud_controller_R2.tga");
  m_sprControllerChars[CC_North].SetInfo("hud_controller_arrow.tga");
  m_sprControllerChars[CC_South].SetInfo("hud_controller_arrow.tga");
  m_sprControllerChars[CC_West].SetInfo("hud_controller_arrow_02.tga");
  m_sprControllerChars[CC_East].SetInfo("hud_controller_arrow_02.tga");
}

const TiSprite *TiFont::GetSpriteForChar(const char8 ch) const
{
  const char8 c_arEscapeCodes[CC_Max] = {
    't', 's', 'c', 'x', 'a', 'A', 'l', 'L', 'r', 'R', 'v', 'V', 'z', 'Z',
  };

  for(int32 i = 0; i < CC_Max; i++)
  {
    if(ch == c_arEscapeCodes[i])
      return &m_sprControllerChars[i];
  }

  return NULL;
}

int32 TiFont::DrawTextInternal(const char8 *pszString, TiPoint pt, const SyColor32F &crColor, uint32 uFlags) const
{
  char8 szBuffer[1024];
  wchar_t szBuffer16[1024];

  SyColor32F color = crColor;

  const char8*  pszIn  = pszString;
  char8*        pszOut = szBuffer;

  while( *pszIn )
  {
    if( pszIn[0] == '|' ) // our escape character
    {
      if( pszIn[1] == 0 )
      {
        break;
      }

      // flush current text (if any)
      *pszOut = 0;

      if( pszOut-szBuffer )
      {
        T4Encoding::utf2ucs((unsigned short*)(szBuffer16), sizeof(szBuffer), szBuffer);
        TiUI::Instance()->DrawString( szBuffer16, SyVect2I(pt.x, pt.y), color, (uFlags&TI_TF_SHADOW) ? true : false );
        pt.x += TiUI::Instance()->GetStringWidth( szBuffer );
      }

      pszOut = szBuffer;

      if(pszIn[1] == '|')
      {
        *pszOut++ = '|';
      }
      else
      {
        const TiSprite *pspr = GetSpriteForChar(pszIn[1]);
        if(pspr != NULL)
        {
          TiPoint ptSprite = pt;
          ptSprite.y += (m_nHeight - pspr->GetSize().cy) / 2;
          pspr->Draw(ptSprite);
          pt.x += pspr->GetSize().cx;
        }
      }
      pszIn++;
    }
    else if( pszIn[0] == '\r' )
    {
      *pszOut++ = ' ';
    }
    else
    {
      *pszOut++ = pszIn[0];
    }

    pszIn++;
  }

  // flush remaining text (if any)
  *pszOut = 0;

  if( pszOut-szBuffer )
  {
    T4Encoding::utf2ucs((unsigned short*)szBuffer16, sizeof(szBuffer), szBuffer);
    TiUI::Instance()->DrawString( szBuffer16, SyVect2I(pt.x, pt.y), color, (uFlags&TI_TF_SHADOW) ? true : false );
  }

  return 0;
}

int32 TiFont::Draw(const SyString &str, TiRect rect, SyColor32F crText, uint32 uFlags) const
{
  return Draw(str.AsChar(), rect, crText, uFlags);
}

int32 TiFont::Draw(const char8 *pszString, TiRect rect, SyColor32F crText, uint32 uFlags) const
{
  const TiPoint ptOffsets[4] = {
    TiPoint(1, -1),
    TiPoint(1, 1),
    TiPoint(-1, 1),
    TiPoint(-1, -1),
  };

  bool          bCalcOnly   = ((uFlags & TI_TF_CALCONLY) != 0);
  int32 nRet = 0;

  if(((uFlags & TI_TF_GLOW) != 0) && !bCalcOnly)
  {
    SyColor32F crGlow = crText;
    crGlow.A *= .35f;
    crGlow.B = 1.f;
    
    if((uFlags & TI_TF_SHADOW) != 0)
    {
      nRet = TheRealDraw(pszString, rect, crText, uFlags);
    }

    for(int32 i = 0; i < 4; i++)
    {
      TiRect rectGlow = rect + ptOffsets[i];
      TheRealDraw(pszString, rectGlow, crGlow, (uFlags & ~TI_TF_SHADOW));
    }
    
    // now draw one more time without shadow
    nRet = TheRealDraw(pszString, rect, crText, (uFlags & ~TI_TF_SHADOW));
  }
  else
  {
    nRet = TheRealDraw(pszString, rect, crText, uFlags);
  }
  return nRet;
}

int32 TiFont::TheRealDraw(const char8 *pszString, TiRect rect, SyColor32F crText, uint32 uFlags) const
{
    // note: uFlags == 0xffffffff signifies a super-duper-secret mode, in which we just count lines of text & return the result!

  // compute inner text box size (tight fit to actual text)
  TiPoint pt = rect.TopLeft();

  bool          bCalcOnly   = ((uFlags & TI_TF_CALCONLY) != 0);

  if(uFlags & TI_TF_VCENTER && !bCalcOnly)
  {
    uint32 uNewFlags = uFlags | TI_TF_CALCONLY;
    int32 nTotalHeight = Draw(pszString, rect, crText, uNewFlags);

    if(nTotalHeight < rect.Height())
      pt.y += (rect.Height() - nTotalHeight) / 2;
  }

  // write out formatted text

  int32         nLineWidth  = 0;
  int32         nCount      = 0;
  int32         nSpaceLoc   = 0;
  int32         nBreakLoc   = 0;
  int32         nRowCount   = 0;
  const char8*  pszLineBase = pszString;
  char8         szRowBuffer[256];
  int32         nLineOffset = (uFlags>>24);
  int32         iLine       = 0;
  int32         nMaxRows    = rect.Height() / m_nHeight;

  // word wrapping logic
  while( pszString[nCount] != 0 )
  {
    if( pszString[nCount] == '\n' )
    {
      // output current row
      int32 nLength = int32(&pszString[nCount]-pszLineBase);

      // in case of newlines w/no text
      if( nLength < 0 ) { nLength = 0; }

      if( (!bCalcOnly) )
      {
        SyStr::Strncpy( szRowBuffer, pszLineBase, nLength+1 );
      }
      szRowBuffer[ nLength ] = 0;

      //if( !bCalcOnly )
      {
        if( (uFlags & TI_TF_ELLIPSIS) && (nRowCount+1 == nMaxRows) )
        {
          if( SyStr::strlen( &pszString[nCount+1] ) ) // still remaining text, so append ellipsis
          {
            AppendEllipsis( szRowBuffer, rect.Width() );
          }
        }
      }

      // OUTPUT
      if( iLine >= nLineOffset )
      {
        if( !bCalcOnly )
        {
          TiPoint ptActual = pt;

          if( uFlags & TI_TF_HALIGN_CENTER )
          {
            ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer)) / 2;
          }
          else if( uFlags & TI_TF_HALIGN_RIGHT )
          {
            ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer));
          } 
          DrawTextInternal( szRowBuffer, ptActual, crText, uFlags);
        }

        nRowCount++;
      }

      iLine++;
      pt.y += m_nHeight;

      // advance past '\n' & update line base

      nCount++;
      pszLineBase = &pszString[nCount];
      nSpaceLoc = 0;
      nBreakLoc = 0;

      if( nRowCount >= nMaxRows )
      {
        break;
      }

      continue;
    }

    // compute width of current line in pixels

    int32 nLength = int32(&pszString[nCount]-pszLineBase);

    // in case of newlines w/no text
    if( nLength < 0 ) { nLength = 0; }

    //if( (!bCalcOnly) )
    {
      SyStr::Strncpy( szRowBuffer, pszLineBase, nLength+1 );
    }
    szRowBuffer[ nLength ] = 0;
    int32 nLastLineWidth = nLineWidth;
    nLineWidth = GetTextWidth( szRowBuffer );

    // need to force a line break?

    if( nLineWidth >= (rect.Width() - 6) ) // small cushion
    {
      // output text
      if( nSpaceLoc && ( !(uFlags & TI_TF_HARD_BREAK) || bCalcOnly ) )
      {
        nLength = int32(&pszString[nSpaceLoc+1]-pszLineBase);

        // in case of newlines w/no text
        if( nLength < 0 ) { nLength = 0; }

        //if( (!bCalcOnly) )
        {
          SyStr::Strncpy( szRowBuffer, pszLineBase, nLength+1 );
        }
        szRowBuffer[ nLength ] = 0;

        nCount -= (nCount - nSpaceLoc - 1);
        pszLineBase = &pszString[nCount];

      }
      else
      {
        // hard break
        nLength = int32(&pszString[nBreakLoc]-pszLineBase);

        // in case of newlines w/no text
        if( nLength < 0 ) { nLength = 0; }

        // if( (!bCalcOnly) )
        {
          SyStr::Strncpy( szRowBuffer, pszLineBase, nLength+1 );
        }
        szRowBuffer[ nLength ] = 0;

        nCount -= (nCount - nBreakLoc);
        pszLineBase = &pszString[nBreakLoc];
      }

      //if( !bCalcOnly )
      {
        if( (uFlags & TI_TF_ELLIPSIS) && (nRowCount+1 == nMaxRows) )
        {
          if( SyStr::strlen( pszLineBase ) ) // still remaining text, so append ellipsis
          {
            AppendEllipsis( szRowBuffer, rect.Width() );
          }
        }
      }

      // OUTPUT
      if( iLine >= nLineOffset )
      {
        if( !bCalcOnly )
        {
          TiPoint ptActual = pt;

          if( uFlags & TI_TF_HALIGN_CENTER )
          {
            ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer)) / 2;
          }
          else if( uFlags & TI_TF_HALIGN_RIGHT )
          {
            ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer));
          } 
          DrawTextInternal( szRowBuffer, ptActual, crText, uFlags);
        }

        nRowCount++;
      }

      iLine++;
      pt.y += m_nHeight;

      if( nRowCount >= nMaxRows )
      {
        break;
      }

      nSpaceLoc = 0;
      nBreakLoc = 0;

      continue;
    } 
    else
    {
      if( nLastLineWidth != nLineWidth )
      {
        nBreakLoc = nCount;
      }
    }

    // keep track of last 'space'
    if( pszString[nCount] == ' ' )
    {
      nSpaceLoc = nCount;
    }

    // bump to next character
    nCount++;
  }

  // Do we have left-overs?
  if( &pszString[nCount]-pszLineBase )
  {
    int32 nLength = int32(&pszString[nCount]-pszLineBase);

    // in case of newlines w/no text
    if( nLength < 0 ) { nLength = 0; }

    // if( (!bCalcOnly) )
    {
      SyStr::Strncpy( szRowBuffer, pszLineBase, nLength+1 );
    }
    szRowBuffer[ nLength ] = 0;

    // OUTPUT
    if( iLine >= nLineOffset )
    {
      if( !bCalcOnly )
      {
        TiPoint ptActual = pt;

        if( uFlags & TI_TF_HALIGN_CENTER )
        {
          ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer)) / 2;
        }
        else if( uFlags & TI_TF_HALIGN_RIGHT )
        {
          ptActual.x += (rect.Width() - GetTextWidth(szRowBuffer));
        } 
        DrawTextInternal( szRowBuffer, ptActual, crText, uFlags);
      }

      nRowCount++;
    }
  }

  return( nRowCount * m_nHeight );
}

//***********************************************************************
// AppendEllipsis
//***********************************************************************

void TiFont::AppendEllipsis( char8* pszText, int32 nMaxWidth ) const
{
  int32 nLength            = SyStr::strlen( pszText );
  int32 nEllipsisWidth = TiUI::Instance()->GetStringWidth( "..." );

  if( nEllipsisWidth >= nMaxWidth )
  {
    return;
  }

  // output ellipsis
  while( GetTextWidth( pszText ) + nEllipsisWidth >= nMaxWidth )
  {
    nLength--;
    pszText[nLength] = 0;
  }

  strcat( pszText, "..." );
}


//***********************************************************************
// GetTextWidth
//***********************************************************************

int32 TiFont::GetTextWidth(const char8 *pszString) const
{
  char8 szBuffer[1024];
  if( SyStr::strlen( pszString ) >= 1024 )
  {
    return( 0 );
  }

  int32 nExtra = 0; // nExtra non-text width

  // parse out escape sequences, noting the ones that have physical width

  const char8*  pszIn = pszString;
  char8*        pszOut = szBuffer;

  while( *pszIn )
  {
    if( pszIn[0] == '|' ) // our escape character
    {
      const TiSprite *pspr = GetSpriteForChar(pszIn[1]);
      if(pspr != NULL)
      {
        nExtra += pspr->GetSize().cx;
      }

      if( pszIn[1] == 0 )
      {
        break;
      }
      else if(pszIn[1] == '|')
      {
        *pszOut++ = '|';
      }

      pszIn++;
    }
    else
    {
      *pszOut++ = pszIn[0];
    }

    pszIn++;
  }

  *pszOut = 0;

  return( TiUI::Instance()->GetStringWidth( szBuffer ) + nExtra );
}

