#include "shippingHandler.h"

//extracting values from a hash.
void shippingHandler :: addFormattedTextToFixedDimensions (Param & hash, TextContainer & tc)
{
  string stringToAdd = "STUB STRING", helpRef = "NULL_INDEX";
  int colorIndex = 0, lineLimit = -1;
  bool forceNewLine = false;

  hash.getString("String", stringToAdd);
  hash.getString("Help Link Reference", helpRef);
  hash.getInt("Font Color Index", colorIndex);
  hash.getInt("Max Number of Lines",lineLimit);
  hash.getBool("Force Newline?",forceNewLine);

  addFormattedTextToFixedDimensions(stringToAdd, tc.getFontName(), tc.getLineSpacing(), tc.getFormattedText(), tc.getTextContainerWidth()-tc.getMarginLeft()-tc.getMarginRight(), forceNewLine, colorIndex, lineLimit, helpRef);
}

//extracting values from a hash.
void shippingHandler :: addFormattedTextToFixedDimensions (Param & hash, vector <vector<FormattedText> > & fmt)
{
  string stringToAdd = "STUB STRING", fontName = "NULL_INDEX", helpRef = "NULL_INDEX";
  int heightFactor = 8, containerWidth = 100, colorIndex = 0, lineLimit = -1;
  bool forceNewLine = false;

  hash.getString("String", stringToAdd);
  hash.getString("Font Name", fontName);
  hash.getString("Help Link Reference", helpRef);
  hash.getInt("Text Height Calculation Factor", heightFactor);
  hash.getInt("Container Width", containerWidth);
  hash.getInt("Font Color Index", colorIndex);
  hash.getInt("Max Number of Lines",lineLimit);
  hash.getBool("Force Newline?",forceNewLine);

  addFormattedTextToFixedDimensions(stringToAdd, fontName, heightFactor, fmt, containerWidth, forceNewLine, colorIndex, lineLimit, helpRef);
}

//Dimensions are fixed. Text is added so it wraps within the given width.
void shippingHandler::addFormattedTextToFixedDimensions (const string & stringToAdd,
                                                         const string & fontName,
                                                         uint16_t heightFactor,
                                                         vector<vector <FormattedText> > & fmt,
                                                         uint16_t containerWidth,
                                                         bool forceNewLine,
                                                         int colorIndex,
                                                         int lineLimit,
                                                         const string & helpRef)
{
  int xPos=0, yPos=0;
  uint16_t leftoverWidth;
  bool done=false, wrappedNewLine;
  string stringToUse = stringToAdd, stringLeftover="";
  uint16_t textWidth, textHeight;
  FormattedText f;
  leftoverWidth=containerWidth-getWidthOfLastLine(fmt);
  int response;
  int numLinesAdded=0;
  vector <FormattedText> newLineOfFmt;

  if (stringToAdd.empty())
    stringToUse="A";

  while (!done)
    {
      newLineOfFmt.clear();
      wrappedNewLine=false;
      f.clear();
      //get the largest piece of text that will fit within the given width.
      response=getLargestStringToFitInWidth(stringToUse, stringLeftover, leftoverWidth, fontName);

      //if nothing will fit within the given space, set a newline, reset the width and get the largest piece of text that fits within the full width.
      if (response!=0)
        {
          wrappedNewLine=true;
          leftoverWidth=containerWidth;
          if (response==2)
            {
              //there isn't enough room to fit another letter on. Add a new line.
              fmt.back().back().setNewline(true);
              getLargestStringToFitInWidth(stringToUse, stringLeftover, leftoverWidth, fontName);
            }
        }
      getTextDimensions(stringToUse, fontName, textWidth, textHeight);

      //get coordinates
      getNextFormattedTextCoordinate(fmt, xPos, yPos);

      //apply heightFactor. It's out of 16ths.
      textHeight *= heightFactor;
      textHeight /= 16;

      //Now set up a new FormattedText.
      if (stringToAdd.empty())
        f.setText("");
      else
        f.setText(stringToUse);
      f.setColorIndex(colorIndex);
      f.setHelpIndex(helpRef);
      f.setPosition(xPos, yPos);
      f.setDimensions(textWidth,textHeight);

      if (!wrappedNewLine)
        {
          f.setNewline(forceNewLine);
        }
      else
        {
          numLinesAdded++;
          f.setNewline(true);
        }

      //add a new line if you need to:
      //1) The text Container is empty.
      //2) The last line has a new line.
      if (fmt.size()==0||fmt.back().back().getNewline())
        fmt.push_back(newLineOfFmt);

      //finally push the FormattedText to the stack.
      fmt.back().push_back(f);

      //Update stringToUse. if the text is blank, bail
      stringToUse=stringLeftover;
      if (stringToUse.empty())
        done=true;

      //check the number of lines against the limit.
      if (lineLimit > -1 && numLinesAdded >= lineLimit)
        done=true;
    }
}

//get the length of the last line.
uint16_t shippingHandler::getWidthOfLastLine(vector<vector<FormattedText> >& fmt)
{
  vector <FormattedText> :: iterator fmtIter, fmtIterEnd;

  if (fmt.size()==0)
    return 0;

  if ((fmt.begin())==(fmt.end()))
    return 0;

  uint16_t ans=0;

  //exception: If the last line ends with a newline, ans = 0
  if (fmt.back().back().getNewline())
    return 0;

  //count up the width of each FormattedText.
  fmtIter=fmt.back().begin();
  fmtIterEnd=fmt.back().end();

  ans=0;
  while (fmtIter!=fmtIterEnd)
    {
      ans+=fmtIter->getWidth();
      fmtIter++;
    }
  return ans;
}

//getting the biggest string that will fit.
//return 0 if string completely fits.
//return 1 if some of string can fit on the line.
//return 2 if none of the string can fit on the line.
int shippingHandler::getLargestStringToFitInWidth(string & stringToUse, string & stringLeftover, uint16_t leftoverWidth, const string & fontName)
{
  if (stringToUse.empty())
    {
      stringLeftover="";
      return 0;
    }

  //1) If the first character doesn't fit, return false.
  string testString=stringToUse.substr(0,1);
  uint16_t stringWidth, stringHeight;

  getTextDimensions(testString, fontName, stringWidth, stringHeight);

  if (stringWidth>leftoverWidth)
    {
      stringLeftover="";
      return 2;
    }

  //2) If the entire string fits, use the whole string, no leftover, return true.
  getTextDimensions(stringToUse, fontName, stringWidth, stringHeight);

  if (stringWidth<=leftoverWidth)
    {
      stringLeftover="";
      return 0;
    }

  //3) Otherwise, do a binary search to find the largest string that fits.
  //   a) Low bound = 0, Up bound = length of string. First guess = halfway.
  int lowerBound=0, upperBound=stringToUse.size(), guess=(lowerBound+upperBound)/2, precision=1;

  //   b) until lowbound +1 = upbound
  while (upperBound-lowerBound>precision)
    {
      //1) get length of substring
      testString=stringToUse.substr(0,guess);
      getTextDimensions(testString, fontName, stringWidth, stringHeight);
      //2) if it's exact, set up and low bound to guess.
      if (stringWidth==leftoverWidth)
        {
          lowerBound=guess;
          upperBound=guess;
        }
      //3) if it's too long, upbound = guess
      else if (stringWidth > leftoverWidth)
        upperBound=guess;
      //4) if it's too short, lowbound = guess
      else if (stringWidth < leftoverWidth)
        lowerBound=guess;

      guess=(lowerBound+upperBound)/2;
    }

  //   c) lowbound is the length of the string to use.
  //      stringToUse = [0,lowbound)
  //      stringLeftover = [lowbound, length)
  stringLeftover=stringToUse.substr(lowerBound);
  stringToUse=stringToUse.substr(0,lowerBound);
  return 1;
}

//returns the coordinates of the next formatted text to be added to the given vector.
void shippingHandler::getNextFormattedTextCoordinate(vector<vector<FormattedText> > & fmt, int & xPos, int & yPos)
{
  xPos=0;
  yPos=0;

  //3 situations:

  //1) There is no text.
  if (fmt.size()==0)
    return;

  int heightOfThisLine=0;
  bool lastLineIsFull=fmt.back().back().getNewline();
  bool thisIsTheFinalLine;

  //2) The last line is full.
  //3) The last line is not full.

  //We're going to iterate through the lines, summing the height of each line except the final.
  //If we're in scenario 2, we'll add the height of the final line but not its length. The text should start at a new line.
  //If we're in scenario 3, we'll sum the length of the final line but not the height. The text should start at the end of this line.

  vector <FormattedText> :: iterator fmtLineIter, fmtLineIterEnd;
  vector <vector <FormattedText> > :: iterator fmtIter=fmt.begin(), fmtFinalLine=fmt.end()-1;

  while (fmtIter!=fmt.end())
    {
      fmtLineIter=fmtIter->begin();
      fmtLineIterEnd=fmtIter->end();
      thisIsTheFinalLine=(fmtIter==fmtFinalLine);
      heightOfThisLine=0;
      while (fmtLineIter!=fmtLineIterEnd)
        {
          if (fmtLineIter->getHeight()>heightOfThisLine)
            heightOfThisLine=fmtLineIter->getHeight();
          if (thisIsTheFinalLine && !lastLineIsFull)
            xPos+=fmtLineIter->getWidth();
          fmtLineIter++;
        }
      if (!thisIsTheFinalLine ||
          (thisIsTheFinalLine && lastLineIsFull))
        yPos+=heightOfThisLine;
      fmtIter++;
    }
}
