/*
    This file is part of libkabc.
    Copyright (c) 2001 Cornelius Schumacher <schumacher@kde.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 "address.h"

#include <kapplication.h>
#include <kdebug.h>
#include <klocale.h>
#include <ksimpleconfig.h>
#include <kstandarddirs.h>
#include <kstaticdeleter.h>

#include <qfile.h>

using namespace KABC;

QMap<QString, QString> *Address::mISOMap = 0;
static KStaticDeleter< QMap<QString, QString> > isoMapDeleter;

Address::Address() :
  mEmpty( true ), mType( 0 )
{
  mId = KApplication::randomString( 10 );
}

Address::Address( int type ) :
  mEmpty( true ), mType( type )
{
  mId = KApplication::randomString( 10 );
}

bool Address::operator==( const Address &a ) const
{
  if ( mPostOfficeBox != a.mPostOfficeBox ) return false;
  if ( mExtended != a.mExtended ) return false;
  if ( mStreet != a.mStreet ) return false;
  if ( mLocality != a.mLocality ) return false;
  if ( mRegion != a.mRegion ) return false;
  if ( mPostalCode != a.mPostalCode ) return false;
  if ( mCountry != a.mCountry ) return false;
  if ( mLabel != a.mLabel ) return false;
  
  return true;
}

bool Address::operator!=( const Address &a ) const
{
  return !( a == *this );
}

bool Address::isEmpty() const
{
  if ( mPostOfficeBox.isEmpty() &&
       mExtended.isEmpty() &&
       mStreet.isEmpty() &&
       mLocality.isEmpty() &&
       mRegion.isEmpty() &&
       mPostalCode.isEmpty() &&
       mCountry.isEmpty() &&
       mLabel.isEmpty() ) {
    return true;
  }
  return false;
}

void Address::clear()
{
  *this = Address();
}

void Address::setId( const QString &id )
{
  mEmpty = false;

  mId = id;
}

QString Address::id() const
{
  return mId;
}

void Address::setType( int type )
{
  mEmpty = false;

  mType = type;
}

int Address::type() const
{
  return mType;
}

QString Address::typeLabel() const
{
  QString label;
  bool first = true;

  const TypeList list = typeList();

  TypeList::ConstIterator it;
  for ( it = list.begin(); it != list.end(); ++it ) {
    if ( ( type() & (*it) ) && ( (*it) != Pref ) ) {
      label.append( ( first ? "" : "/" ) + typeLabel( *it ) );
      if ( first )
        first = false;
    }
  }

  return label;
}

void Address::setPostOfficeBox( const QString &s )
{
  mEmpty = false;

  mPostOfficeBox = s;
}

QString Address::postOfficeBox() const
{
  return mPostOfficeBox;
}

QString Address::postOfficeBoxLabel()
{
  return i18n("Post Office Box");
}


void Address::setExtended( const QString &s )
{
  mEmpty = false;

  mExtended = s;
}

QString Address::extended() const
{
  return mExtended;
}

QString Address::extendedLabel()
{
  return i18n("Extended Address Information");
}


void Address::setStreet( const QString &s )
{
  mEmpty = false;

  mStreet = s;
}

QString Address::street() const
{
  return mStreet;
}

QString Address::streetLabel()
{
  return i18n("Street");
}


void Address::setLocality( const QString &s )
{
  mEmpty = false;

  mLocality = s;
}

QString Address::locality() const
{
  return mLocality;
}

QString Address::localityLabel()
{
  return i18n("Locality");
}


void Address::setRegion( const QString &s )
{
  mEmpty = false;

  mRegion = s;
}

QString Address::region() const
{
  return mRegion;
}

QString Address::regionLabel()
{
  return i18n("Region");
}


void Address::setPostalCode( const QString &s )
{
  mEmpty = false;

  mPostalCode = s;
}

QString Address::postalCode() const
{
  return mPostalCode;
}

QString Address::postalCodeLabel()
{
  return i18n("Postal Code");
}


void Address::setCountry( const QString &s )
{
  mEmpty = false;

  mCountry = s;
}

QString Address::country() const
{
  return mCountry;
}

QString Address::countryLabel()
{
  return i18n("Country");
}


void Address::setLabel( const QString &s )
{
  mEmpty = false;

  mLabel = s;
}

QString Address::label() const
{
  return mLabel;
}

QString Address::labelLabel()
{
  return i18n("Delivery Label");
}

Address::TypeList Address::typeList()
{
  static TypeList list;

  if ( list.isEmpty() )
    list << Dom << Intl << Postal << Parcel << Home << Work << Pref;

  return list;
}

QString Address::typeLabel( int type )
{
  if ( type & Pref )
    return i18n( "Preferred address", "Preferred" );

  switch ( type ) {
    case Dom:
      return i18n("Domestic");
      break;
    case Intl:
      return i18n("International");
      break;
    case Postal:
      return i18n("Postal");
      break;
    case Parcel:
      return i18n("Parcel");
      break;
    case Home:
      return i18n("Home Address", "Home");
      break;
    case Work:
      return i18n("Work Address", "Work");
      break;
    case Pref:
      return i18n("Preferred Address");
      break;
    default:
      return i18n("Other");
      break;
  }
}

void Address::dump() const
{
  kdDebug(5700) << "  Address {" << endl;
  kdDebug(5700) << "    Id: " << id() << endl;
  kdDebug(5700) << "    Extended: " << extended() << endl;
  kdDebug(5700) << "    Street: " << street() << endl;
  kdDebug(5700) << "    Postal Code: " << postalCode() << endl;
  kdDebug(5700) << "    Locality: " << locality() << endl;
  kdDebug(5700) << "  }" << endl;
}


QString Address::formattedAddress( const QString &realName,
                                   const QString &orgaName ) const
{
  QString ciso;
  QString addrTemplate;
  QString ret;

  // FIXME: first check for iso-country-field and prefer that one
  if ( !country().isEmpty() ) {
    ciso = countryToISO( country() );
  } else {
    // fall back to our own country
    ciso = KGlobal::locale()->country();
  }
  KSimpleConfig entry( locate( "locale", 
        QString( "l10n/" ) + ciso + QString( "/entry.desktop" ) ) );
  entry.setGroup( "KCM Locale" );

  // decide whether this needs special business address formatting
  if ( orgaName.isEmpty() ) {
    addrTemplate = entry.readEntry( "AddressFormat" );
  } else {
    addrTemplate = entry.readEntry( "BusinessAddressFormat" );
    if ( addrTemplate.isEmpty() )
      addrTemplate = entry.readEntry( "AddressFormat" );
  }

  // in the case there's no format found at all, default to what we've always
  // used:
  if ( addrTemplate.isEmpty() ) {
    kdWarning(5700) << "address format database incomplete "
        << "(no format for locale " << ciso 
        << " found). Using default address formatting." << endl;
    addrTemplate = "%0(%n\\n)%0(%cm\\n)%0(%s\\n)%0(PO BOX %p\\n)%0(%l%w%r)%,%z";
  }

  // scan
  parseAddressTemplateSection( addrTemplate, ret, realName, orgaName );

  // now add the country line if needed (formatting this time according to
  // the rules of our own system country )
  if ( !country().isEmpty() ) {
    KSimpleConfig entry( locate( "locale", QString( "l10n/" )
          + KGlobal::locale()->country() + QString( "/entry.desktop" ) ) );
    entry.setGroup( "KCM Locale" );
    QString cpos = entry.readEntry( "AddressCountryPosition" );
    if ( "BELOW" == cpos || cpos.isEmpty() ) {
      ret = ret + "\n\n" + country().upper();
    } else if ( "below" == cpos ) {
      ret = ret + "\n\n" + country();
    } else if ( "ABOVE" == cpos ) {
      ret = country().upper() + "\n\n" + ret;
    } else if ( "above" == cpos ) {
      ret = country() + "\n\n" + ret;
    }
  }
  
  return ret;
}

bool Address::parseAddressTemplateSection( const QString &tsection, 
    QString &result, const QString &realName, const QString &orgaName ) const
{
  // This method first parses and substitutes any bracketed sections and
  // after that replaces any tags with their values. If a bracketed section
  // or a tag evaluate to zero, they are not just removed but replaced
  // with a placeholder. This is because in the last step conditionals are
  // resolved which depend on information about zero-evaluations.
  result = tsection;
  int stpos = 0;
  bool ret = false;
  
  // first check for brackets that have to be evaluated first 
  int fpos = result.find( KABC_FMTTAG_purgeempty, stpos );
  while ( -1 != fpos ) {
    int bpos1 = fpos + KABC_FMTTAG_purgeempty.length();
    int bpos2;
    // expect opening bracket and find next balanced closing bracket. If 
    // next char is no opening bracket, continue parsing (no valid tag)
    if ( '(' == result[bpos1] ) {
      bpos2 = findBalancedBracket( result, bpos1 );
      if ( -1 != bpos2 ) {
        // we have balanced brackets, recursively parse:
        QString rplstr;
        bool purge = !parseAddressTemplateSection( result.mid( bpos1+1,
                                                   bpos2-bpos1-1 ), rplstr,
                                                   realName, orgaName );
        if ( purge ) {
          // purge -> remove all
          // replace with !_P_!, so conditional tags work later
          result.replace( fpos, bpos2 - fpos + 1, "!_P_!" );
          // leave stpos as it is
        } else {
          // no purge -> replace with recursively parsed string
          result.replace( fpos, bpos2 - fpos + 1, rplstr );
          ret = true;
          stpos = fpos + rplstr.length();
        }
      } else {
        // unbalanced brackets:  keep on parsing (should not happen 
        // and will result in bad formatting)
        stpos = bpos1; 
      }
    }
    fpos = result.find( KABC_FMTTAG_purgeempty, stpos );
  }

  // after sorting out all purge tags, we just search'n'replace the rest,
  // keeping track of whether at least one tag evaluates to something.
  // The following macro needs QString for R_FIELD
  // It substitutes !_P_! for empty fields so conditional tags work later
#define REPLTAG(R_TAG,R_FIELD) \
  if ( result.find(R_TAG, false) != -1 ) { \
    QString rpl = R_FIELD.isEmpty() ? QString("!_P_!") : R_FIELD; \
    result.replace( R_TAG, rpl ); \
    if ( !R_FIELD.isEmpty() ) { \
      ret = true; \
    } \
  }
  REPLTAG( KABC_FMTTAG_realname, realName );
  REPLTAG( KABC_FMTTAG_REALNAME, realName.upper() );
  REPLTAG( KABC_FMTTAG_company, orgaName );
  REPLTAG( KABC_FMTTAG_COMPANY, orgaName.upper() );
  REPLTAG( KABC_FMTTAG_pobox, postOfficeBox() );
  REPLTAG( KABC_FMTTAG_street, street() );
  REPLTAG( KABC_FMTTAG_STREET, street().upper() );
  REPLTAG( KABC_FMTTAG_zipcode, postalCode() );
  REPLTAG( KABC_FMTTAG_location, locality() );
  REPLTAG( KABC_FMTTAG_LOCATION, locality().upper() );
  REPLTAG( KABC_FMTTAG_region, region() );
  REPLTAG( KABC_FMTTAG_REGION, region().upper() );
  result.replace( KABC_FMTTAG_newline, "\n" );
#undef REPLTAG
 
  // conditional comma 
  fpos = result.find( KABC_FMTTAG_condcomma, 0 );
  while ( -1 != fpos ) {
    QString str1 = result.mid( fpos - 5, 5 );
    QString str2 = result.mid( fpos + 2, 5 );
    if ( str1 != "!_P_!" && str2 != "!_P_!" ) {
      result.replace( fpos, 2, ", " );
    } else {
      result.remove( fpos, 2 );
    }
    fpos = result.find( KABC_FMTTAG_condcomma, fpos );
  }
  // conditional whitespace
  fpos = result.find( KABC_FMTTAG_condwhite, 0 );
  while ( -1 != fpos ) {
    QString str1 = result.mid( fpos - 5, 5 );
    QString str2 = result.mid( fpos + 2, 5 );
    if ( str1 != "!_P_!" && str2 != "!_P_!" ) {
      result.replace( fpos, 2, " " );
    } else {
      result.remove( fpos, 2 );
    }
    fpos = result.find( KABC_FMTTAG_condwhite, fpos );
  }

  // remove purged:
  result.remove( "!_P_!" );

  return ret;
}

int Address::findBalancedBracket( const QString &tsection, int pos ) const
{
  int balancecounter = 0;
  for( unsigned int i = pos + 1; i < tsection.length(); i++ ) {
    if ( ')' == tsection[i] && 0 == balancecounter ) {
      // found end of brackets
      return i;
    } else
    if ( '(' == tsection[i] ) {
      // nested brackets
      balancecounter++;
    }
  }
  return -1;
}

QString Address::countryToISO( const QString &cname )
{
  // we search a map file for translations from country names to
  // iso codes, storing caching things in a QMap for faster future 
  // access.
  if ( !mISOMap )
    isoMapDeleter.setObject( mISOMap, new QMap<QString, QString>() );

  QMap<QString, QString>::ConstIterator it;
  it = mISOMap->find( cname );
  if ( it != mISOMap->end() )
    return it.data();

  QString mapfile = KGlobal::dirs()->findResource( "data", 
          QString::fromLatin1( "kabc/countrytransl.map" ) );

  QFile file( mapfile );
  if ( file.open( IO_ReadOnly ) ) {
    QTextStream s( &file );
    QString strbuf = s.readLine();
    while( !strbuf.isEmpty() ) {
      QStringList countryInfo = QStringList::split( '\t', strbuf, true );
      if ( countryInfo[ 0 ] == cname ) {
        file.close();
        mISOMap->insert( cname, countryInfo[ 1 ] );
        return countryInfo[ 1 ];
      }
      strbuf = s.readLine();
    }
    file.close();
  }
  
  // fall back to system country
  mISOMap->insert( cname, KGlobal::locale()->country() );
  return KGlobal::locale()->country();
}

QString Address::ISOtoCountry( const QString &ISOname )
{
  // get country name from ISO country code (e.g. "no" -> i18n("Norway"))
  if ( ISOname.simplifyWhiteSpace().isEmpty() )
    return QString::null;

  QString mapfile = KGlobal::dirs()->findResource( "data", 
          QString::fromLatin1( "kabc/countrytransl.map" ) );

  QFile file( mapfile );
  if ( file.open( IO_ReadOnly ) ) {
    QTextStream s( &file );
    QString searchStr = "\t" + ISOname.simplifyWhiteSpace().lower();
    QString strbuf = s.readLine();
    int pos;
    while ( !strbuf.isEmpty() ) {
      if ( (pos = strbuf.find( searchStr )) != -1 ) {
        file.close();
        return i18n( strbuf.left( pos ).utf8() );
      }
      strbuf = s.readLine();
    }
    file.close();
  }

  return ISOname;
}

QDataStream &KABC::operator<<( QDataStream &s, const Address &addr )
{
    return s << addr.mId << addr.mType << addr.mPostOfficeBox <<
	    addr.mExtended << addr.mStreet << addr.mLocality <<
	    addr.mRegion << addr.mPostalCode << addr.mCountry <<
	    addr.mLabel;
}

QDataStream &KABC::operator>>( QDataStream &s, Address &addr )
{
    s >> addr.mId >> addr.mType >> addr.mPostOfficeBox >> addr.mExtended >>
	    addr.mStreet >> addr.mLocality >> addr.mRegion >>
	    addr.mPostalCode >> addr.mCountry >> addr.mLabel;

    addr.mEmpty = false;

    return s;
}
