/*
    kmime_util.cpp

    KMime, the KDE internet mail/usenet news message library.
    Copyright (c) 2001 the KMime authors.
    See file AUTHORS for details

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software Foundation,
    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, US
*/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "kmime_util.h"

#include <kmdcodec.h> // for KCodec::{quotedPrintableDe,base64{En,De}}code
#include <tdeglobal.h>
#include <tdelocale.h>
#include <kcharsets.h>
#include <tdeversion.h>
#if KDE_IS_VERSION( 3, 1, 90 )
#include <kcalendarsystem.h>
#endif

#include <tqtextcodec.h>
#include <tqstrlist.h> // for TQStrIList
#include <tqregexp.h>
#include <tqdatetime.h>

#include <stdlib.h>
#include <ctype.h>
#include <time.h> // for time()
#include <unistd.h> // for getpid()

using namespace KMime;

namespace KMime {

TQStrIList c_harsetCache;
TQStrIList l_anguageCache;

const char* cachedCharset(const TQCString &name)
{
  int idx=c_harsetCache.find(name.data());
  if(idx>-1)
    return c_harsetCache.at(idx);

  c_harsetCache.append(name.upper().data());
  //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl;
  return c_harsetCache.last();
}

const char* cachedLanguage(const TQCString &name)
{
  int idx=l_anguageCache.find(name.data());
  if(idx>-1)
    return l_anguageCache.at(idx);

  l_anguageCache.append(name.upper().data());
  //kdDebug() << "KNMimeBase::cachedCharset() number of cs " << c_harsetCache.count() << endl;
  return l_anguageCache.last();
}

bool isUsAscii(const TQString &s)
{
  uint sLength = s.length();
  for (uint i=0; i<sLength; i++)
    if (s.at(i).latin1()<=0)    // c==0: non-latin1, c<0: non-us-ascii
      return false;

  return true;
}

// "(),.:;<>@[\]
const uchar specialsMap[16] = {
  0x00, 0x00, 0x00, 0x00, // CTLs
  0x20, 0xCA, 0x00, 0x3A, // SPACE ... '?'
  0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
  0x00, 0x00, 0x00, 0x00  // '`' ... DEL
};

// "(),:;<>@[\]/=?
const uchar tSpecialsMap[16] = {
  0x00, 0x00, 0x00, 0x00, // CTLs
  0x20, 0xC9, 0x00, 0x3F, // SPACE ... '?'
  0x80, 0x00, 0x00, 0x1C, // '@' ... '_'
  0x00, 0x00, 0x00, 0x00  // '`' ... DEL
};

// all except specials, CTLs, SPACE.
const uchar aTextMap[16] = {
  0x00, 0x00, 0x00, 0x00,
  0x5F, 0x35, 0xFF, 0xC5,
  0x7F, 0xFF, 0xFF, 0xE3,
  0xFF, 0xFF, 0xFF, 0xFE
};

// all except tspecials, CTLs, SPACE.
const uchar tTextMap[16] = {
  0x00, 0x00, 0x00, 0x00,
  0x5F, 0x36, 0xFF, 0xC0,
  0x7F, 0xFF, 0xFF, 0xE3,
  0xFF, 0xFF, 0xFF, 0xFE
};

// none except a-zA-Z0-9!*+-/
const uchar eTextMap[16] = {
  0x00, 0x00, 0x00, 0x00,
  0x40, 0x35, 0xFF, 0xC0,
  0x7F, 0xFF, 0xFF, 0xE0,
  0x7F, 0xFF, 0xFF, 0xE0
};

#if defined(_AIX) && defined(truncate)
#undef truncate
#endif

TQString decodeRFC2047String(const TQCString &src, const char **usedCS,
			    const TQCString &defaultCS, bool forceCS)
{
  TQCString result, str;
  TQCString declaredCS;
  const char *beg, *end, *mid, *pos=0;
  char *dest, *endOfLastEncWord=0;
  char encoding = '\0';
  bool valid, onlySpacesSinceLastWord=false;
  const int maxLen=400;
  int i;

  if(src.find("=?") < 0)
    result = src.copy();
  else {
    result.truncate(src.length());
    for (pos=src.data(), dest=result.data(); *pos; pos++)
    {
      if (pos[0]!='=' || pos[1]!='?')
      {
        *dest++ = *pos;
        if (onlySpacesSinceLastWord)
          onlySpacesSinceLastWord = (pos[0]==' ' || pos[1]=='\t');
        continue;
      }
      beg = pos+2;
      end = beg;
      valid = TRUE;
      // parse charset name
      declaredCS="";
      for (i=2,pos+=2; i<maxLen && (*pos!='?'&&(ispunct(*pos)||isalnum(*pos))); i++) {
        declaredCS+=(*pos);
        pos++;
      }
      if (*pos!='?' || i<4 || i>=maxLen) valid = FALSE;
      else
      {
        // get encoding and check delimiting question marks
        encoding = toupper(pos[1]);
        if (pos[2]!='?' || (encoding!='Q' && encoding!='B'))
          valid = FALSE;
        pos+=3;
        i+=3;
      }
      if (valid)
      {
        mid = pos;
        // search for end of encoded part
        while (i<maxLen && *pos && !(*pos=='?' && *(pos+1)=='='))
        {
          i++;
          pos++;
        }
        end = pos+2;//end now points to the first char after the encoded string
        if (i>=maxLen || !*pos) valid = FALSE;
      }

      if (valid) {
        // cut all linear-white space between two encoded words
        if (onlySpacesSinceLastWord)
          dest=endOfLastEncWord;

        if (mid < pos) {
          str = TQCString(mid, (int)(pos - mid + 1));
          if (encoding == 'Q')
          {
            // decode quoted printable text
            for (i=str.length()-1; i>=0; i--)
              if (str[i]=='_') str[i]=' ';
            str = KCodecs::quotedPrintableDecode(str);
          }
          else
          {
            str = KCodecs::base64Decode(str);
          }
          if (!str.isNull()) {
            for (i=0; str[i]; i++) {
              *dest++ = str[i];
            }
          }
        }

        endOfLastEncWord=dest;
        onlySpacesSinceLastWord=true;

        pos = end -1;
      }
      else
      {
        pos = beg - 2;
        *dest++ = *pos++;
        *dest++ = *pos;
      }
    }
    *dest = '\0';
  }

  //find suitable TQTextCodec
  TQTextCodec *codec=0;
  bool ok=true;
  if (forceCS || declaredCS.isEmpty()) {
    codec=TDEGlobal::charsets()->codecForName(defaultCS);
    (*usedCS)=cachedCharset(defaultCS);
  }
  else {
    codec=TDEGlobal::charsets()->codecForName(declaredCS, ok);
    if(!ok) {     //no suitable codec found => use default charset
      codec=TDEGlobal::charsets()->codecForName(defaultCS);
      (*usedCS)=cachedCharset(defaultCS);
    }
    else
      (*usedCS)=cachedCharset(declaredCS);
  }

  return codec->toUnicode(result.data(), result.length());
}

TQString decodeRFC2047String(const TQCString &src)
{
  const char *usedCS;
  return decodeRFC2047String(src, &usedCS, "utf-8", false);
}

TQCString encodeRFC2047String(const TQString &src, const char *charset,
			     bool addressHeader, bool allow8BitHeaders)
{
  TQCString encoded8Bit, result, usedCS;
  unsigned int start=0,end=0;
  bool nonAscii=false, ok=true, useTQEncoding=false;
  TQTextCodec *codec=0;

  usedCS=charset;
  codec=TDEGlobal::charsets()->codecForName(usedCS, ok);

  if(!ok) {
    //no codec available => try local8Bit and hope the best ;-)
    usedCS=TDEGlobal::locale()->encoding();
    codec=TDEGlobal::charsets()->codecForName(usedCS, ok);
  }

  if (usedCS.find("8859-")>=0)  // use "B"-Encoding for non iso-8859-x charsets
    useTQEncoding=true;

  encoded8Bit=codec->fromUnicode(src);

  if(allow8BitHeaders)
    return encoded8Bit;

  uint encoded8BitLength = encoded8Bit.length();
  for (unsigned int i=0; i<encoded8BitLength; i++) {
    if (encoded8Bit[i]==' ')    // encoding starts at word boundaries
      start = i+1;

    // encode escape character, for japanese encodings...
    if (((signed char)encoded8Bit[i]<0) || (encoded8Bit[i] == '\033') ||
        (addressHeader && (strchr("\"()<>@,.;:\\[]=",encoded8Bit[i])!=0))) {
      end = start;   // non us-ascii char found, now we determine where to stop encoding
      nonAscii=true;
      break;
    }
  }

  if (nonAscii) {
    while ((end<encoded8Bit.length())&&(encoded8Bit[end]!=' '))  // we encode complete words
      end++;

    for (unsigned int x=end;x<encoded8Bit.length();x++)
      if (((signed char)encoded8Bit[x]<0) || (encoded8Bit[x] == '\033') ||
          (addressHeader && (strchr("\"()<>@,.;:\\[]=",encoded8Bit[x])!=0))) {
        end = encoded8Bit.length();     // we found another non-ascii word

      while ((end<encoded8Bit.length())&&(encoded8Bit[end]!=' '))  // we encode complete words
        end++;
    }

    result = encoded8Bit.left(start)+"=?"+usedCS;

    if (useTQEncoding) {
      result += "?Q?";

      char c,hexcode;                       // implementation of the "Q"-encoding described in RFC 2047
      for (unsigned int i=start;i<end;i++) {
        c = encoded8Bit[i];
        if (c == ' ')       // make the result readable with not MIME-capable readers
          result+='_';
        else
          if (((c>='a')&&(c<='z'))||      // paranoid mode, we encode *all* special characters to avoid problems
              ((c>='A')&&(c<='Z'))||      // with "From" & "To" headers
              ((c>='0')&&(c<='9')))
            result+=c;
          else {
            result += "=";                 // "stolen" from KMail ;-)
            hexcode = ((c & 0xF0) >> 4) + 48;
            if (hexcode >= 58) hexcode += 7;
            result += hexcode;
            hexcode = (c & 0x0F) + 48;
            if (hexcode >= 58) hexcode += 7;
            result += hexcode;
          }
      }
    } else {
      result += "?B?"+KCodecs::base64Encode(encoded8Bit.mid(start,end-start), false);
    }

    result +="?=";
    result += encoded8Bit.right(encoded8Bit.length()-end);
  }
  else
    result = encoded8Bit;

  return result;
}

TQCString uniqueString()
{
  static char chars[] = "0123456789abcdefghijklmnopqrstuvxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  time_t now;
  TQCString ret;
  char p[11];
  int pos, ran;
  unsigned int timeval;

  p[10]='\0';
  now=time(0);
  ran=1+(int) (1000.0*rand()/(RAND_MAX+1.0));
  timeval=(now/ran)+getpid();

  for(int i=0; i<10; i++){
    pos=(int) (61.0*rand()/(RAND_MAX+1.0));
    //kdDebug(5003) << pos << endl;
    p[i]=chars[pos];
  }
  ret.sprintf("%d.%s", timeval, p);

  return ret;
}


TQCString multiPartBoundary()
{
  TQCString ret;
  ret="nextPart"+uniqueString();
  return ret;
}

TQCString extractHeader(const TQCString &src, const char *name)
{
  TQCString n=TQCString(name)+":";
  int pos1=-1, pos2=0, len=src.length()-1;
  bool folded(false);

  if (n.lower() == src.left(n.length()).lower()) {
    pos1 = 0;
  } else {
    n.prepend("\n");
    pos1 = src.find(n.data(),0,false);
  }

  if (pos1>-1) {    //there is a header with the given name
    pos1+=n.length(); //skip the name
    // skip the usual space after the colon
    if ( src.at( pos1 ) == ' ' )
      ++pos1;
    pos2=pos1;

    if (src[pos2]!='\n') {  // check if the header is not empty
      while(1) {
        pos2=src.find("\n", pos2+1);
        if(pos2==-1 || pos2==len || ( src[pos2+1]!=' ' && src[pos2+1]!='\t') ) //break if we reach the end of the string, honor folded lines
          break;
        else
          folded = true;
      }
    }

    if(pos2<0) pos2=len+1; //take the rest of the string

    if (!folded)
      return src.mid(pos1, pos2-pos1);
    else
      return (src.mid(pos1, pos2-pos1).replace(TQRegExp("\\s*\\n\\s*")," "));
  }
  else {
    return TQCString(0); //header not found
  }
}


TQCString CRLFtoLF(const TQCString &s)
{
  TQCString ret=s.copy();
  ret.replace(TQRegExp("\\r\\n"), "\n");
  return ret;
}


TQCString CRLFtoLF(const char *s)
{
  TQCString ret=s;
  ret.replace(TQRegExp("\\r\\n"), "\n");
  return ret;
}


TQCString LFtoCRLF(const TQCString &s)
{
  TQCString ret=s.copy();
  ret.replace(TQRegExp("\\n"), "\r\n");
  return ret;
}


void removeQuots(TQCString &str)
{
  // Removes any quote or backslash caracter
  str.replace(TQRegExp("[\\\"]"), "");
}


void removeQuots(TQString &str)
{
  // Removes any quote or backslash caracter
  str.replace(TQRegExp("[\\\"]"), "");
}


void addQuotes(TQCString &str, bool forceQuotes)
{
  if ( forceQuotes || TQString(str).contains( TQRegExp( TQString( "\"|\\\\|=|\\]|\\[|:|;|,|\\.|,|@|<|>|\\)|\\(" ) ) ) ) {
    // Adds a backslash in front of any existing quote or backslash caracter
    str.replace(TQRegExp("([\\\"])"), "\\\\1");
    // Adds quote at beginning and end of thestring
    str.insert(0,'"');
    str.append("\"");
  }
}

int DateFormatter::mDaylight = -1;
DateFormatter::DateFormatter(FormatType fType)
  : mFormat( fType ), mCurrentTime( 0 )
{

}

DateFormatter::~DateFormatter()
{/*empty*/}

DateFormatter::FormatType
DateFormatter::getFormat() const
{
  return mFormat;
}

void
DateFormatter::setFormat( FormatType t )
{
  mFormat = t;
}

TQString
DateFormatter::dateString( time_t otime , const TQString& lang ,
		       bool shortFormat, bool includeSecs ) const
{
  switch ( mFormat ) {
  case Fancy:
    return fancy( otime );
    break;
  case Localized:
    return localized( otime, shortFormat, includeSecs, lang );
    break;
  case CTime:
    return cTime( otime );
    break;
  case Iso:
    return isoDate( otime );
    break;
  case Custom:
    return custom( otime );
    break;
  }
  return TQString();
}

TQString
DateFormatter::dateString(const TQDateTime& dtime, const TQString& lang,
		       bool shortFormat, bool includeSecs ) const
{
  return DateFormatter::dateString( qdateToTimeT(dtime), lang, shortFormat, includeSecs );
}

TQCString
DateFormatter::rfc2822(time_t otime) const
{
  TQDateTime tmp;
  TQCString  ret;

  tmp.setTime_t(otime);

  ret = tmp.toString("ddd, dd MMM yyyy hh:mm:ss ").latin1();
  ret += zone(otime);

  return ret;
}

TQString
DateFormatter::custom(time_t t) const
{
  if ( mCustomFormat.isEmpty() )
    return TQString();

  int z = mCustomFormat.find("Z");
  TQDateTime d;
  TQString ret = mCustomFormat;

  d.setTime_t(t);
  if ( z != -1 ) {
    ret.replace(z,1,zone(t));
  }

  ret = d.toString(ret);

  return ret;
}

void
DateFormatter::setCustomFormat(const TQString& format)
{
  mCustomFormat = format;
  mFormat = Custom;
}

TQString
DateFormatter::getCustomFormat() const
{
  return mCustomFormat;
}


TQCString
DateFormatter::zone(time_t otime) const
{
  TQCString ret;
#if defined(HAVE_TIMEZONE) || defined(HAVE_TM_GMTOFF)
  struct tm *local = localtime( &otime );
#endif

#if defined(HAVE_TIMEZONE)

  //hmm, could make hours & mins static
  int secs = abs(timezone);
  int neg  = (timezone>0)?1:0;
  int hours = secs/3600;
  int mins  = (secs - hours*3600)/60;

  // adjust to daylight
  if ( local->tm_isdst > 0 ) {
      mDaylight = 1;
      if ( neg )
        --hours;
      else
        ++hours;
  } else
      mDaylight = 0;

  ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins);

#elif defined(HAVE_TM_GMTOFF)

  int secs = abs( local->tm_gmtoff );
  int neg  = (local->tm_gmtoff<0)?1:0; //no, I don't know why it's backwards :o
  int hours = secs/3600;
  int mins  = (secs - hours*3600)/60;

  if ( local->tm_isdst > 0 )
      mDaylight = 1;
  else
      mDaylight = 0;

  ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins);

#else

  TQDateTime d1 = TQDateTime::fromString( asctime(gmtime(&otime)) );
  TQDateTime d2 = TQDateTime::fromString( asctime(localtime(&otime)) );
  int secs = d1.secsTo(d2);
  int neg = (secs<0)?1:0;
  secs = abs(secs);
  int hours = secs/3600;
  int mins  = (secs - hours*3600)/60;
  // daylight should be already taken care of here
  ret.sprintf("%c%.2d%.2d",(neg)?'-':'+', hours, mins);

#endif /* HAVE_TIMEZONE */

  return ret;
}

time_t
DateFormatter::qdateToTimeT(const TQDateTime& dt) const
{
  TQDateTime epoch( TQDate(1970, 1,1), TQTime(00,00,00) );
  time_t otime;
  time( &otime );

  TQDateTime d1 = TQDateTime::fromString( asctime(gmtime(&otime)) );
  TQDateTime d2 = TQDateTime::fromString( asctime(localtime(&otime)) );
  time_t drf = epoch.secsTo( dt ) - d1.secsTo( d2 );

  return drf;
}

TQString
DateFormatter::fancy(time_t otime) const
{
  TDELocale *locale = TDEGlobal::locale();

  if ( otime <= 0 )
    return i18n( "unknown" );

  if ( !mCurrentTime ) {
    time( &mCurrentTime );
    mDate.setTime_t( mCurrentTime );
  }

  TQDateTime old;
  old.setTime_t( otime );

  // not more than an hour in the future
  if ( mCurrentTime + 60 * 60 >= otime ) {
    time_t diff = mCurrentTime - otime;

    if ( diff < 24 * 60 * 60 ) {
      if ( old.date().year() == mDate.date().year() &&
	   old.date().dayOfYear() == mDate.date().dayOfYear() )
	return i18n( "Today %1" ).arg( locale->
				       formatTime( old.time(), true ) );
    }
    if ( diff < 2 * 24 * 60 * 60 ) {
      TQDateTime yesterday( mDate.addDays( -1 ) );
      if ( old.date().year() == yesterday.date().year() &&
	   old.date().dayOfYear() == yesterday.date().dayOfYear() )
	return i18n( "Yesterday %1" ).arg( locale->
					   formatTime( old.time(), true) );
    }
    for ( int i = 3; i < 7; i++ )
      if ( diff < i * 24 * 60 * 60 ) {
	TQDateTime weekday( mDate.addDays( -i + 1 ) );
	if ( old.date().year() == weekday.date().year() &&
	     old.date().dayOfYear() == weekday.date().dayOfYear() )
	  return i18n( "1. weekday, 2. time", "%1 %2" ).
#if KDE_IS_VERSION( 3, 1, 90 )
	    arg( locale->calendar()->weekDayName( old.date() ) ).
#else
	    arg( locale->weekDayName( old.date().dayOfWeek() ) ).
#endif
	    arg( locale->formatTime( old.time(), true) );
      }
  }

  return locale->formatDateTime( old );

}

TQString
DateFormatter::localized(time_t otime, bool shortFormat, bool includeSecs,
			 const TQString& localeLanguage ) const
{
  TQDateTime tmp;
  TQString ret;
  TDELocale *locale = TDEGlobal::locale();

  tmp.setTime_t( otime );


  if ( !localeLanguage.isEmpty() ) {
    locale=new TDELocale(localeLanguage);
    locale->setLanguage(localeLanguage);
    locale->setCountry(localeLanguage);
    ret = locale->formatDateTime( tmp, shortFormat, includeSecs );
    delete locale;
  } else {
    ret = locale->formatDateTime( tmp, shortFormat, includeSecs );
  }

  return ret;
}

TQString
DateFormatter::cTime(time_t otime) const
{
  return TQString::fromLatin1( ctime(  &otime ) ).stripWhiteSpace() ;
}

TQString
DateFormatter::isoDate(time_t otime) const
{
  char cstr[64];
  strftime( cstr, 63, "%Y-%m-%d %H:%M:%S", localtime(&otime) );
  return TQString( cstr );
}


void
DateFormatter::reset()
{
  mCurrentTime = 0;
}

TQString
DateFormatter::formatDate(DateFormatter::FormatType t, time_t otime,
			  const TQString& data, bool shortFormat, bool includeSecs )
{
  DateFormatter f( t );
  if ( t == DateFormatter::Custom ) {
    f.setCustomFormat( data );
  }
  return f.dateString( otime, data, shortFormat, includeSecs );
}

TQString
DateFormatter::formatCurrentDate( DateFormatter::FormatType t, const TQString& data,
				  bool shortFormat, bool includeSecs )
{
  DateFormatter f( t );
  if ( t == DateFormatter::Custom ) {
    f.setCustomFormat( data );
  }
  return f.dateString( time(0), data, shortFormat, includeSecs );
}

TQCString
DateFormatter::rfc2822FormatDate( time_t t )
{
  DateFormatter f;
  return f.rfc2822( t );
}

bool
DateFormatter::isDaylight()
{
  if ( mDaylight == -1 ) {
    time_t ntime = time( 0 );
    struct tm *local = localtime( &ntime );
    if ( local->tm_isdst > 0 ) {
      mDaylight = 1;
      return true;
    } else {
      mDaylight = 0;
      return false;
    }
  } else if ( mDaylight != 0 )
    return true;
  else
    return false;
}

} // namespace KMime
