//-----------------------------------------------------------------------------
/*
 * Description: This file contains the class AxOsdMailItem
 *
 * See the file README in the main directory for a description of
 * this software, copyright information, and how to reach the author.
 *
 * Author: alex
 * Date:   17.11.2003
 *
 * Last modfied:
 *   $Author: alex $
 *   $Date: 2004/01/24 18:08:38 $
 *   $Revision: 1.9 $
 */
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
//     includes
//-----------------------------------------------------------------------------
//----- local -----------------------------------------------------------------
#include "AxOsdMailItem.h"

//----- multiple defs of swap() workaround ------------------------------------
#include "AxPluginGlobals.h"

//----- qt --------------------------------------------------------------------
//----- CORBA -----------------------------------------------------------------
//----- ICE -------------------------------------------------------------------
//----- C++ -------------------------------------------------------------------
#include <algorithm>

//----- C ---------------------------------------------------------------------
#include <ctype.h>

//----- AxLib -----------------------------------------------------------------
#include <Ax/Tools/Trace.h>
// #include <Ax/Tools/TraceOn.h>
#include <Ax/Mail/Mail.h>
#include <Ax/Mail/MailPart.h>

//----- vdr -------------------------------------------------------------------
#include <vdr/i18n.h>

//=============================================================================
//     using namespaces
//=============================================================================
using namespace Ax;

//=============================================================================
//     class AxOsdMailItem
//=============================================================================

//-------------------------------------------------------------------------
//     AxOsdMailItem::AxOsdMailItem()
//-------------------------------------------------------------------------
AxOsdMailItem::AxOsdMailItem( int X
                            , int Y
                            , int W
                            , int H
                            , eDvbColor theFgColor
                            , eDvbColor theBgColor
                            , eDvbFont  theFont
                            , int       theJumpSize
                            )
              : myMail    (0)
              , myText    (0)
              , myMode    (MVM_FLOWED)
              , myXPos    (X)
              , myYPos    (Y)
              , myWidth   (W)
              , myHeight  (H)
              , myOffset  (0)
              , myJumpSize(theJumpSize)
              , fgColor   (theFgColor )
              , bgColor   (theBgColor )
              , myFont    (theFont    )
{
  wsdebug(("> AxOsdMailItem::AxOsdMailItem() x: %3d  y: %3d  w: %3d  h: %3d  NumLines: %3d  ofs: %3d"
         , myXPos, myYPos, myWidth, myHeight, myText.size(), myOffset
         ));

  wsdebug(("< AxOsdMailItem::AxOsdMailItem() x: %3d  y: %3d  w: %3d  h: %3d  NumLines: %3d  ofs: %3d"
         , myXPos, myYPos, myWidth, myHeight, myText.size(), myOffset
         ));
} // AxOsdMailItem::AxOsdMailItem()

//-------------------------------------------------------------------------
//     AxOsdMailItem::~AxOsdMailItem()
//-------------------------------------------------------------------------
AxOsdMailItem::~AxOsdMailItem()
{
} // AxOsdMailItem::~AxOsdMailItem()

//-------------------------------------------------------------------------
//     AxOsdMailItem::Height()
//-------------------------------------------------------------------------
int AxOsdMailItem::Height(void)
{
  return myHeight;
} // AxOsdMailItem::Height()

//-------------------------------------------------------------------------
//     AxOsdMailItem::Clear()
//-------------------------------------------------------------------------
void AxOsdMailItem::Clear(void)
{
  Interface->Fill(myXPos, myYPos, myWidth, myHeight, bgColor);
} // AxOsdMailItem::Clear()

//-------------------------------------------------------------------------
//     AxOsdMailItem::setMail()
//-------------------------------------------------------------------------
void AxOsdMailItem::setMail(Ax::Mail::Mail *theMail)
{
  wsdebug(("> AxOsdMailItem::setMail() Mail: %p", theMail));

  myOffset = 0;
  myMail   = theMail;
  myText.clear();

  if (myMail == 0)
  {
    wsdebug(("< AxOsdMailItem::setMail() Mail: %p", theMail));
    return;
  } //if

  Mail::MailPartList aPartList = myMail->getFlatPartList(false);

  //----- set header line -----
  if (aPartList.size() > 1)
  {
    char *szLine;
    asprintf( &szLine, "---[%s]----------------------------------------", tr("Multipart mail"));
    myText.push_back(AxLine(szLine, true));
    free(szLine);
  } // if

  //----- iterate through the parts of the mail -----
  for (Mail::MailPartList::const_iterator  anIter  = aPartList.begin();
                                          anIter != aPartList.end();
                                        ++anIter)
  {
    const Mail::MailPart *aPart = *anIter;

#if BUILD_RELEASE
    if (aPartList.size() > 1)
#endif
    {
      char *szLine;
#if 0
      asprintf( &szLine
              , "---[%s %s: %s/%s/%s %s: %ld  %s: %ld]\n"
              , "P", aPart->getPartName().c_str()
              ,      aPart->getTypeStr().c_str()
              ,      aPart->getEncStr().c_str()
              ,      aPart->getSubType().c_str()
              , "L", aPart->getCountLines()
              , "B", aPart->getCountBytes()
              );
#else
      asprintf( &szLine
              , "---[%s %s: %s/%s/%s]----------------------------------------"
              , "P", aPart->getPartName().c_str()
              ,      aPart->getTypeStr().c_str()
              ,      aPart->getEncStr().c_str()
              ,      aPart->getSubType().c_str()
              );
#endif
      myText.push_back(AxLine(szLine, true));
      free(szLine);
      szLine = 0;
    } // if

#if BUILD_DEVEL
    {
      std::string aParamStr;
      Ax::Mail::String2StringMap aMap = aPart->getParams();
      for (Ax::Mail::String2StringMap::const_iterator aParamIter  = aMap.begin();
                                                      aParamIter != aMap.end();
                                                    ++aParamIter)
      {
        if (aParamStr.length()) aParamStr += ";";

        aParamStr += aParamIter->first;

        if (aParamIter->second.length())
        {
          aParamStr += "=" + aParamIter->second;
        } // if
      } // for

      if (aParamStr.length() > 0)
      {
        aParamStr = "---[" + aParamStr + "]";
        myText.push_back(AxLine(aParamStr, true));
      } // if
    }
#endif
    LineCltn aPartLines = getPartText(aPart);
    myText.insert(myText.end(), aPartLines.begin(), aPartLines.end());
  } // for

  wsdebug(("< AxOsdMailItem::setMail() Mail: %p", theMail));
} // AxOsdMailItem::setMail()

//-------------------------------------------------------------------------
//     AxOsdMailItem::setViewMode()
//-------------------------------------------------------------------------
void AxOsdMailItem::setViewMode(MailViewMode theMode)
{
  myMode = (theMode >= MVM_FIRST && theMode <= MVM_LAST) ? theMode : MVM_SMALL;
  setMail(myMail);
} // AxOsdMailItem::setViewMode()

//-------------------------------------------------------------------------
//     AxOsdMailItem::getViewMode()
//-------------------------------------------------------------------------
AxOsdMailItem::MailViewMode AxOsdMailItem::getViewMode() const
{
  return myMode;
} // AxOsdMailItem::getViewMode()

//-------------------------------------------------------------------------
//     AxOsdMailItem::getViewModeName()
//-------------------------------------------------------------------------
const char *AxOsdMailItem::getViewModeName() const
{
  switch (myMode)
  {
    case MVM_HARD  : return tr("hard"  );
    case MVM_QUOTE : return tr("quotes");
    case MVM_FLOWED: return tr("flowed");
    case MVM_SMALL : return tr("small" );
  } // switch
  return "UNKOWN";
} // AxOsdMailItem::getViewModeName()

//-------------------------------------------------------------------------
//     AxOsdMailItem::ProcessKey()
//-------------------------------------------------------------------------
eOSState AxOsdMailItem::ProcessKey(eKeys Key)
{
  switch (Key)
  {
    case kLeft|k_Repeat:
    case kLeft:
    case kUp|k_Repeat:
    case kUp:
    {
      ScrollUp(NORMALKEY(Key) == kLeft);
      break;
    }
    case kRight|k_Repeat:
    case kRight:
    case kDown|k_Repeat:
    case kDown:
    {
      ScrollDown(NORMALKEY(Key) == kRight);
      break;
    }
    default:
    {
      return osUnknown;
    }
  } // switch
  return osContinue;
} // AxOsdMailItem::ProcessKey()

//-------------------------------------------------------------------------
//     AxOsdMailItem::CanScrollUp()
//-------------------------------------------------------------------------
bool AxOsdMailItem::CanScrollUp(void)
{
  return myOffset > 0;
} // AxOsdMailItem::CanScrollUp()

//-------------------------------------------------------------------------
//     AxOsdMailItem::CanScrollDown()
//-------------------------------------------------------------------------
bool AxOsdMailItem::CanScrollDown(void)
{
  return myHeight + myOffset < int(myText.size());
} // AxOsdMailItem::CanScrollDown()

//-------------------------------------------------------------------------
//     AxOsdMailItem::ScrollUp()
//-------------------------------------------------------------------------
void AxOsdMailItem::ScrollUp(bool Page)
{
  if (CanScrollUp())
  {
    Clear();
    myOffset = max(myOffset - (Page ? myHeight : myJumpSize), 0);
    Display();
  }
  cStatus::MsgOsdTextItem(NULL, true);
} // AxOsdMailItem::ScrollUp()

//-------------------------------------------------------------------------
//     AxOsdMailItem::ScrollDown()
//-------------------------------------------------------------------------
void AxOsdMailItem::ScrollDown(bool Page)
{
  if (CanScrollDown())
  {
    Clear();
    myOffset = min(myOffset + (Page ? myHeight : myJumpSize), myText.size() - myHeight);
    Display();
  }
  cStatus::MsgOsdTextItem(NULL, false);
} // AxOsdMailItem::ScrollDown()

//-----------------------------------------------------------------------------
//     AxOsdMailItem::getQuotes()
//-----------------------------------------------------------------------------
std::string AxOsdMailItem::getQuotes(const std::string &theStr)
{
  if (theStr.length() == 0 || theStr[0] != '>')
  {
    return std::string("");
  } // if

  std::string aQuote = theStr.substr(0, theStr.find_first_not_of("> "));

  if (int(aQuote.length()) > Setup.OSDwidth / 2)
  {
    // ignore lines, that start with more than n characters of "> "
    return std::string("");
  } // if

  std::string::size_type aLastQPos = aQuote.find_last_not_of(' ');
  if (aLastQPos != std::string::npos)
  {
    if (aLastQPos + 1 == aQuote.length())
    {
      aQuote  = aQuote.substr(0, aLastQPos + 1);
    }
    else
    {
      aQuote  = aQuote.substr(0, aLastQPos + 2);
    } // if
  } // if

  return aQuote;
} // AxOsdMailItem::getQuotes()

//-------------------------------------------------------------------------
//     AxOsdMailItem::splitText()
//-------------------------------------------------------------------------
AxOsdMailItem::LineCltn AxOsdMailItem::splitText(const std::string &theStr)
{
  wsdebug(("> AxOsdMailItem::splitText()"));
  LineCltn aDst;

  std::string::size_type aPosStart = 0;
  std::string::size_type aPosEnd   = std::string::npos;

  for (; aPosStart != std::string::npos; aPosStart = aPosEnd)
  {
    aPosEnd  = theStr.find_first_of('\n', aPosStart);

    std::string  aLine("");
    if (aPosEnd != std::string::npos)
    {
      aLine = theStr.substr(aPosStart, aPosEnd - aPosStart);
      aPosEnd++;
    }
    else
    {
      aLine = theStr.substr(aPosStart);
    } // if

    //----- remove trailing CR/LF-chars -----
    std::string::size_type  aPos = aLine.find_last_not_of("\r\n");
    if (aPos != std::string::npos) aLine = aLine.substr(0, aPos + 1);
    else                           aLine = "";

    //----- process line -----
    aDst.push_back(aLine);
  } // while

  wsdebug(("< AxOsdMailItem::splitText() NumLines: %d", aDst.size()));
  return aDst;
} //  AxOsdMailItem::splitText()

//-------------------------------------------------------------------------
//     AxOsdMailItem::getFlowedText()
//-------------------------------------------------------------------------
AxOsdMailItem::LineCltn AxOsdMailItem::getFlowedText(const LineCltn &theSrc)
{
  wsdebug(("> AxOsdMailItem::getFlowedText()"));

  AxOsdMailItem::LineCltn aDst;
  std::string             aParagraph = "";
  std::string             aQuotePrev = "";

  //----- iterate through all lines -----
  for (LineCltn::const_iterator anIter  = theSrc.begin();
                                anIter != theSrc.end();
                              ++anIter)
  {
    std::string aLine = (*anIter).Text;

    //----- signature separator or empty line -----
    if (aLine == "-- " || aLine.length() == 0)
    {
      if (aParagraph.length() > 0)
      {
        aDst.push_back(AxLine(aParagraph));
        aParagraph = "";
        aQuotePrev = "";
      } // if
      aDst.push_back(AxLine(aLine));
      continue;
    } // if

    // removed stuffed space
    bool fStuffed = aLine.length() > 0 && aLine[0] == ' ';
    if (fStuffed)
    {
      aLine = aLine.erase(0, 1);
    } // if

    //----- check quote -----
    std::string aQuoteAct = getQuotes(aLine);
    std::string aContent  = aLine.substr(aQuoteAct.length(), aLine.length() - aQuoteAct.length());

    if (aQuotePrev != aQuoteAct)
    {
      // quote has changed

      if (aParagraph.length() > 0)
      {
        // paragraph present -> add to Dst
        aDst.push_back(aParagraph);
        aParagraph = "";
      } // if

      if (aContent.length() > 0 && aContent[aContent.length() - 1] == ' ')
      {
        // flowed line -> start new paragraph
        aParagraph = aLine;
      }
      else
      {
        // fixed line -> append directly
        aDst.push_back(AxLine(aLine));
      } // if
    }
    else
    {
      // quote is equal

      // add content or complete line to paragraph
      if (aParagraph.length() == 0)
      {
        aParagraph = aLine;
      }
      else
      {
        aParagraph += aContent;
      } // if

      if (aContent.length() > 0 && aContent[aContent.length() - 1] == ' ')
      {
        // flowed line
        // already added to the paragraph
      }
      else
      {
        // fixed line
        // paragraph present -> add to Dst
        aDst.push_back(aParagraph);
        aParagraph = "";

      } // if
    } // if

    aQuotePrev = aQuoteAct;
  } // for

  wsdebug(("< AxOsdMailItem::getFlowedText()"));
  return aDst;
} // AxOsdMailItem::getPartTextFlowed()

//-------------------------------------------------------------------------
//     AxOsdMailItem::improveWrap()
//-------------------------------------------------------------------------
AxOsdMailItem::LineCltn AxOsdMailItem::improveWrap(const LineCltn &theSrc)
{
  wsdebug(("> AxOsdMailItem::improveWrap()"));

  AxOsdMailItem::LineCltn aDst;
  std::string             aParagraph = "";
  std::string             aQuotePrev = "";

  bool fFirst = true;

  //----- iterate through all lines -----
  for (LineCltn::const_iterator anIter  = theSrc.begin();
                                anIter != theSrc.end();
                              ++anIter)
  {
    std::string aLine = (*anIter).Text;

    //----- check quote -----
    std::string aQuoteAct  = getQuotes(aLine);
    std::string aContent   = aLine.substr(aQuoteAct.length(), aLine.length() - aQuoteAct.length());

    if (aQuotePrev != aQuoteAct)
    {
      aDst.push_back(aParagraph);
      aParagraph = "";

      aParagraph = aLine;
    }
    else
    {
      std::string::size_type aPosLast  = aParagraph.find_last_not_of (' ');
      std::string::size_type aPosFirst = aContent  .find_first_not_of(' ');

      if ( aPosLast  != std::string::npos
        && aPosFirst != std::string::npos
        && strchr("abcdefghijklmnopqrstuvwxyzäöüß", aParagraph[aPosLast ])
        && strchr("abcdefghijklmnopqrstuvwxyzäöüß", aContent  [aPosFirst])
         )
      {
        if (aParagraph[0] != ' ' && aParagraph.length() > 0 && aParagraph[aParagraph.length() - 1] != ' ')
        {
          aParagraph += " ";
        } // if
        aParagraph += aContent;
      }
      else
      {
        if (aParagraph.length() > 0 || !fFirst)
        {
          aDst.push_back(aParagraph);
          aParagraph = "";
        } // if

        aParagraph = aLine;
      } // if
    } // if

    aQuotePrev = aQuoteAct;
    fFirst = false;
  } // for

  wsdebug(("< AxOsdMailItem::improveWrap()"));
  return aDst;
} // AxOsdMailItem::improveWrap()

//-------------------------------------------------------------------------
//     AxOsdMailItem::wrapText()
//-------------------------------------------------------------------------
AxOsdMailItem::LineCltn AxOsdMailItem::wrapText(const LineCltn &theSrc)
{
  wsdebug(("> AxOsdMailItem::wrapText() NumLines: %d", theSrc.size()));
  LineCltn aDst;

  // this is borrowed and stripped down from interface/osd/osdbase
  int aScreenWidth = (Setup.OSDwidth - 2) * cOsd::CellWidth();

#if VDRVERSNUM >= 10300
  const cFont *aFontPtr = cFont::GetFont(myFont);
#else
  cFont aFont(myFont);
  cFont *aFontPtr = &aFont;
#endif

  //----- iterate through all lines -----
  for (LineCltn::const_iterator anIter  = theSrc.begin();
                                anIter != theSrc.end();
                              ++anIter)
  {
    std::string aLine = (*anIter).Text;
    bool        fSep  = (*anIter).fSep;

    std::string aQuoteChars  = getQuotes(aLine);
    std::string aCntntChars  = aLine.substr(aQuoteChars.length(), aLine.length() - aQuoteChars.length());

    //----- get width of quote ------------
    int aQuoteWidth(0);
    for (std::string::const_iterator aQuoteIter  = aQuoteChars.begin();
                                     aQuoteIter != aQuoteChars.end();
                                   ++aQuoteIter)
    {
      aQuoteWidth += aFontPtr->Width(*aQuoteIter);
    } // for

    //----- iterate within line -----------
    do
    {
      std::string::iterator aPosLastSpace = aCntntChars.end();
      std::string::iterator aPosLastDelim = aCntntChars.end();

      std::string aNewLine ("");
      int         aDstWidth( 0);
      bool        fWrapped = false;

      //----- process up to osd width -----
      for (std::string::iterator aCharIter  = aCntntChars.begin();
                                 aCharIter != aCntntChars.end() && !fWrapped;
                               ++aCharIter)
      {
        char aChar  = *aCharIter;
        aDstWidth  += aFontPtr->Width(aChar);

        if (aDstWidth + aQuoteWidth > aScreenWidth)
        {
          //----- line break necessary -----

          std::string::iterator aPosWrap = aCharIter;
          if (aPosLastSpace != aCntntChars.end())
          {
            aPosWrap = aPosLastSpace + 1;
          }
          else if (aPosLastDelim != aCntntChars.end())
          {
            aPosWrap = aPosLastDelim + 1;
          }
          else
          {
            aPosWrap = aCharIter;
          } // if

          aNewLine = std::string(aCntntChars.begin(), aPosWrap);
          if (fSep)
          {
            // cut (don't wrap) 'separator' lines
            aCntntChars = "";
          }
          else
          {
            aCntntChars = std::string(aPosWrap, aCntntChars.end());
          } // if
          fWrapped = true;
        }
        else if (aChar == ' '              ) aPosLastSpace = aCharIter;
        else if (strchr("-.,:;!?_/", aChar)) aPosLastDelim = aCharIter;
      } // for

      if (!fWrapped)
      {
        aNewLine    = aCntntChars;
        aCntntChars = "";
      } // if

      aDst.push_back(AxLine(aQuoteChars + aNewLine, fSep));

      //----- if not using 'continued quotes' -> delete quote-string/width
      if (myMode < MVM_QUOTE)
      {
        aQuoteChars = "";
        aQuoteWidth =  0;
      } // if
    }
    while (aCntntChars.length() > 0);
  } // for

  wsdebug(("< AxOsdMailItem::wrapText()"));
  return aDst;
} // AxOsdMailItem::wrapText()

//-------------------------------------------------------------------------
//     AxOsdMailItem::getPartText()
//-------------------------------------------------------------------------
AxOsdMailItem::LineCltn AxOsdMailItem::getPartText(const Ax::Mail::MailPart *thePart)
{
  LineCltn aDst;

  switch (thePart->getType())
  {
    case TYPETEXT:
    {
      aDst = splitText(thePart->getBodyText());

      if (myMode >= MVM_FLOWED && thePart->getParam("FORMAT") == "FLOWED")
      {
        aDst = getFlowedText(aDst);
      } // if

      if (myMode >= MVM_SMALL)
      {
        aDst = improveWrap(aDst);
      } // if

      aDst = wrapText(aDst);
      break;
    }

    default:
    {
      // unable to display parts != text
      break;
    }
  } // if

  return aDst;
} // AxOsdMailItem::getPartText()

//-------------------------------------------------------------------------
//     AxOsdMailItem::Display()
//-------------------------------------------------------------------------
// this is essentially the same method as in cOsdTextItem
// with added comments to help me understanding what it
// does, if I have a look at this method in the future.
void AxOsdMailItem::Display(int, eDvbColor FgColor, eDvbColor BgColor)
{
#if 0
  wsdebug(("> AxOsdMailItem::Display()        x: %3d  y: %3d  w: %3d  h: %3d  NumLines: %3d ofs: %3d"
         , myXPos, myYPos, myWidth, myHeigth, myText.size(), myOffset
         ));
#endif

  // set the desired font and save the current font
  eDvbFont oldFont = Interface->SetFont(myFont);

  int aNumLines = myText.size();

  //----- draw the mail text -----
  for (int anIndex = myOffset; anIndex < aNumLines && anIndex < myOffset + myHeight; ++anIndex)
  {
    std::string aLine    = myText[anIndex].Text;
    eDvbColor   aFgColor = myText[anIndex].fSep ? clrScrolLine : fgColor;

    Interface->Write(myXPos, myYPos + anIndex - myOffset, aLine.c_str(), aFgColor, bgColor);
  } // for

  //----- draw scroll-bar -----
  int        xcoord     = myXPos + myWidth - 0;
  eDvbColor  ColorPtrFg = bgColor;
  eDvbColor  ColorPtrBg = clrScrolLine;

  int aHeightTotal      = myHeight - 2;
  int aHeigthScrHandle  = aNumLines <= myHeight
                        ? aHeightTotal
                        : int(double(myHeight) / double(aNumLines) * double(aHeightTotal))
                        ;

  int aScrOfs           = aNumLines <= myHeight
                        ? 0
                        : int(double(myOffset) / double(aNumLines) * double(aHeightTotal))
                        ;

  for (int ycoord = myYPos + 1; ycoord <= myYPos + 1 + aHeightTotal; ++ycoord)
  {
    if (myYPos + 1 + aScrOfs <= ycoord && ycoord <= myYPos + 1 + aScrOfs + aHeigthScrHandle)
    {
      Interface->Write(xcoord, ycoord, "  ",  ColorPtrFg, ColorPtrBg);
    }
    else
    {
      Interface->Write(xcoord, ycoord, "  ", bgColor, bgColor);
    }
  } // for

  if (CanScrollUp())    Interface->Write(xcoord, myYPos               , "^" , ColorPtrFg, ColorPtrBg);
  else                  Interface->Write(xcoord, myYPos               , "  ", ColorPtrBg, ColorPtrBg);

  if (CanScrollDown())  Interface->Write(xcoord, myYPos + myHeight - 1, "v" , ColorPtrFg, ColorPtrBg);
  else                  Interface->Write(xcoord, myYPos + myHeight - 1, "  ", ColorPtrFg, ColorPtrBg);

  // restore the previous font
  Interface->SetFont(oldFont);

#if 0
  wsdebug(("< AxOsdMailItem::Display()        x: %3d  y: %3d  w: %3d  h: %3d  NumLines: %3d ofs: %3d  Can-ScrollUp: %d ScrollDown: %d"
         , myXPos, myYPos, myWidth, myHeigth, myText.size(), myOffset, CanScrollUp(), CanScrollDown()
         ));
#endif
} // AxOsdMailItem::Display()
