/*
    This file is part of libtdeabc.
    Copyright (c) 2001 Mark Westcott <mark@houseoffish.org>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    along with this library; see the file COPYING.LIB.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <tqmap.h>
#include <tqregexp.h>
#include <kmdcodec.h>

#include "vcard21parser.h"
#include "vcardconverter.h"

using namespace TDEABC;

bool VCardLineX::isValid() const
{
  // Invalid: if it is "begin:vcard" or "end:vcard"
  if ( name == VCARD_BEGIN_N || name == VCARD_END_N )
    return false;

  if ( name[0] == 'x' && name[1] == '-' ) // A custom x- line
    return true;

  // This is long but it makes it a bit faster (and saves me from using
  // a tree which is probably the ideal situation, but a bit memory heavy)
  switch( name[0] ) {
    case 'a':
      if ( name == VCARD_ADR && qualified &&
                   (qualifiers.contains(VCARD_ADR_DOM)    ||
                    qualifiers.contains(VCARD_ADR_INTL)   ||
                    qualifiers.contains(VCARD_ADR_POSTAL) ||
                    qualifiers.contains(VCARD_ADR_HOME)   ||
                    qualifiers.contains(VCARD_ADR_WORK)   ||
                    qualifiers.contains(VCARD_ADR_PREF)
         ) )
        return true;

      if ( name == VCARD_AGENT )
        return true;
      break;

    case 'b':
      if ( name == VCARD_BDAY )
        return true;
      break;

    case 'c':
      if ( name == VCARD_CATEGORIES )
        return true;
      if ( name == VCARD_CLASS && qualified &&
                   (qualifiers.contains(VCARD_CLASS_PUBLIC)      ||
                    qualifiers.contains(VCARD_CLASS_PRIVATE)     ||
                    qualifiers.contains(VCARD_CLASS_CONFIDENTIAL)
         ) )
        return true;
      break;

    case 'e':
      if ( name == VCARD_EMAIL && qualified &&
                  (qualifiers.contains(VCARD_EMAIL_INTERNET) ||
                   qualifiers.contains(VCARD_EMAIL_PREF)     ||
                   qualifiers.contains(VCARD_EMAIL_X400)
         ) )
        return true;
      break;

    case 'f':
      if ( name == VCARD_FN )
        return true;
      break;

    case 'g':
      if ( name == VCARD_GEO )
        return true;
      break;

    case 'k':
      if ( name == VCARD_KEY && qualified &&
                   (qualifiers.contains(VCARD_KEY_X509) ||
                    qualifiers.contains(VCARD_KEY_PGP)
         ) )
        return true;
      break;

    case 'l':
      if ( name == VCARD_LABEL )
        return true;
      if ( name == VCARD_LOGO )
        return true;
      break;

    case 'm':
      if ( name == VCARD_MAILER )
        return true;
      break;

    case 'n':
      if ( name == VCARD_N )
        return true;
      if ( name == VCARD_NAME )
        return true;
      if ( name == VCARD_NICKNAME )
        return true;
      if ( name == VCARD_NOTE )
        return true;
      break;

    case 'o':
      if ( name == VCARD_ORG )
        return true;
      break;

    case 'p':
      if ( name == VCARD_PHOTO )
        return true;
      if ( name == VCARD_PROFILE )
        return true;
      if ( name == VCARD_PRODID )
        return true;
      break;

    case 'r':
      if ( name == VCARD_ROLE )
        return true;
      if ( name == VCARD_REV )
        return true;
      break;

    case 's':
      if ( name == VCARD_SOURCE )
        return true;
      if ( name == VCARD_SOUND )
        return true;
      break;

    case 't':
      if ( name == VCARD_TEL && qualified &&
                   (qualifiers.contains(VCARD_TEL_HOME)  ||
                    qualifiers.contains(VCARD_TEL_WORK)  ||
                    qualifiers.contains(VCARD_TEL_PREF)  ||
                    qualifiers.contains(VCARD_TEL_VOICE) ||
                    qualifiers.contains(VCARD_TEL_FAX)   ||
                    qualifiers.contains(VCARD_TEL_MSG)   ||
                    qualifiers.contains(VCARD_TEL_CELL)  ||
                    qualifiers.contains(VCARD_TEL_PAGER) ||
                    qualifiers.contains(VCARD_TEL_BBS)   ||
                    qualifiers.contains(VCARD_TEL_MODEM) ||
                    qualifiers.contains(VCARD_TEL_CAR)   ||
                    qualifiers.contains(VCARD_TEL_ISDN)  ||
                    qualifiers.contains(VCARD_TEL_VIDEO) ||
                    qualifiers.contains(VCARD_TEL_PCS)
         ) )
        return true;
      if ( name == VCARD_TZ )
        return true;
      if ( name == VCARD_TITLE )
        return true;
      break;

    case 'u':
      if ( name == VCARD_URL )
        return true;
      if ( name == VCARD_UID )
        return true;
      break;

    case 'v':
      if ( name == VCARD_VERSION )
        return true;
      break;
    default:
      break;
  }

  return false;
}


VCard21Parser::VCard21Parser()
{
}

VCard21Parser::~VCard21Parser()
{
}

void VCard21Parser::readFromString(TDEABC::AddressBook *addressbook, const TQString &data)
{
  TDEABC::Addressee mAddressee = readFromString(data);
  addressbook->insertAddressee(mAddressee);
}

TDEABC::Addressee VCard21Parser::readFromString( const TQString &data)
{
  TDEABC::Addressee addressee;
  VCard21ParserImpl *vCard = VCard21ParserImpl::parseVCard(data);
  TQString tmpStr;

  // Check if parsing failed
  if (vCard == 0)
  {
     kdDebug() << "Parsing failed" << endl;
     return addressee;
  }
  //set the addressees name and formated name
  TQStringList tmpList = vCard->getValues(VCARD_N);
  TQString formattedName = "";
  if (tmpList.count() > 0)
    addressee.setFamilyName(tmpList[0]);
  if (tmpList.count() > 1)
    addressee.setGivenName(tmpList[1]);
  if (tmpList.count() > 2)
    addressee.setAdditionalName(tmpList[2]);
  if (tmpList.count() > 3)
    addressee.setPrefix(tmpList[3]);
  if (tmpList.count() > 4)
    addressee.setSuffix(tmpList[4]);

  tmpStr = (vCard->getValue(VCARD_FN));
  if (!tmpStr.isEmpty())
    addressee.setFormattedName(tmpStr);

  //set the addressee's nick name
  tmpStr = vCard->getValue(VCARD_NICKNAME);
  addressee.setNickName(tmpStr);
  //set the addressee's organization
  tmpStr = vCard->getValue(VCARD_ORG);
  addressee.setOrganization(tmpStr);
  //set the addressee's title
  tmpStr = vCard->getValue(VCARD_TITLE);
  addressee.setTitle(tmpStr);
  //set the addressee's email - we can only deal with two.  The preferenced one and one other.
  tmpStr = vCard->getValue(VCARD_EMAIL, VCARD_EMAIL_INTERNET);
  addressee.insertEmail(tmpStr, false);
  tmpStr = vCard->getValue(VCARD_EMAIL,VCARD_EMAIL_PREF);
  addressee.insertEmail(tmpStr, true);
  //set the addressee's url
  tmpStr = vCard->getValue(VCARD_URL);
  if (tmpStr.isEmpty()) tmpStr = vCard->getValue(VCARD_URL, VCARD_ADR_WORK);
  if (tmpStr.isEmpty()) tmpStr = vCard->getValue(VCARD_URL, VCARD_ADR_HOME);
  if (!tmpStr.isEmpty()) {
    addressee.setUrl(KURL(tmpStr));
  }

  //set the addressee's birthday
  tmpStr = vCard->getValue(VCARD_BDAY);
  addressee.setBirthday(VCardStringToDate(tmpStr));

  //set the addressee's phone numbers
  for ( TQValueListIterator<VCardLineX> i = vCard->_vcdata->begin();i != vCard->_vcdata->end(); ++i ) {
    if ( (*i).name == VCARD_TEL ) {
      int type = 0;
      if ( (*i).qualified ) {
        if ( (*i).qualifiers.contains( VCARD_TEL_HOME ) )
          type |= PhoneNumber::Home;
        if ( (*i).qualifiers.contains( VCARD_TEL_WORK ) )
          type |= PhoneNumber::Work;
        if ( (*i).qualifiers.contains( VCARD_TEL_PREF ) )
          type |= PhoneNumber::Pref;
        // if ( (*i).qualifiers.contains( VCARD_TEL_VOICE ) )
        //  type |= PhoneNumber::Voice;
        if ( (*i).qualifiers.contains( VCARD_TEL_FAX ) )
          type |= PhoneNumber::Fax;
        if ( (*i).qualifiers.contains( VCARD_TEL_MSG ) )
          type |= PhoneNumber::Msg;
        if ( (*i).qualifiers.contains( VCARD_TEL_CELL ) )
          type |= PhoneNumber::Cell;
        if ( (*i).qualifiers.contains( VCARD_TEL_PAGER ) )
          type |= PhoneNumber::Pager;
        if ( (*i).qualifiers.contains( VCARD_TEL_BBS ) )
          type |= PhoneNumber::Bbs;
        if ( (*i).qualifiers.contains( VCARD_TEL_MODEM ) )
          type |= PhoneNumber::Modem;
        if ( (*i).qualifiers.contains( VCARD_TEL_CAR ) )
          type |= PhoneNumber::Car;
        if ( (*i).qualifiers.contains( VCARD_TEL_ISDN ) )
          type |= PhoneNumber::Isdn;
        if ( (*i).qualifiers.contains( VCARD_TEL_VIDEO ) )
          type |= PhoneNumber::Video;
        if ( (*i).qualifiers.contains( VCARD_TEL_PCS ) )
          type |= PhoneNumber::Pcs;
      }
      addressee.insertPhoneNumber( PhoneNumber( (*i).parameters[ 0 ], type ) );
    }
  }

  //set the addressee's addresses
  for ( TQValueListIterator<VCardLineX> i = vCard->_vcdata->begin();i != vCard->_vcdata->end(); ++i ) {
    if ( (*i).name == VCARD_ADR ) {
      int type = 0;
      if ( (*i).qualified ) {
        if ( (*i).qualifiers.contains( VCARD_ADR_DOM ) )
          type |= Address::Dom;
        if ( (*i).qualifiers.contains( VCARD_ADR_INTL ) )
          type |= Address::Intl;
        if ( (*i).qualifiers.contains( VCARD_ADR_POSTAL ) )
          type |= Address::Postal;
        if ( (*i).qualifiers.contains( VCARD_ADR_PARCEL ) )
          type |= Address::Parcel;
        if ( (*i).qualifiers.contains( VCARD_ADR_HOME ) )
          type |= Address::Home;
        if ( (*i).qualifiers.contains( VCARD_ADR_WORK ) )
          type |= Address::Work;
        if ( (*i).qualifiers.contains( VCARD_ADR_PREF ) )
          type |= Address::Pref;
      }
      addressee.insertAddress( readAddressFromQStringList( (*i).parameters, type ) );
    }
  }

  //set the addressee's delivery label
  tmpStr = vCard->getValue(VCARD_LABEL);
  if (!tmpStr.isEmpty()) {
    tmpStr.replace("\r\n","\n");
    Address tmpAddress;
    tmpAddress.setLabel(tmpStr);
    addressee.insertAddress(tmpAddress);
  }

  //set the addressee's notes
  tmpStr = vCard->getValue(VCARD_NOTE);
  tmpStr.replace("\r\n","\n");
  addressee.setNote(tmpStr);

  //set the addressee's timezone
  tmpStr = vCard->getValue(VCARD_TZ);
  TimeZone tmpZone(tmpStr.toInt());
  addressee.setTimeZone(tmpZone);

  //set the addressee's geographical position
  tmpList = vCard->getValues(VCARD_GEO);
  if (tmpList.count()==2)
  {
    tmpStr = tmpList[0];
    float glat = tmpStr.toFloat();
    tmpStr = tmpList[1];
    float glong = tmpStr.toFloat();
    Geo tmpGeo(glat,glong);
    addressee.setGeo(tmpGeo);
  }

  //set the last revision date
  tmpStr = vCard->getValue(VCARD_REV);
  addressee.setRevision(VCardStringToDate(tmpStr));

  //set the role of the addressee
  tmpStr = vCard->getValue(VCARD_ROLE);
  addressee.setRole(tmpStr);

  delete vCard;

  return addressee;
}



TDEABC::Address VCard21Parser::readAddressFromQStringList ( const TQStringList &data, const int type )
{
  TDEABC::Address mAddress;
  mAddress.setType( type );

  if ( data.count() > 0 )
    mAddress.setPostOfficeBox( data[0] );
  if ( data.count() > 1 )
    mAddress.setExtended( data[1] );
  if ( data.count() > 2 )
    mAddress.setStreet( data[2] );
  if ( data.count() > 3 )
    mAddress.setLocality( data[3] );
  if ( data.count() > 4 )
    mAddress.setRegion( data[4] );
  if ( data.count() > 5 )
    mAddress.setPostalCode( data[5] );
  if ( data.count() > 6 )
    mAddress.setCountry( data[6] );

  return mAddress;
}


VCard21ParserImpl *VCard21ParserImpl::parseVCard( const TQString& vc, int *err )
{
  int _err = 0;
  int _state = VC_STATE_BEGIN;

  TQValueList<VCardLineX> *vcdata;
  TQValueList<TQString> lines;

  vcdata = new TQValueList<VCardLineX>;

  lines = TQStringList::split( TQRegExp( "[\x0d\x0a]" ), vc );

  // for each line in the vCard
  for ( TQStringList::Iterator j = lines.begin(); j != lines.end(); ++j ) {
    VCardLineX _vcl;

    // take spaces off the end - ugly but necessary hack
    for ( int g = (*j).length()-1; g > 0 && (*j)[g].isSpace(); --g )
      (*j)[g] = 0;

    // first token:
    //   verify state, update if necessary
    if ( _state & VC_STATE_BEGIN) {
      if ( !tqstricmp( (*j).latin1(), VCARD_BEGIN ) ) {
        _state = VC_STATE_BODY;
        continue;
      } else {
        _err = VC_ERR_NO_BEGIN;
        break;
      }
    } else if ( _state & VC_STATE_BODY ) {
      if ( !tqstricmp( (*j).latin1(), VCARD_END ) ) {
        _state |= VC_STATE_END;
        break;
      }

      // split into two tokens
      int colon = (*j).find( ':' );
      if ( colon < 0 ) {
        _err = VC_ERR_INVALID_LINE;
        break;
      }

      TQString key = (*j).left( colon );
      TQString value = (*j).mid( colon + 1 );

      // check for qualifiers and
      // set name, qualified, qualifier(s)
      TQStringList keyTokens = TQStringList::split( ';', key );
      bool qp = false, first_pass = true;
      bool b64 = false;

      if ( keyTokens.count() > 0 ) {
        _vcl.qualified = false;
        _vcl.name = keyTokens[ 0 ].lower();

        for ( TQStringList::Iterator z = keyTokens.begin(); z != keyTokens.end(); ++z ) {
          TQString zz = (*z).lower();
          if ( zz == VCARD_QUOTED_PRINTABLE || zz == VCARD_ENCODING_QUOTED_PRINTABLE ) {
            qp = true;
          } else if ( zz == VCARD_BASE64 ) {
            b64 = true;
          } else if ( !first_pass ) {
            _vcl.qualified = true;
            _vcl.qualifiers.append( zz );
          }
          first_pass = false;
        }
      } else {
        _err = VC_ERR_INVALID_LINE;
      }

      if ( _err != 0 )
        break;

      if ( _vcl.name == VCARD_VERSION )
        _state |= VC_STATE_HAVE_VERSION;

      if ( _vcl.name == VCARD_N || _vcl.name == VCARD_FN )
        _state |= VC_STATE_HAVE_N;

      // second token:
      //    split into tokens by ;
      //    add to parameters vector
      if ( b64 ) {
        if ( value[ value.length() - 1 ] != '=' )
          do {
            value += *( ++j );
          } while ( (*j)[ (*j).length() - 1 ] != '=' );
      } else {
        if ( qp ) { // join any split lines
          while ( value[ value.length() - 1 ] == '=' ) {
            value.remove( value.length() - 1, 1 );
            value.append(*( ++j ));
          }
        }
        _vcl.parameters = TQStringList::split( ';', value, true );
        if ( qp ) { // decode the quoted printable
          for ( TQStringList::Iterator z = _vcl.parameters.begin(); z != _vcl.parameters.end(); ++z )
            *z = KCodecs::quotedPrintableDecode( TQCString((*z).latin1()) );
        }
      }
    } else {
      _err = VC_ERR_INTERNAL;
      break;
    }

    // validate VCardLineX
    if ( !_vcl.isValid() ) {
      _err = VC_ERR_INVALID_LINE;
      break;
    }

    // add to vector
    vcdata->append( _vcl );
  }

  // errors to check at the last minute (exit state related)
  if ( _err == 0 ) {
    if ( !( _state & VC_STATE_END ) ) // we have to have an end!!
      _err = VC_ERR_NO_END;

    if ( !( _state & VC_STATE_HAVE_N ) || // we have to have the mandatories!
         !( _state & VC_STATE_HAVE_VERSION ) )
      _err = VC_ERR_MISSING_MANDATORY;
  }

  // set the error message if we can, and only return an object
  // if the vCard was valid.
  if ( err )
    *err = _err;

  if ( _err != 0 ) {
    delete vcdata;
    return 0;
  }

  return new VCard21ParserImpl( vcdata );
}

VCard21ParserImpl::VCard21ParserImpl()
  : _vcdata( 0 )
{
}

VCard21ParserImpl::VCard21ParserImpl(TQValueList<VCardLineX> *_vcd)
  : _vcdata(_vcd)
{
}

VCard21ParserImpl::~VCard21ParserImpl()
{
  delete _vcdata;
  _vcdata = 0;
}

TQString VCard21ParserImpl::getValue(const TQString& name, const TQString& qualifier)
{
  TQString failed;
  const TQString lowname = name.lower();
  const TQString lowqualifier = qualifier.lower();

  for (TQValueListIterator<VCardLineX> i = _vcdata->begin();i != _vcdata->end();++i) {
   if ((*i).name == lowname && (*i).qualified && (*i).qualifiers.contains(lowqualifier)) {
    if ((*i).parameters.count() > 0)
     return (*i).parameters[0];
    else return failed;
    }
  }
  return failed;
}


TQString VCard21ParserImpl::getValue(const TQString& name)
{
  TQString failed;
  const TQString lowname = name.lower();

  for (TQValueListIterator<VCardLineX> i = _vcdata->begin();i != _vcdata->end();++i) {
    if ((*i).name == lowname && !(*i).qualified) {
      if ((*i).parameters.count() > 0)
        return (*i).parameters[0];
      else return failed;
    }
   }
  return failed;
}


TQStringList VCard21ParserImpl::getValues(const TQString& name)
{
  const TQString lowname = name.lower();
  for (TQValueListIterator<VCardLineX> i = _vcdata->begin();i != _vcdata->end();++i) {
    if ((*i).name == lowname && !(*i).qualified)
      return (*i).parameters;
  }
  // failed.
  return TQStringList();
}

TQStringList VCard21ParserImpl::getValues(const TQString& name, const TQString& qualifier)
{
  const TQString lowname = name.lower();
  const TQString lowqualifier = qualifier.lower();
  for (TQValueListIterator<VCardLineX> i = _vcdata->begin();i != _vcdata->end();++i) {
    if ((*i).name == lowname && (*i).qualified && (*i).qualifiers.contains(lowqualifier))
       return (*i).parameters;
  }
  // failed.
  return TQStringList();
}