//-----------------------------------------------------------------------------
/*
 * Description: This file contains the class AxMailWrapper
 *
 * 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:   28.06.2004
 *
 * Last modfied:
 *   $Author: alex $
 *   $Date: 2007-08-14 09:40:13 +0200 (Di, 14 Aug 2007) $
 *   $Revision: 1.2 $
 */
//-----------------------------------------------------------------------------

//=============================================================================
//     includes
//=============================================================================

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

//----- qt --------------------------------------------------------------------
//----- CORBA -----------------------------------------------------------------
//----- ICE -------------------------------------------------------------------
//----- C++ -------------------------------------------------------------------
//----- C ---------------------------------------------------------------------
//----- vdr -------------------------------------------------------------------
#include <vdr/tools.h>

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

//----- local plugin ----------------------------------------------------------
#include "AxMailWrapper.h"
#include "AxVdrTools.h"

//----- locals ----------------------------------------------------------------

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

//=============================================================================
//     class AxMailWrapper
//=============================================================================

//-------------------------------------------------------------------------
//     AxMailWrapper::AxMailWrapper()
//-------------------------------------------------------------------------
AxMailWrapper::AxMailWrapper(const cFont *theFont, int theWidth)
             : myFont (theFont )
             , myWidth(theWidth)
{
  wsdebug(("> AxMailWrapper::AxMailWrapper() Font: %p  Width: %3d", myFont, myWidth));
  wsdebug(("< AxMailWrapper::AxMailWrapper() Font: %p  Width: %3d", myFont, myWidth));
} // AxMailWrapper::AxMailWrapper()

//-------------------------------------------------------------------------
//     AxMailWrapper::~AxMailWrapper()
//-------------------------------------------------------------------------
AxMailWrapper::~AxMailWrapper()
{
  wsdebug(("> AxMailWrapper::~AxMailWrapper() Font: %p  Width: %3d", myFont, myWidth));
  wsdebug(("< AxMailWrapper::~AxMailWrapper() Font: %p  Width: %3d", myFont, myWidth));
} // AxMailWrapper::~AxMailWrapper()

//-------------------------------------------------------------------------
//     AxMailWrapper::getMailText()
//-------------------------------------------------------------------------
std::string AxMailWrapper::getMailText(Ax::Mail::Mail *theMail, WrapMode theWrapMode)
{
  wsdebug(("> AxMailWrapper::getMailText() Font: %p  Width: %3d  theMail: %p", myFont, myWidth, theMail));
  if (theMail == 0)
  {
    wsdebug(("< AxMailWrapper::getMailText() Font: %p  Width: %3d  theMail: %p", myFont, myWidth, theMail));
    return std::string("");
  } // if

  myWrapMode = theWrapMode;

  bool fHasOSDProps = myFont != 0 && myWidth != 0;

  if (!fHasOSDProps)
  {
    myWrapMode = WM_NONE;
  } // if

  LineCltn  myText;

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

  if (aPartList.size() > 1)
  {
    std::string aLine = Ax::Tools::String::sprintf(
          fHasOSDProps
          ? "---[%s]---------------------------------------------------------------------------------------"
          : "---[%s]----------------------"
        , tr("Multipart mail")
        );
    myText.push_back(AxLine(cutLine(aLine), true));
  } // 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 (
#if 0
        true ||
#endif
        aPartList.size() > 1)
    {
      std::string aLine = Ax::Tools::String::sprintf(
                fHasOSDProps
              ? "---[%s %s: %s/%s/%s/%s]-----------------------------------------------------------------"
              : "---[%s %s: %s/%s/%s/%s]---"
              , tr("Part")
              , aPart->getPartName().c_str()
              , aPart->getTypeStr().c_str()
              , aPart->getEncStr().c_str()
              , aPart->getSubType().c_str()
              , aPart->getParam("charset").c_str()
              );

      myText.push_back(AxLine(cutLine(aLine), true));
    } // if

    LineCltn aPartLines = getPartText(aPart);
    myText.insert(myText.end(), aPartLines.begin(), aPartLines.end());
  } // for
#if 0
  std::string aLine = fHasOSDProps
                    ? std::string("---[EOM]-----------------------------------------------------------------------------------------------\n")
                    : std::string("---[EOM]-----\n")
                    ;
  myText.push_back(AxLine(cutLine(aLine), true));
#endif

  std::string aBodyText;
  for (LineCltn::iterator anIter = myText.begin(); anIter != myText.end(); ++anIter)
  {
    aBodyText += anIter->Text + "\n";
  } // if

  wsdebug(("< AxMailWrapper::getMailText()"));
  return aBodyText;
} // AxMailWrapper::getMailText()

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

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

  if (int(aQuote.length()) > myWidth / 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;
} // AxMailWrapper::getQuotes()

//-------------------------------------------------------------------------
//     AxMailWrapper::cutLine()
//-------------------------------------------------------------------------
std::string AxMailWrapper::cutLine(const std::string theSrc)
{
  if (myWidth == 0 || myFont == 0)
  {
    wswarn(("AxMailWrapper::cutLine() myFont: %p  myWidth: %d unable to cut!", myFont, myWidth));
    return theSrc;
  } // if

  wsdebug(("> AxMailWrapper::cutLine() Src: '%s'", theSrc.c_str()));

  int aLineWidth = 0;

  std::string::const_iterator anIter = theSrc.begin();
  for (; anIter != theSrc.end() && aLineWidth < myWidth; ++anIter, aLineWidth += myFont->Width(*anIter))
    ;

  // go on character back
  if (aLineWidth > myWidth && anIter != theSrc.begin())
  {
    --anIter;
  } // if

  std::string aDst(theSrc.begin(), anIter);

  wsdebug(("< AxMailWrapper::cutLine() Dst: %s", aDst.c_str()));
  return aDst;
} // AxMailWrapper::splitLine()

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

  std::string::size_type aStrLen   = theStr.length();
  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("\r\n", aPosStart);

    std::string  aLine("");
    if (aPosEnd != std::string::npos)
    {
      aLine = theStr.substr(aPosStart, aPosEnd - aPosStart);
      if      (aPosEnd + 1 < aStrLen && theStr[aPosEnd] == '\r' && theStr[aPosEnd + 1] == '\n') aPosEnd += 2;
      else if (aPosEnd + 1 < aStrLen && theStr[aPosEnd] == '\n' && theStr[aPosEnd + 1] == '\r') aPosEnd += 2;
      else                                                                                      aPosEnd += 1;
      if (aPosEnd >= aStrLen) aPosEnd = std::string::npos;
    }
    else
    {
      aLine = theStr.substr(aPosStart);
    } // if

    aDst.push_back(aLine);
  } // while

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

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

  AxMailWrapper::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.empty())
    {
      if (!aParagraph.empty())
      {
        aDst.push_back(AxLine(aParagraph));
        aParagraph = "";
        aQuotePrev = "";
      } // if
      aDst.push_back(AxLine(aLine));
      continue;
    } // if

    // removed stuffed space
    bool fStuffed = !aLine.empty() && 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.empty())
      {
        // paragraph present -> add to Dst
        aDst.push_back(aParagraph);
        aParagraph = "";
      } // if

      if (!aContent.empty() && 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.empty())
      {
        aParagraph = aLine;
      }
      else
      {
        aParagraph += aContent;
      } // if

      if (!aContent.empty() && 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(("< AxMailWrapper::getFlowedText()"));
  return aDst;
} // AxMailWrapper::getFlowedText()

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

  AxMailWrapper::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)
    {
      if (anIter != theSrc.begin()) // don't insert an empty line if the first line of the mail is a quote
      {
        aDst.push_back(aParagraph);
      } // if
      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.empty() && aParagraph[0] != ' ' && aParagraph[aParagraph.length() - 1] != ' ')
        {
          aParagraph += " ";
        } // if
        aParagraph += aContent;
      }
      else
      {
        if (!aParagraph.empty() || !fFirst)
        {
          aDst.push_back(aParagraph);
          aParagraph = "";
        } // if

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

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

  // if theres still data -> append to dst
  if (!aParagraph.empty())
  {
    aDst.push_back(aParagraph);
  } // if

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

//-------------------------------------------------------------------------
//     AxMailWrapper::wrapText()
//-------------------------------------------------------------------------
AxMailWrapper::LineCltn AxMailWrapper::wrapText(const LineCltn &theSrc)
{
  if (myFont == 0 || myWidth == 0)
  {
    wswarn(("AxMailWrapper::wrapText() myFont: %p  myWidth: %d unable to wrap!", myFont, myWidth));
    return theSrc;
  } // if

  wsdebug(("> AxMailWrapper::wrapText() NumLines: %d", theSrc.size()));
  LineCltn aDst;

  //----- 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 += myFont->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  += myFont->Width(aChar);

        if (aDstWidth + aQuoteWidth > myWidth)
        {
          //----- 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 (myWrapMode < WM_QUOTE)
      {
        aQuoteChars = "";
        aQuoteWidth =  0;
      } // if
    }
    while (!aCntntChars.empty());
  } // for

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

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

  switch (thePart->getType())
  {
    case Mail::BT_TEXT:
    {
      std::string aStr = thePart->getBodyText(); // quoted printable / base64 already handled in getBodyText()

      //----- convert to OSD-CharSet -----
      Ax::Mail::Tools::map2CharSet( aStr, thePart->getParam("charset")
                                  , aStr, AxVdrTools::getVdrCharSet()
                                  );

      //----- do formatting---------------
      aDst = splitText(aStr);
      dumpText("after splitText", aDst);

      if (myWrapMode >= WM_FLOWED && thePart->getParam("FORMAT") == "FLOWED")
      {
        aDst = getFlowedText(aDst);
        dumpText("after getFlowedText", aDst);
      } // if

      if (myWrapMode >= WM_SMALL)
      {
        aDst = improveWrap(aDst);
        dumpText("after improveWrap", aDst);
      } // if

      if (myWrapMode >= WM_NONE)
      {
        aDst = wrapText(aDst);
        dumpText("after wrapText", aDst);
      }
      break;
    }

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

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

//-------------------------------------------------------------------------
//     AxMailWrapper::dumpText()
//-------------------------------------------------------------------------
void AxMailWrapper::dumpText(const std::string &theMsg, const LineCltn &theSrc)
{
#if TRACE_IS_ON
  wsdebug(("> AxMailWrapper::dumpText() %s,  size: %d", theMsg.c_str(), theSrc.size()));

  int i = 1;
  for (LineCltn::const_iterator anIter  = theSrc.begin();
                                anIter != theSrc.end();
                              ++anIter, ++i)
  {
    const AxLine aLine = (*anIter);
    wsdebug(("[%3d]  Sep: %d: '%s'", i, aLine.fSep, aLine.Text.c_str()));
  } // for

  wsdebug(("< AxMailWrapper::dumpText() %s,  size: %d", theMsg.c_str(), theSrc.size()));
#endif
} // AxMailWrapper::dumpText()
