//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

#include "core/stream.h"
#include "dgl/gFont.h"
#include "dgl/gBitmap.h"
#include "core/fileStream.h"
#include "dgl/gTexManager.h"

S32 GOldFont::smSheetIdCount = 0;

ResourceInstance* constructFont(Stream& stream)
{
   GOldFont *ret = new GOldFont;
   if(!ret->read(stream))
   {
      delete ret;
      return NULL;
   }

   return ret;
}
const U32 GOldFont::csm_fileVersion = 1;

Resource<GOldFont> GOldFont::create(const char *faceName, U32 size, const char *cacheDirectory, U32 charset /* = 0 */)
{
   char buf[256];
   dSprintf(buf, sizeof(buf), "%s/%s_%d.gft", cacheDirectory, faceName, size);
   
   Resource<GOldFont> ret = ResourceManager->load(buf);
   if(bool(ret))
      return ret;
   
   GOldFont *resFont = createFont(faceName, size, charset);
   if (resFont == NULL) 
   {
      AssertISV(dStricmp(faceName, "Arial") != 0, "Error, The Arial Font must always be available!");

      // Need to handle this case better.  For now, let's just return a font that we're
      //  positive exists, in the correct size...
      return create("Arial", size, cacheDirectory);
   }

   FileStream stream;
   if(ResourceManager->openFileForWrite(stream, buf)) 
   {
      resFont->write(stream);
      stream.close();
   }
   ResourceManager->add(buf, resFont, false);
   return ResourceManager->load(buf);
}

GOldFont::GOldFont()
{
   VECTOR_SET_ASSOCIATION(mCharInfoList);

   for (U32 i = 0; i < 256; i++)
      mRemapTable[i] = -1;

   mTextureSheets = NULL;
}

GOldFont::~GOldFont()
{
   delete [] mTextureSheets;
   mTextureSheets = NULL;
}

void GOldFont::insertBitmap(U16 index, U8 *src, U32 stride, U32 width, U32 height, S32 xOrigin, S32 yOrigin, S32 xIncrement)
{
   CharInfo c;
   c.bitmapIndex = -1;
   c.xOffset = 0;
   c.yOffset = 0;
   c.width = width;
   c.height = height;
   
   c.xOrigin = xOrigin;
   c.yOrigin = yOrigin;
   c.xIncrement = xIncrement;
   
   c.bitmapData = new U8[c.width * c.height];

   for(U32 y = 0; S32(y) < c.height; y++)
   {
      U32 x;
      for(x = 0; x < width; x++)
         c.bitmapData[y * c.width + x] = src[y * stride + x];
   }
   mRemapTable[index] = mCharInfoList.size();
   mCharInfoList.push_back(c);
}

static S32 QSORT_CALLBACK CharInfoCompare(const void *a, const void *b)
{
   S32 ha = (*((GOldFont::CharInfo **) a))->height;
   S32 hb = (*((GOldFont::CharInfo **) b))->height;
   
   return hb - ha;
}

void GOldFont::pack(U32 inFontHeight, U32 inBaseLine)
{
   mFontHeight = inFontHeight;
   mBaseLine = inBaseLine;
   
   // pack all the bitmap data into sheets.
   Vector<CharInfo *> vec;
   
   U32 size = mCharInfoList.size();
   U32 i;
   
   for(i = 0; i < size; i++)
   {
      CharInfo *ch = &mCharInfoList[i];
      vec.push_back(ch);
   }

   dQsort(vec.address(), size, sizeof(CharInfo *), CharInfoCompare);
   // sorted by height

   Vector<Point2I> sheetSizes;
   Point2I curSheetSize(256, 256);
   
   S32 curY = 0;
   S32 curX = 0;
   S32 curLnHeight = 0;
   for(i = 0; i < size; i++)
   {
      CharInfo *ci = vec[i];
      
      if(curX + ci->width > curSheetSize.x)
      {
         curY += curLnHeight;
         curX = 0;
         curLnHeight = 0;
      }
      if(curY + ci->height > curSheetSize.y)
      {
         sheetSizes.push_back(curSheetSize);
         curX = 0;
         curY = 0;
         curLnHeight = 0;
      }
      if(ci->height > curLnHeight)
         curLnHeight = ci->height;
      ci->bitmapIndex = sheetSizes.size();
      ci->xOffset = curX;
      ci->yOffset = curY;
      curX += ci->width;
   }
   curY += curLnHeight;
   
   if(curY < 64)
      curSheetSize.y = 64;
   else if(curY < 128)
      curSheetSize.y = 128;
      
   sheetSizes.push_back(curSheetSize);

   Vector<GBitmap *> bitmapArray;

   mNumSheets = sheetSizes.size();
   mTextureSheets = new TextureHandle[mNumSheets];
   
   for(i = 0; i < mNumSheets; i++)
      bitmapArray.push_back(new GBitmap(sheetSizes[i].x, sheetSizes[i].y, false, GBitmap::Alpha));

   for(i = 0; i < size; i++)
   {
      CharInfo *ci = vec[i];
      GBitmap *bmp = bitmapArray[ci->bitmapIndex];
      S32 x, y;
      for(y = 0; y < ci->height; y++)
         for(x = 0; x < ci->width; x++)
            *bmp->getAddress(x + ci->xOffset, y + ci->yOffset) =
                  ci->bitmapData[y * ci->width + x];
      delete[] ci->bitmapData;
   }
   for(i = 0; i < mNumSheets; i++)
	{
      assignSheet(i, bitmapArray[i]);
		mTextureSheets[i].setFilterNearest();
	}
}

TextureHandle GOldFont::getTextureHandle(S32 index)
{
   return mTextureSheets[index];
}
 
void GOldFont::assignSheet(S32 sheetNum, GBitmap *bmp)
{
   char buf[30];
   dSprintf(buf, sizeof(buf), "font_%d", smSheetIdCount++);
   mTextureSheets[sheetNum] = TextureHandle(buf, bmp);
}

U32 GOldFont::getStrWidth(const char* in_pString) const
{
   AssertFatal(in_pString != NULL, "GOldFont::getStrWidth: String is NULL, height is undefined");
   // If we ain't running debug...
   if (in_pString == NULL)
      return 0;

   return getStrNWidth(in_pString, dStrlen(in_pString));
}

U32 GOldFont::getStrWidthPrecise(const char* in_pString) const
{
   AssertFatal(in_pString != NULL, "GOldFont::getStrWidth: String is NULL, height is undefined");
   // If we ain't running debug...
   if (in_pString == NULL)
      return 0;

   return getStrNWidthPrecise(in_pString, dStrlen(in_pString));
}

//-----------------------------------------------------------------------------
U32 GOldFont::getStrNWidth(const char *str, U32 n) const
{
   AssertFatal(str != NULL, "GOldFont::getStrNWidth: String is NULL");

   if (str == NULL)   
      return(0);
      
   U32 totWidth = 0;
   const char *curChar;
   const char *endStr;
   for (curChar = str, endStr = str + n; curChar < endStr; curChar++)
   {
      if(isValidChar(*curChar))
      {
         const CharInfo& rChar = getCharInfo(*curChar);
         totWidth += rChar.xIncrement;
      }
      else if (*curChar == '\t')
      {
         const CharInfo& rChar = getCharInfo(' ');
         totWidth += rChar.xIncrement * TabWidthInSpaces;
      }
   }

   return(totWidth);
}

U32 GOldFont::getStrNWidthPrecise(const char *str, U32 n) const
{
   AssertFatal(str != NULL, "GOldFont::getStrNWidth: String is NULL");

   if (str == NULL)   
      return(0);
      
   U32 totWidth = 0;
   const char *curChar;
   const char *endStr;
   for (curChar = str, endStr = str + n; curChar < endStr; curChar++)
   {
      if(isValidChar(*curChar))
      {
         const CharInfo& rChar = getCharInfo(*curChar);
         totWidth += rChar.xIncrement;
      }
      else if (*curChar == '\t')
      {
         const CharInfo& rChar = getCharInfo(' ');
         totWidth += rChar.xIncrement * TabWidthInSpaces;
      }
   }

   if (n != 0) 
   {
      // Need to check the last char to see if it has some slop...
      char endChar = str[n-1];
      if (isValidChar(endChar))
      {
         const CharInfo& rChar = getCharInfo(endChar);
         if (rChar.width > rChar.xIncrement)
            totWidth += (rChar.width - rChar.xIncrement);
      }
   }

   return(totWidth);
}

U32 GOldFont::getBreakPos(const char *string, U32 slen, U32 width, bool breakOnWhitespace)
{
   U32 ret = 0;
   U32 lastws = 0;
   while(ret < slen)
   {
      char c = string[ret];
      if(c == '\t')
         c = ' ';
      if(!isValidChar(c))
      {
         ret++;
         continue;
      }
      if(c == ' ')
         lastws = ret+1;
      const CharInfo& rChar = getCharInfo(c);
      if(rChar.width > width || rChar.xIncrement > width)
      {
         if(lastws && breakOnWhitespace)
            return lastws;
         return ret;
      }
      width -= rChar.xIncrement;
      
      ret++;
   }
   return ret;
}

void GOldFont::wrapString(const char *txt, U32 lineWidth, Vector<U32> &startLineOffset, Vector<U32> &lineLen)
{
   startLineOffset.clear();
   lineLen.clear();

   if (!txt || !txt[0] || lineWidth < getCharWidth('W')) //make sure the line width is greater then a single character
      return;

   U32 len = dStrlen(txt);

   U32 startLine; 

   for (U32 i = 0; i < len;)
   {
      startLine = i;
      startLineOffset.push_back(startLine);

      // loop until the string is too large
      bool needsNewLine = false;
      U32 lineStrWidth = 0;
      for (; i < len; i++)
      {
         if(isValidChar(txt[i]))
         {
            lineStrWidth += getCharInfo(txt[i]).xIncrement;
            if ( txt[i] == '\n' || lineStrWidth > lineWidth )
            {
               needsNewLine = true;
               break;      
            }
         }
      }

      if (!needsNewLine)
      {
         // we are done!
         lineLen.push_back(i - startLine);
         return;
      }

      // now determine where to put the newline
      // else we need to backtrack until we find a either space character 
      // or \\ character to break up the line. 
      S32 j;
      for (j = i - 1; j >= startLine; j--)
      {
         if (dIsspace(txt[j]))
            break;
      }

      if (j < startLine)
      {
         // the line consists of a single word!              
         // So, just break up the word
         j = i - 1;
      }
      lineLen.push_back(j - startLine);
      i = j;

      // now we need to increment through any space characters at the
      // beginning of the next line
      for (i++; i < len; i++)
      {
         if (!dIsspace(txt[i]) || txt[i] == '\n')
            break;
      }
   }
}

//------------------------------------------------------------------------------
//-------------------------------------- Persist functionality
//
static const U32 csm_fileVersion = 1;

bool GOldFont::read(Stream& io_rStream)
{
   // Handle versioning
   U32 version;
   io_rStream.read(&version);
   if(version != csm_fileVersion)
      return false;
      
   // Read Font Information
   io_rStream.read(&mFontHeight);
   io_rStream.read(&mBaseLine);

   U32 size = 0;
   io_rStream.read(&size);
   mCharInfoList.setSize(size);
   U32 i;
   for(i = 0; i < size; i++)
   {
      CharInfo *ci = &mCharInfoList[i];
      io_rStream.read(&ci->bitmapIndex);
      io_rStream.read(&ci->xOffset);
      io_rStream.read(&ci->yOffset);
      io_rStream.read(&ci->width);
      io_rStream.read(&ci->height);
      io_rStream.read(&ci->xOrigin);
      io_rStream.read(&ci->yOrigin);
      io_rStream.read(&ci->xIncrement);
   }
   io_rStream.read(&mNumSheets);

   mTextureSheets = new TextureHandle[mNumSheets];
   for(i = 0; i < mNumSheets; i++)
   {
      GBitmap *bmp = new GBitmap;
      if(!bmp->readPNG(io_rStream))
      {
         delete bmp;
         return false;
      }
      assignSheet(i, bmp);
		mTextureSheets[i].setFilterNearest();
   }

   // Read character remap table
   for(i = 0; i < 256; i++)
      io_rStream.read(&mRemapTable[i]);

   return (io_rStream.getStatus() == Stream::Ok);
}

bool
GOldFont::write(Stream& stream) const
{
   // Handle versioning
   stream.write(csm_fileVersion);

   // Write Font Information
   stream.write(mFontHeight);
   stream.write(mBaseLine);

   stream.write(U32(mCharInfoList.size()));
   U32 i;
   for(i = 0; i < mCharInfoList.size(); i++)
   {
      const CharInfo *ci = &mCharInfoList[i];
      stream.write(ci->bitmapIndex);
      stream.write(ci->xOffset);
      stream.write(ci->yOffset);
      stream.write(ci->width);
      stream.write(ci->height);
      stream.write(ci->xOrigin);
      stream.write(ci->yOrigin);
      stream.write(ci->xIncrement);
   }
   stream.write(mNumSheets);
   for(i = 0; i < mNumSheets; i++)
      mTextureSheets[i].getBitmap()->writePNG(stream);

   for(i = 0; i < 256; i++)
      stream.write(mRemapTable[i]);

   return (stream.getStatus() == Stream::Ok);
}