// TiSprite.cpp
// This class draws 2D images on the screen for the UI.  These images are usually static, but the 
// sprite class also handles 2D animations, because it is BAD ASS!

#include "TiSprite.h"
#include "tinyxml/tinyxml.h"
#include "SyPathname.h"
#include "SyDebug.h"
#include "TiUi.h"

///////////////////////////////////////////////////////////////////////////////
//
// TiSpriteFrame
//
///////////////////////////////////////////////////////////////////////////////

TiSpriteFrame TiSpriteFrame::sm_arPool[SF_SpriteFrameCount];
int32 TiSpriteFrame::sm_nNextFrame = 0;

TiSpriteFrame::TiSpriteFrame()
{
    Init();
}

void TiSpriteFrame::Init()
{
    m_uHashId = 0;
    m_nSurfaceId = SF_InvalidSurfaceId;
    m_ptHotspot.Set(0, 0);
    m_ptAtlasOffset.Set(0, 0);
    m_rectSource.SetRect(0, 0, 0, 0);
    m_fDuration = 1.f;
}

TiSpriteFrame *TiSpriteFrame::AllocFrame()
{
    SyAssert(sm_nNextFrame < SF_SpriteFrameCount);

    TiSpriteFrame *pReturn = &sm_arPool[sm_nNextFrame];
    sm_nNextFrame++;

    return pReturn;
}

int32 TiSpriteFrame::GetSurfaceId() const
{
    if(m_nSurfaceId != SF_InvalidSurfaceId)
        return m_nSurfaceId;

    SyVect2I ptOffset, ptSize;

    TiUI::Instance()->GetBitmapInfo(m_uHashId, m_nSurfaceId, ptOffset, ptSize);

    m_ptAtlasOffset.Set(ptOffset.X, ptOffset.Y);

    return m_nSurfaceId;
}

void TiSpriteFrame::ClearAll()
{
    sm_nNextFrame = 0;
    for(int32 i = 0; i < TiSpriteFrame::SF_SpriteFrameCount; i++)
    {
        sm_arPool[i].Init();
    }
}

///////////////////////////////////////////////////////////////////////////////
//
// TiSpriteInfo
//
///////////////////////////////////////////////////////////////////////////////

TiSpriteInfo TiSpriteInfo::sm_arPool[SIP_SpriteInfoCount];
int32 TiSpriteInfo::sm_nNextSprite = 0;

TiSpriteInfo::TiSpriteInfo()
{
    Init();
}

void TiSpriteInfo::Init()
{
    m_fTotalDuration = 0.f;
    m_nCount = 0;
    m_arFrames = NULL;
    m_uId = 0;
}

const TiSpriteFrame *TiSpriteInfo::GetFrameFromStart(float32 fOffsetTime, bool bLoop) const
{
    if(m_fTotalDuration == 0.0f)
        return NULL;

    if(m_nCount == 1 || m_fTotalDuration < 0.0f)
        return &m_arFrames[0];


    if(bLoop)
    {
        float32 fCycles = floor(fOffsetTime / m_fTotalDuration);
        fOffsetTime -= fCycles * m_fTotalDuration;
    }
    else
    {
        fOffsetTime = SY_MIN(fOffsetTime, m_fTotalDuration);
    }


    int32 iFrame = 0;
    while(iFrame < m_nCount)
    {
        float fTime = m_arFrames[iFrame].m_fDuration;
        if(fOffsetTime <= fTime)
            return m_arFrames + iFrame;
        fOffsetTime -= fTime;
        iFrame++;
    }

    return NULL;
}

void TiSpriteInfo::CreateTextureSprites(std::map< SyResourceID, TiBitmapInfo > &mapAtlasDirectory)
{
    std::map< SyResourceID, TiBitmapInfo > ::const_iterator itr;
    itr = mapAtlasDirectory.begin();

    while(itr != mapAtlasDirectory.end())
    {
        SyResourceID id = itr->first;
        const TiBitmapInfo &bi = itr->second;

        TiSpriteInfo *pInfo = AllocSprite();
        pInfo->m_uId = id;
        pInfo->m_fTotalDuration = 1.f;
        pInfo->m_nCount = 1;
        pInfo->m_arFrames = TiSpriteFrame::AllocFrame();
        pInfo->m_arFrames[0].m_fDuration = 1.f;
        pInfo->m_arFrames[0].m_nSurfaceId = TiSpriteFrame::SF_InvalidSurfaceId ;
        pInfo->m_arFrames[0].m_ptAtlasOffset.Set(bi.GetSurfaceOffset().X, bi.GetSurfaceOffset().Y);
        pInfo->m_arFrames[0].m_rectSource.SetRect(TiPoint(0, 0), TiSize(bi.GetWidth(), bi.GetHeight()));
        pInfo->m_arFrames[0].m_uHashId = id;

        itr++;
    }
}

void TiSpriteInfo::ReadXML(const char8 *pszFilename)
{
    // open file
    SyString strPath = "programming/ui/";
    strPath += pszFilename;
    SyPathname pathname( strPath.AsChar() );

    TiXmlDocument doc( pathname.Full().AsChar() );
    if( !doc.LoadFile() )
    {
        return ;
    }

    TiXmlHandle root( &doc );

    const char8* pszDocName = pathname.Base().AsChar();

    // read items
    for( TiXmlElement* pElement = root.FirstChildElement(pszDocName).FirstChildElement("sprite").Element();
        pElement;
        pElement = pElement->NextSiblingElement( "sprite" ) )
    {

        const char8 *pszName = pElement->Attribute( "name" );


        TiSpriteInfo *pInfo = TiSpriteInfo::AllocSprite();
        pInfo->m_nCount = 0;
        pInfo->m_arFrames = NULL;
        pInfo->m_fTotalDuration = 0.f;
        pInfo->m_uId = SyHashResourceID( pszName );

        for(TiXmlElement *pFrameElement = pElement->FirstChildElement("frame");
            pFrameElement;
            pFrameElement = pFrameElement->NextSiblingElement( "frame" ) )
        {
            TiSpriteFrame *psf = TiSpriteFrame::AllocFrame();
            if(pInfo->m_arFrames == NULL)
            {
                // set the 'array' pointer to the first frame
                pInfo->m_arFrames = psf;
            }
            pInfo->m_nCount++;

            const char8 *pszFile = pFrameElement->Attribute( "file" );
            pFrameElement->QueryIntAttribute( "left", &psf->m_rectSource.left );
            pFrameElement->QueryIntAttribute( "top", &psf->m_rectSource.top );
            
            if(TIXML_NO_ATTRIBUTE == pFrameElement->QueryIntAttribute( "right", &psf->m_rectSource.right ))
            {
                int32 nWidth = 0;
                pFrameElement->QueryIntAttribute("width", &nWidth);
                psf->m_rectSource.right = psf->m_rectSource.left + nWidth;
            }
            if(TIXML_NO_ATTRIBUTE == pFrameElement->QueryIntAttribute( "bottom", &psf->m_rectSource.bottom ))
            {
                int32 nHeight = 0;
                pFrameElement->QueryIntAttribute("height", &nHeight);
                psf->m_rectSource.bottom = psf->m_rectSource.top + nHeight;
            }
            pFrameElement->QueryIntAttribute( "hx", &psf->m_ptHotspot.x );
            pFrameElement->QueryIntAttribute( "hy", &psf->m_ptHotspot.y );
            
            double dDuration = psf->m_fDuration;
            pFrameElement->QueryDoubleAttribute( "duration", &dDuration );
            psf->m_fDuration = (float32)dDuration;

            psf->m_uHashId = SyHashResourceID( pszFile );
            psf->m_nSurfaceId = TiSpriteFrame::SF_InvalidSurfaceId;

            pInfo->m_fTotalDuration += psf->m_fDuration;
        }
    }
    SyDebug("%d of %d SpriteFrames used\n%d of %d SpriteInfos used\n", 
        TiSpriteFrame::sm_nNextFrame,
        TiSpriteFrame::SF_SpriteFrameCount,
        TiSpriteInfo::sm_nNextSprite,
        TiSpriteInfo::SIP_SpriteInfoCount);
}

TiSpriteInfo *TiSpriteInfo::FindSprite(uint32 uId)
{
    if(uId == 0)
        return NULL;
    for(int32 i = 0; i < sm_nNextSprite; i++)
    {
        if(uId == sm_arPool[i].m_uId)
            return &sm_arPool[i];
    }
    return NULL;
}

TiSpriteInfo *TiSpriteInfo::FindSprite(const char8 *pszName)
{
    return FindSprite(SyHashResourceID(pszName));
}

TiSpriteInfo *TiSpriteInfo::AllocSprite()
{
    SyAssert(sm_nNextSprite < SIP_SpriteInfoCount);

    TiSpriteInfo *pReturn = &sm_arPool[sm_nNextSprite];
    sm_nNextSprite++;

    memset(pReturn, 0, sizeof(TiSpriteInfo));

    return pReturn;
}

TiSize TiSpriteInfo::GetSize() const
{
    if(m_arFrames != NULL)
        return m_arFrames->m_rectSource.GetSize();
    return TiSize(0, 0);
}

void TiSpriteInfo::ClearAll()
{
    sm_nNextSprite = 0;
    for(int32 i = 0; i < TiSpriteInfo::SIP_SpriteInfoCount; i++)
    {
        sm_arPool[i].Init();
    }
    TiSpriteFrame::ClearAll();
    TiSprite::ForceReload();
}

///////////////////////////////////////////////////////////////////////////////
//  TiSprite
///////////////////////////////////////////////////////////////////////////////

float TiSprite::sm_fAnimationTime = 0;
int32 TiSprite::sm_nReloadCount = 0;

void TiSprite::SetInfo(TiSpriteInfo *pInfo)
{
    m_pInfo = pInfo;
    m_idSpriteInfo = m_pInfo != NULL ? m_pInfo->GetID() : 0;
    m_iReload = sm_nReloadCount;
}

void TiSprite::SetInfo(const char8 *psz)
{
    m_pInfo = TiSpriteInfo::FindSprite(psz);
    m_idSpriteInfo = m_pInfo != NULL ? m_pInfo->GetID() : 0;
    m_iReload = sm_nReloadCount;
}

void TiSprite::SetInfo(SyResourceID uHash)
{
    if(uHash == 0)
    {
        m_pInfo = NULL;
        return;
    }
    m_pInfo = TiSpriteInfo::FindSprite(uHash);
    m_idSpriteInfo = m_pInfo != NULL ? m_pInfo->GetID() : 0;
    m_iReload = sm_nReloadCount;
}

void TiSprite::ForceReload()
{
  sm_nReloadCount++;
}

void TiSprite::VerifySpriteInfo() const
{
  // this needs to be a const function because it will be called 
  // from the Draw family.  I hate this...
  if(sm_nReloadCount > m_iReload)
  {
    m_pInfo = TiSpriteInfo::FindSprite(m_idSpriteInfo);
    m_iReload = sm_nReloadCount;
  }
}

void TiSprite::UpdateAnimationTime(float32 fElapsed)
{
    sm_fAnimationTime += fElapsed;
}

void TiSprite::UpdateAnimationTime(SyTime time)
{
    static SyTime timeLast = 0;
    if(timeLast != 0)
    {
        float32 fDelta = ((float32)(time - timeLast)/1000.0f);
        UpdateAnimationTime(fDelta);
    }
    timeLast = time;
}

TiSize TiSprite::GetSize() const
{
    VerifySpriteInfo();

    if(NULL != m_pInfo)
        return m_pInfo->GetSize();

    return TiSize(0, 0);
}

int32 TiSprite::PrepareRects(TiPoint pt, TiRect rectClip, TiRect &rectSrc, TiRect &rectOS) const
{
  const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime, m_bLoop);

  pt -= pFrame->m_ptHotspot;

  rectClip.right = SY_MIN(rectClip.right, c_rectPS3Screen.right);
  rectClip.bottom = SY_MIN(rectClip.bottom, c_rectPS3Screen.bottom);
  rectClip.top = SY_MAX(0, rectClip.top);
  rectClip.left = SY_MAX(0, rectClip.left);

  rectOS.SetRect(pt.x, pt.y, pt.x + pFrame->m_rectSource.Width(), pt.y + pFrame->m_rectSource.Height());
  rectSrc = pFrame->m_rectSource;
  rectSrc += pFrame->m_ptAtlasOffset;

  // get new rect src and new rect OS
  if(rectOS.left < rectClip.left)
  {
    rectSrc.left += rectClip.left - rectOS.left;
    rectOS.left = rectClip.left;
  }
  if(rectOS.top < rectClip.top)
  {
    rectSrc.top += rectClip.top - rectOS.top;
    rectOS.top = rectClip.top;
  }
  if(rectOS.right > rectClip.right)
  {
    rectSrc.right -= (rectOS.right - rectClip.right);
    rectOS.right = rectClip.right;
  }
  if(rectOS.bottom > rectClip.bottom)
  {
    rectSrc.bottom -= (rectOS.bottom - rectClip.bottom);
    rectOS.bottom = rectClip.bottom;
  }

  return 0;
}

int32 TiSprite::PrepareRects(TiRect rectOSIn, TiRect rectClip, TiRect &rectSrc, TiRect &rectOS) const
{
  const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime, m_bLoop);

  if(pFrame->m_rectSource.Width() == rectOSIn.Width() && pFrame->m_rectSource.Height() == rectOSIn.Height())
    return PrepareRects(rectOSIn.TopLeft(), rectClip, rectSrc, rectOS);

  if(rectOSIn.Width() == 0 || rectOSIn.Height() == 0 || pFrame->m_rectSource.Width() == 0 || pFrame->m_rectSource.Height() == 0)
  {
    rectSrc.SetRectEmpty();
    rectOS.SetRectEmpty();
    return 0;
  }

  float32 fScalarX = (float32)rectOSIn.Width() / (float32)pFrame->m_rectSource.Width();
  float32 fScalarY = (float32)rectOSIn.Height() / (float32)pFrame->m_rectSource.Height();

  TiPoint ptHotspot = pFrame->m_ptHotspot;
  ptHotspot.x = (int32)((float32)ptHotspot.x * fScalarX);
  ptHotspot.y = (int32)((float32)ptHotspot.y * fScalarY);

  rectClip.right = SY_MIN(rectClip.right, c_rectPS3Screen.right);
  rectClip.bottom = SY_MIN(rectClip.bottom, c_rectPS3Screen.bottom);
  rectClip.top = SY_MAX(0, rectClip.top);
  rectClip.left = SY_MAX(0, rectClip.left);

  rectOS = rectOSIn + ptHotspot;
  rectSrc = pFrame->m_rectSource;
  rectSrc += pFrame->m_ptAtlasOffset;

  // get new rect src and new rect OS
  if(rectOS.left < rectClip.left)
  {
    rectSrc.left += (int32)((float32)(rectClip.left - rectOS.left) / fScalarX);
    rectOS.left = rectClip.left;
  }
  if(rectOS.top < rectClip.top)
  {
    rectSrc.top += (int32)((float32)(rectClip.top - rectOS.top) / fScalarY);
    rectOS.top = rectClip.top;
  }
  if(rectOS.right > rectClip.right)
  {
    rectSrc.right -= (int32)((float32)(rectOS.right - rectClip.right) / fScalarX);
    rectOS.right = rectClip.right;
  }
  if(rectOS.bottom > rectClip.bottom)
  {
    rectSrc.bottom -= (int32)((float32)(rectOS.bottom - rectClip.bottom) / fScalarY);
    rectOS.bottom = rectClip.bottom;
  }

  return 0;
}

int32 TiSprite::PrepareRects(TiFRect rectOSIn, TiRect rectClip, TiRect &rectSrc, TiFRect &rectOS) const
{
  const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime, m_bLoop);

  if(rectOSIn.Width() == 0 || rectOSIn.Height() == 0 || pFrame->m_rectSource.Width() == 0 || pFrame->m_rectSource.Height() == 0)
  {
    rectSrc.SetRectEmpty();
    rectOS.SetRectEmpty();
    return 0;
  }

  float32 fScalarX = rectOSIn.Width() / (float32)pFrame->m_rectSource.Width();
  float32 fScalarY = rectOSIn.Height() / (float32)pFrame->m_rectSource.Height();

  TiPoint ptHotspot = pFrame->m_ptHotspot;
  ptHotspot.x = (int32)((float32)ptHotspot.x * fScalarX);
  ptHotspot.y = (int32)((float32)ptHotspot.y * fScalarY);

  rectClip.right = SY_MIN(rectClip.right, c_rectPS3Screen.right);
  rectClip.bottom = SY_MIN(rectClip.bottom, c_rectPS3Screen.bottom);
  rectClip.top = SY_MAX(0, rectClip.top);
  rectClip.left = SY_MAX(0, rectClip.left);

  rectOS = rectOSIn + TiFPoint(ptHotspot);
  rectSrc = pFrame->m_rectSource;
  rectSrc += pFrame->m_ptAtlasOffset;

  // get new rect src and new rect OS
  if(rectOS.left < rectClip.left)
  {
    rectSrc.left += (int32)((float32)(rectClip.left - rectOS.left) / fScalarX);
    rectOS.left = (float32)rectClip.left;
  }
  if(rectOS.top < rectClip.top)
  {
    rectSrc.top += (int32)((float32)(rectClip.top - rectOS.top) / fScalarY);
    rectOS.top = (float32)rectClip.top;
  }
  if(rectOS.right > rectClip.right)
  {
    rectSrc.right -= (int32)((float32)(rectOS.right - rectClip.right) / fScalarX);
    rectOS.right = (float32)rectClip.right;
  }
  if(rectOS.bottom > rectClip.bottom)
  {
    rectSrc.bottom -= (int32)((float32)(rectOS.bottom - rectClip.bottom) / fScalarY);
    rectOS.bottom = (float32)rectClip.bottom;
  }

  return 0;
}

int32 TiSprite::Draw(const TiPoint &pt, const TiRect &rectClip, float32 fOpacity) const
{
    int32 nRet = -1;

    VerifySpriteInfo();

    if(NULL == m_pInfo)
    {
        static bool bEverHere = false;
        if(!bEverHere)
            SyDebug("Exiting TiSprite::Draw because the UA info is NULL.\n");
        bEverHere = true;
        return 0;
    }

    const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime);
    if(NULL == pFrame)
    {
        return 0;
    }

    int32 nSurfaceId = pFrame->GetSurfaceId();

    TiRect rectOS;
    TiRect rectSrc;
    PrepareRects(pt, rectClip, rectSrc, rectOS);

    if(rectSrc.IsAbnormal() || rectOS.IsAbnormal())
    {
        return 0;
    }
    if(nSurfaceId != TiSpriteFrame::SF_InvalidSurfaceId)
    {
        TiUI::Instance()->Blit(nSurfaceId, 0, 
            SyVect2I(rectOS.left, rectOS.top), 
            SyVect2I(rectSrc.left, rectSrc.top), 
            SyVect2I(rectSrc.Width(), rectSrc.Height()), 
            SYALPHABLENDMODE_AVG, fOpacity);
    }

    return nRet;
}

int32 TiSprite::Tile(const TiRect &rect, const TiRect &rectClip, float32 fOpacity) const
{
    VerifySpriteInfo();

    TiPoint pt = rect.TopLeft();
    if(GetSize().cx <= 0 || GetSize().cy <= 0)
        return 0;

    for(pt.y = rect.top; pt.y < rect.bottom; pt.y += GetSize().cy)
    {
        for(pt.x = rect.left; pt.x < rect.right; pt.x += GetSize().cx)
        {
            Draw(pt, rect & rectClip, fOpacity);
        }
    }
    
    return 0;
}

int32 TiSprite::Stretch(const TiRect &rectOSIn, const TiRect &rectClip, float32 fOpacity) const
{
    VerifySpriteInfo();

  
    if(NULL == m_pInfo)
        return 0;

    const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime);
    if(NULL == pFrame)
        return 0;

    TiRect rectOS;
    TiRect rectSrc;
    PrepareRects(rectOSIn, rectClip, rectSrc, rectOS);


    int32 nSurfaceId = pFrame->GetSurfaceId();
    if(nSurfaceId != TiSpriteFrame::SF_InvalidSurfaceId)
    {
      TiUI::Instance()->StretchBlit(nSurfaceId, 0, 
        SyVect2I(rectOS.left, rectOS.top), 
        SyVect2I(rectOS.Width(), rectOS.Height()),
        SyVect2I(rectSrc.left, rectSrc.top), 
        SyVect2I(rectSrc.Width(), rectSrc.Height()), 
        SYALPHABLENDMODE_AVG, fOpacity);
    }

    return 0;
}

int32 TiSprite::Pulse(TiRect rect, float32 fMagnitude, float32 fSpeed, const TiRect &rectClip, float32 fOpacity /* = 1.0f */) const
{
  //return Pulse(TiFRect(rect), fMagnitude, fSpeed, rectClip, fOpacity);

  VerifySpriteInfo();

  if(fMagnitude <= 0.f)
  {
    return Stretch(rect, rectClip, fOpacity);
  }

  fMagnitude = SY_MIN(1.f, fMagnitude);

  // fMagnitude = sine wave from 0 to 1 
  fMagnitude *= (.5f + (sinf(sm_fAnimationTime * fSpeed) / 2.f) );

  rect.AdjustSize(- (int)(((float32)rect.Width()  / 2.f) * fMagnitude),
                  - (int)(((float32)rect.Height() / 2.f) * fMagnitude));


  return Stretch(rect, rectClip, fOpacity);
}

int32 TiSprite::Oscillate(TiPoint pt1, TiPoint pt2, float32 fSpeed /* = 2 pi */, TiRect rectClip /* = screen */, float32 fOpacity /* = 1.f */) const
{
  VerifySpriteInfo();

  if(fSpeed <= 0.f)
    return Draw(pt1, rectClip, fOpacity);

  TiPoint pt = pt1;

  float32 fMag = .5f + sinf(sm_fAnimationTime * fSpeed) / 2.f;

  pt.x += (int32)((float32)(pt2.x - pt1.x) * fMag);
  pt.y += (int32)((float32)(pt2.y - pt1.y) * fMag);

  return Draw(pt, rectClip, fOpacity);
}

int32 TiSprite::Stretch(const TiFRect &rectOSIn, const TiRect &rectClip, float32 fOpacity) const
{
  VerifySpriteInfo();


  if(NULL == m_pInfo)
    return 0;

  const TiSpriteFrame *pFrame = m_pInfo->GetFrameFromStart(sm_fAnimationTime - m_fStartTime);
  if(NULL == pFrame)
    return 0;

  TiFRect rectOS;
  TiRect rectSrc;
  PrepareRects(rectOSIn, rectClip, rectSrc, rectOS);


  int32 nSurfaceId = pFrame->GetSurfaceId();
  if(nSurfaceId != TiSpriteFrame::SF_InvalidSurfaceId)
  {
    TiUI::Instance()->StretchBlit(nSurfaceId, 0, 
      SyVect2(rectOS.left, rectOS.top), 
      SyVect2(rectOS.Width(), rectOS.Height()),
      SyVect2I(rectSrc.left, rectSrc.top), 
      SyVect2I(rectSrc.Width(), rectSrc.Height()), 
      SYALPHABLENDMODE_AVG, fOpacity);
  }

  return 0;
}

int32 TiSprite::Pulse(TiFRect frect, float32 fMagnitude, float32 fSpeed, const TiRect &rectClip, float32 fOpacity /* = 1.0f */) const
{
  VerifySpriteInfo();

  if(fMagnitude <= 0.f)
  {
    return Stretch(frect, rectClip, fOpacity);
  }

  fMagnitude = SY_MIN(1.f, fMagnitude);

  // fMagnitude = sine wave from 0 to 1 
  fMagnitude *= (.5f + (sinf(sm_fAnimationTime * fSpeed) / 2.f) );

  frect.AdjustSize(- ((frect.Width()  / 2.f) * fMagnitude),
    - ((frect.Height() / 2.f) * fMagnitude));

  return Stretch(frect, rectClip, fOpacity);
}

