/*
   This file is part of KAddressBook.
   Copyright (C) 2003 Tobias Koenig <tokoe@kde.org>
                 based on the code of KSpread's CSV Import Dialog

   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 <tqbuttongroup.h>
#include <tqcheckbox.h>
#include <tqcombobox.h>
#include <tqlabel.h>
#include <tqlayout.h>
#include <tqlineedit.h>
#include <tqpushbutton.h>
#include <tqradiobutton.h>
#include <tqtable.h>
#include <tqtextcodec.h>
#include <tqtooltip.h>

#include <tdeapplication.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <tdefiledialog.h>
#include <klineedit.h>
#include <tdelocale.h>
#include <kinputdialog.h>
#include <tdemessagebox.h>
#include <kprogress.h>
#include <tdestandarddirs.h>
#include <kurlrequester.h>

#include "dateparser.h"

#include "csvimportdialog.h"

enum { Local = 0, Guess = 1, Latin1 = 2, Uni = 3, MSBug = 4, Codec = 5 };

CSVImportDialog::CSVImportDialog( TDEABC::AddressBook *ab, TQWidget *parent,
                                  const char * name )
  : KDialogBase( Plain, i18n ( "CSV Import Dialog" ), Ok | Cancel | User1 |
                 User2, Ok, parent, name, true, true ),
    mAdjustRows( false ),
    mStartLine( 0 ),
    mTextQuote( '"' ),
    mDelimiter( "," ),
    mAddressBook( ab )
{
  initGUI();

  mTypeMap.insert( i18n( "Undefined" ), Undefined );
  mTypeMap.insert( TDEABC::Addressee::formattedNameLabel(), FormattedName );
  mTypeMap.insert( TDEABC::Addressee::familyNameLabel(), FamilyName );
  mTypeMap.insert( TDEABC::Addressee::givenNameLabel(), GivenName );
  mTypeMap.insert( TDEABC::Addressee::additionalNameLabel(), AdditionalName );
  mTypeMap.insert( TDEABC::Addressee::prefixLabel(), Prefix );
  mTypeMap.insert( TDEABC::Addressee::suffixLabel(), Suffix );
  mTypeMap.insert( TDEABC::Addressee::nickNameLabel(), NickName );
  mTypeMap.insert( TDEABC::Addressee::birthdayLabel(), Birthday );

  mTypeMap.insert( TDEABC::Addressee::homeAddressStreetLabel(), HomeAddressStreet );
  mTypeMap.insert( TDEABC::Addressee::homeAddressLocalityLabel(),
                   HomeAddressLocality );
  mTypeMap.insert( TDEABC::Addressee::homeAddressRegionLabel(), HomeAddressRegion );
  mTypeMap.insert( TDEABC::Addressee::homeAddressPostalCodeLabel(),
                   HomeAddressPostalCode );
  mTypeMap.insert( TDEABC::Addressee::homeAddressCountryLabel(),
                   HomeAddressCountry );
  mTypeMap.insert( TDEABC::Addressee::homeAddressLabelLabel(), HomeAddressLabel );

  mTypeMap.insert( TDEABC::Addressee::businessAddressStreetLabel(),
                   BusinessAddressStreet );
  mTypeMap.insert( TDEABC::Addressee::businessAddressLocalityLabel(),
                   BusinessAddressLocality );
  mTypeMap.insert( TDEABC::Addressee::businessAddressRegionLabel(),
                   BusinessAddressRegion );
  mTypeMap.insert( TDEABC::Addressee::businessAddressPostalCodeLabel(),
                   BusinessAddressPostalCode );
  mTypeMap.insert( TDEABC::Addressee::businessAddressCountryLabel(),
                   BusinessAddressCountry );
  mTypeMap.insert( TDEABC::Addressee::businessAddressLabelLabel(),
                   BusinessAddressLabel );

  mTypeMap.insert( TDEABC::Addressee::homePhoneLabel(), HomePhone );
  mTypeMap.insert( TDEABC::Addressee::businessPhoneLabel(), BusinessPhone );
  mTypeMap.insert( TDEABC::Addressee::mobilePhoneLabel(), MobilePhone );
  mTypeMap.insert( TDEABC::Addressee::homeFaxLabel(), HomeFax );
  mTypeMap.insert( TDEABC::Addressee::businessFaxLabel(), BusinessFax );
  mTypeMap.insert( TDEABC::Addressee::carPhoneLabel(), CarPhone );
  mTypeMap.insert( TDEABC::Addressee::isdnLabel(), Isdn );
  mTypeMap.insert( TDEABC::Addressee::pagerLabel(), Pager );
  mTypeMap.insert( TDEABC::Addressee::emailLabel(), Email );
  mTypeMap.insert( TDEABC::Addressee::mailerLabel(), Mailer );
  mTypeMap.insert( TDEABC::Addressee::titleLabel(), Title );
  mTypeMap.insert( TDEABC::Addressee::roleLabel(), Role );
  mTypeMap.insert( TDEABC::Addressee::organizationLabel(), Organization );
  mTypeMap.insert( TDEABC::Addressee::noteLabel(), Note );
  mTypeMap.insert( TDEABC::Addressee::urlLabel(), URL );

  mCustomCounter = mTypeMap.count();
  int count = mCustomCounter;

  TDEABC::Field::List fields = mAddressBook->fields( TDEABC::Field::CustomCategory );
  TDEABC::Field::List::Iterator it;
  for ( it = fields.begin(); it != fields.end(); ++it, ++count )
    mTypeMap.insert( (*it)->label(), count );

  reloadCodecs();

  connect( mDelimiterBox, TQ_SIGNAL( clicked( int ) ),
           this, TQ_SLOT( delimiterClicked( int ) ) );
  connect( mDelimiterEdit, TQ_SIGNAL( returnPressed() ),
           this, TQ_SLOT( returnPressed() ) );
  connect( mDelimiterEdit, TQ_SIGNAL( textChanged ( const TQString& ) ),
           this, TQ_SLOT( textChanged ( const TQString& ) ) );
  connect( mComboLine, TQ_SIGNAL( activated( const TQString& ) ),
           this, TQ_SLOT( lineSelected( const TQString& ) ) );
  connect( mComboQuote, TQ_SIGNAL( activated( const TQString& ) ),
           this, TQ_SLOT( textquoteSelected( const TQString& ) ) );
  connect( mIgnoreDuplicates, TQ_SIGNAL( stateChanged( int ) ),
           this, TQ_SLOT( ignoreDuplicatesChanged( int ) ) );
  connect( mCodecCombo, TQ_SIGNAL( activated( const TQString& ) ),
           this, TQ_SLOT( codecChanged() ) );

  connect( mUrlRequester, TQ_SIGNAL( returnPressed( const TQString& ) ),
           this, TQ_SLOT( setFile( const TQString& ) ) );
  connect( mUrlRequester, TQ_SIGNAL( urlSelected( const TQString& ) ),
           this, TQ_SLOT( setFile( const TQString& ) ) );
  connect( mUrlRequester->lineEdit(), TQ_SIGNAL( textChanged ( const TQString& ) ),
           this, TQ_SLOT( urlChanged( const TQString& ) ) );

  connect( this, TQ_SIGNAL( user1Clicked() ),
           this, TQ_SLOT( applyTemplate() ) );

  connect( this, TQ_SIGNAL( user2Clicked() ),
           this, TQ_SLOT( saveTemplate() ) );
}

CSVImportDialog::~CSVImportDialog()
{
  mCodecs.clear();
}

TDEABC::AddresseeList CSVImportDialog::contacts() const
{
  DateParser dateParser( mDatePatternEdit->text() );
  TDEABC::AddresseeList contacts;

  KProgressDialog progressDialog( mPage );
  progressDialog.setAutoClose( true );
  progressDialog.progressBar()->setTotalSteps( mTable->numRows() );
  progressDialog.setLabel( i18n( "Importing contacts" ) );
  progressDialog.show();

  tdeApp->processEvents();

  for ( int row = 1; row < mTable->numRows(); ++row ) {
    TDEABC::Addressee a;
    bool emptyRow = true;
    TDEABC::Address addrHome( TDEABC::Address::Home );
    TDEABC::Address addrWork( TDEABC::Address::Work );
    for ( int col = 0; col < mTable->numCols(); ++col ) {
      TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
                                                             col ) );
      if ( !item ) {
        kdError() << "ERROR: item cast failed" << endl;
        continue;
      }

      TQString value = mTable->text( row, col );
      if ( 1 == row && static_cast<TQTableItem *>(item)->text() == value )
        // we are looking at a header row, stop now
        break;

      if ( !value.isEmpty() )
        emptyRow = false;

      switch ( posToType( item->currentItem() ) ) {
        case Undefined:
          continue;
          break;
        case FormattedName:
          a.setFormattedName( value );
          break;
        case GivenName:
          a.setGivenName( value );
          break;
        case FamilyName:
          a.setFamilyName( value );
          break;
        case AdditionalName:
          a.setAdditionalName( value );
          break;
        case Prefix:
          a.setPrefix( value );
          break;
        case Suffix:
          a.setSuffix( value );
          break;
        case NickName:
          a.setNickName( value );
          break;
        case Birthday:
          a.setBirthday( dateParser.parse( value ) );
          break;
        case Email:
          if ( !value.isEmpty() )
            a.insertEmail( value, true );
          break;
        case Role:
          a.setRole( value );
          break;
        case Title:
          a.setTitle( value );
          break;
        case Mailer:
          a.setMailer( value );
          break;
        case URL:
          a.setUrl( KURL( value ) );
          break;
        case Organization:
          a.setOrganization( value );
          break;
        case Note:
          a.setNote( a.note() + value + "\n" );
          break;

        case HomePhone:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Home );
            a.insertPhoneNumber( number );
          }
          break;
        case BusinessPhone:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Work );
            a.insertPhoneNumber( number );
          }
          break;
        case MobilePhone:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Cell );
            a.insertPhoneNumber( number );
          }
          break;
        case HomeFax:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Home |
                                             TDEABC::PhoneNumber::Fax );
            a.insertPhoneNumber( number );
          }
          break;
        case BusinessFax:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Work |
                                             TDEABC::PhoneNumber::Fax );
            a.insertPhoneNumber( number );
          }
          break;
        case CarPhone:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Car );
            a.insertPhoneNumber( number );
          }
          break;
        case Isdn:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Isdn );
            a.insertPhoneNumber( number );
          }
          break;
        case Pager:
          if ( !value.isEmpty() ) {
            TDEABC::PhoneNumber number( value, TDEABC::PhoneNumber::Pager );
            a.insertPhoneNumber( number );
          }
          break;

        case HomeAddressStreet:
          addrHome.setStreet( value );
          break;
        case HomeAddressLocality:
          addrHome.setLocality( value );
          break;
        case HomeAddressRegion:
          addrHome.setRegion( value );
          break;
        case HomeAddressPostalCode:
          addrHome.setPostalCode( value );
          break;
        case HomeAddressCountry:
          addrHome.setCountry( value );
          break;
        case HomeAddressLabel:
          addrHome.setLabel( value );
          break;

        case BusinessAddressStreet:
          addrWork.setStreet( value );
          break;
        case BusinessAddressLocality:
          addrWork.setLocality( value );
          break;
        case BusinessAddressRegion:
          addrWork.setRegion( value );
          break;
        case BusinessAddressPostalCode:
          addrWork.setPostalCode( value );
          break;
        case BusinessAddressCountry:
          addrWork.setCountry( value );
          break;
        case BusinessAddressLabel:
          addrWork.setLabel( value );
          break;
        default:
          TDEABC::Field::List fields = mAddressBook->fields( TDEABC::Field::CustomCategory );
          TDEABC::Field::List::Iterator it;

          int counter = 0;
          for ( it = fields.begin(); it != fields.end(); ++it ) {
            if ( counter == (int)( posToType( item->currentItem() ) - mCustomCounter ) ) {
              (*it)->setValue( a, value );
              break;
            }
            ++counter;
          }
          break;
      }
    }

    tdeApp->processEvents();

    if ( progressDialog.wasCancelled() )
      return TDEABC::AddresseeList();

    progressDialog.progressBar()->advance( 1 );

    if ( !addrHome.isEmpty() )
      a.insertAddress( addrHome );
    if ( !addrWork.isEmpty() )
      a.insertAddress( addrWork );

    if ( !emptyRow && !a.isEmpty() )
      contacts.append( a );
  }

  return contacts;
}

void CSVImportDialog::initGUI()
{
  mPage = plainPage();

  TQGridLayout *layout = new TQGridLayout( mPage, 1, 1, marginHint(),
                                         spacingHint() );
  TQHBoxLayout *hbox = new TQHBoxLayout();
  hbox->setSpacing( spacingHint() );

  TQLabel *label = new TQLabel( i18n( "File to import:" ), mPage );
  hbox->addWidget( label );

  mUrlRequester = new KURLRequester( mPage );
  mUrlRequester->setFilter( "*.csv" );
  hbox->addWidget( mUrlRequester );

  layout->addMultiCellLayout( hbox, 0, 0, 0, 4 );

  // Delimiter: comma, semicolon, tab, space, other
  mDelimiterBox = new TQButtonGroup( i18n( "Delimiter" ), mPage );
  mDelimiterBox->setColumnLayout( 0, TQt::Vertical );
  mDelimiterBox->layout()->setSpacing( spacingHint() );
  mDelimiterBox->layout()->setMargin( marginHint() );
  TQGridLayout *delimiterLayout = new TQGridLayout( mDelimiterBox->layout() );
  delimiterLayout->setAlignment( TQt::AlignTop );
  layout->addMultiCellWidget( mDelimiterBox, 1, 4, 0, 0 );

  mRadioComma = new TQRadioButton( i18n( "Comma" ), mDelimiterBox );
  mRadioComma->setChecked( true );
  delimiterLayout->addWidget( mRadioComma, 0, 0 );

  mRadioSemicolon = new TQRadioButton( i18n( "Semicolon" ), mDelimiterBox );
  delimiterLayout->addWidget( mRadioSemicolon, 0, 1 );

  mRadioTab = new TQRadioButton( i18n( "Tabulator" ), mDelimiterBox );
  delimiterLayout->addWidget( mRadioTab, 1, 0 );

  mRadioSpace = new TQRadioButton( i18n( "Space" ), mDelimiterBox );
  delimiterLayout->addWidget( mRadioSpace, 1, 1 );

  mRadioOther = new TQRadioButton( i18n( "Other" ), mDelimiterBox );
  delimiterLayout->addWidget( mRadioOther, 0, 2 );

  mDelimiterEdit = new TQLineEdit( mDelimiterBox );
  delimiterLayout->addWidget( mDelimiterEdit, 1, 2 );

  mComboLine = new TQComboBox( false, mPage );
  mComboLine->insertItem( i18n( "1" ) );
  layout->addWidget( mComboLine, 2, 3 );

  mComboQuote = new TQComboBox( false, mPage );
  mComboQuote->insertItem( i18n( "\"" ), 0 );
  mComboQuote->insertItem( i18n( "'" ), 1 );
  mComboQuote->insertItem( i18n( "None" ), 2 );
  layout->addWidget( mComboQuote, 2, 2 );

  mDatePatternEdit = new TQLineEdit( mPage );
  mDatePatternEdit->setText( "Y-M-D" ); // ISO 8601 format as default
  TQToolTip::add( mDatePatternEdit, i18n( "<ul><li>y: year with 2 digits</li>"
                                         "<li>Y: year with 4 digits</li>"
                                         "<li>m: month with 1 or 2 digits</li>"
                                         "<li>M: month with 2 digits</li>"
                                         "<li>d: day with 1 or 2 digits</li>"
                                         "<li>D: day with 2 digits</li></ul>" ) );
  layout->addWidget( mDatePatternEdit, 2, 4 );

  label = new TQLabel( i18n( "Start at line:" ), mPage );
  layout->addWidget( label, 1, 3 );

  label = new TQLabel( i18n( "Textquote:" ), mPage );
  layout->addWidget( label, 1, 2 );

  label = new TQLabel( i18n( "Date format:" ), mPage );
  layout->addWidget( label, 1, 4 );

  mIgnoreDuplicates = new TQCheckBox( mPage );
  mIgnoreDuplicates->setText( i18n( "Ignore duplicate delimiters" ) );
  layout->addMultiCellWidget( mIgnoreDuplicates, 3, 3, 2, 4 );

  mCodecCombo = new TQComboBox( mPage );
  layout->addMultiCellWidget( mCodecCombo, 4, 4, 2, 4 );

  mTable = new TQTable( 0, 0, mPage );
  mTable->setSelectionMode( TQTable::NoSelection );
  mTable->horizontalHeader()->hide();
  layout->addMultiCellWidget( mTable, 5, 5, 0, 4 );

  setButtonText( User1, i18n( "Apply Template..." ) );
  setButtonText( User2, i18n( "Save Template..." ) );

  enableButtonOK( false );
  actionButton( User1 )->setEnabled( false );
  actionButton( User2 )->setEnabled( false );

  resize( 400, 300 );
}

void CSVImportDialog::fillTable()
{
  int row, column;
  bool lastCharDelimiter = false;
  bool ignoreDups = mIgnoreDuplicates->isChecked();
  enum { S_START, S_QUOTED_FIELD, S_MAYBE_END_OF_QUOTED_FIELD, S_END_OF_QUOTED_FIELD,
         S_MAYBE_NORMAL_FIELD, S_NORMAL_FIELD } state = S_START;

  TQChar x;
  TQString field;

  // store previous assignment
  mTypeStore.clear();
  for ( column = 0; column < mTable->numCols(); ++column ) {
    TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
                                                           column ) );
    if ( !item || mClearTypeStore )
      mTypeStore.append( typeToPos( Undefined ) );
    else if ( item )
      mTypeStore.append( item->currentItem() );
  }

  clearTable();

  row = column = 1;

  TQTextStream inputStream( mFileArray, IO_ReadOnly );

  // find the current codec
  int code = mCodecCombo->currentItem();
  if ( code == Local )
    inputStream.setEncoding( TQTextStream::Locale );
  else if ( code >= Codec )
    inputStream.setCodec( mCodecs.at( code - Codec ) );
  else if ( code == Uni )
    inputStream.setEncoding( TQTextStream::Unicode );
  else if ( code == MSBug )
    inputStream.setEncoding( TQTextStream::UnicodeReverse );
  else if ( code == Latin1 )
    inputStream.setEncoding( TQTextStream::Latin1 );
  else if ( code == Guess ) {
    TQTextCodec* codec = TQTextCodec::codecForContent( mFileArray.data(), mFileArray.size() );
    if ( codec ) {
      KMessageBox::information( this, i18n( "Using codec '%1'" ).arg( codec->name() ), i18n( "Encoding" ) );
      inputStream.setCodec( codec );
    }
  }

  int maxColumn = 0;
  while ( !inputStream.atEnd() ) {
    inputStream >> x; // read one char

    if ( x == '\r' ) inputStream >> x; // eat '\r', to handle DOS/LOSEDOWS files correctly

    switch ( state ) {
     case S_START :
      if ( x == mTextQuote ) {
        state = S_QUOTED_FIELD;
      } else if ( x == mDelimiter ) {
        if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
          ++column;
        lastCharDelimiter = true;
      } else if ( x == '\n' ) {
        ++row;
        column = 1;
      } else {
        field += x;
        state = S_MAYBE_NORMAL_FIELD;
      }
      break;
     case S_QUOTED_FIELD :
      if ( x == mTextQuote ) {
        state = S_MAYBE_END_OF_QUOTED_FIELD;
      } else if ( x == '\n' &&  mTextQuote.isNull() ) {
        setText( row - mStartLine + 1, column, field );
        field = "";
        if ( x == '\n' ) {
          ++row;
          column = 1;
        } else {
          if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
            ++column;
          lastCharDelimiter = true;
        }
        state = S_START;
      } else {
        field += x;
      }
      break;
     case S_MAYBE_END_OF_QUOTED_FIELD :
      if ( x == mTextQuote ) {
        field += x;
        state = S_QUOTED_FIELD;
      } else if ( x == mDelimiter || x == '\n' ) {
        setText( row - mStartLine + 1, column, field );
        field = "";
        if ( x == '\n' ) {
          ++row;
          column = 1;
        } else {
          if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
            ++column;
          lastCharDelimiter = true;
        }
        state = S_START;
      } else {
        state = S_END_OF_QUOTED_FIELD;
      }
      break;
     case S_END_OF_QUOTED_FIELD :
      if ( x == mDelimiter || x == '\n' ) {
        setText( row - mStartLine + 1, column, field );
        field = "";
        if ( x == '\n' ) {
          ++row;
          column = 1;
        } else {
          if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
            ++column;
          lastCharDelimiter = true;
        }
        state = S_START;
      } else {
        state = S_END_OF_QUOTED_FIELD;
      }
      break;
     case S_MAYBE_NORMAL_FIELD :
      if ( x == mTextQuote ) {
        field = "";
        state = S_QUOTED_FIELD;
        break;
      }
     case S_NORMAL_FIELD :
      if ( x == mDelimiter || x == '\n' ) {
        setText( row - mStartLine + 1, column, field );
        field = "";
        if ( x == '\n' ) {
          ++row;
          column = 1;
        } else {
          if ( ( ignoreDups == false ) || ( lastCharDelimiter == false ) )
            ++column;
          lastCharDelimiter = true;
        }
        state = S_START;
      } else {
        field += x;
      }
    }
    if ( x != mDelimiter )
      lastCharDelimiter = false;

    if ( column > maxColumn )
      maxColumn = column;
  }

  // file with only one line without '\n'
  if ( field.length() > 0 ) {
    setText( row - mStartLine + 1, column, field );
    ++row;
    field = "";
  }

  adjustRows( row - mStartLine );
  mTable->setNumCols( maxColumn );

  for ( column = 0; column < mTable->numCols(); ++column ) {
    TQComboTableItem *item = new TQComboTableItem( mTable, mTypeMap.keys() );
    mTable->setItem( 0, column, item );
    if ( column < (int)mTypeStore.count() )
      item->setCurrentItem( mTypeStore[ column ] );
    else
      item->setCurrentItem( typeToPos( Undefined ) );
    mTable->adjustColumn( column );
  }

  resizeColumns();
}

void CSVImportDialog::clearTable()
{
  for ( int row = 0; row < mTable->numRows(); ++row )
    for ( int column = 0; column < mTable->numCols(); ++column )
      mTable->clearCell( row, column );
}

void CSVImportDialog::fillComboBox()
{
  mComboLine->clear();
  for ( int row = 1; row < mTable->numRows() + 1; ++row )
    mComboLine->insertItem( TQString::number( row ), row - 1 );
}

void CSVImportDialog::reloadCodecs()
{
  mCodecCombo->clear();

  mCodecs.clear();

  TQTextCodec *codec;
  for ( int i = 0; ( codec = TQTextCodec::codecForIndex( i ) ); i++ )
    mCodecs.append( codec );

  mCodecCombo->insertItem( i18n( "Local (%1)" ).arg( TQTextCodec::codecForLocale()->name() ), Local );
  mCodecCombo->insertItem( i18n( "[guess]" ), Guess );
  mCodecCombo->insertItem( i18n( "Latin1" ), Latin1 );
  mCodecCombo->insertItem( i18n( "Unicode" ), Uni );
  mCodecCombo->insertItem( i18n( "Microsoft Unicode" ), MSBug );

	for ( uint i = 0; i < mCodecs.count(); i++ )
    mCodecCombo->insertItem( mCodecs.at( i )->name(), Codec + i );
}

void CSVImportDialog::setText( int row, int col, const TQString& text )
{
  if ( row < 1 ) // skipped by the user
    return;

  if ( mTable->numRows() < row ) {
    mTable->setNumRows( row + 5000 ); // We add 5000 at a time to limit recalculations
    mAdjustRows = true;
  }

  if ( mTable->numCols() < col )
    mTable->setNumCols( col + 50 ); // We add 50 at a time to limit recalculation

  mTable->setText( row - 1, col - 1, text );
}

/*
 * Called after the first fillTable() when number of rows are unknown.
 */
void CSVImportDialog::adjustRows( int rows )
{
  if ( mAdjustRows ) {
    mTable->setNumRows( rows );
    mAdjustRows = false;
  }
}

void CSVImportDialog::resizeColumns()
{
  TQFontMetrics fm = fontMetrics();
  int width = 0;

  TQMap<TQString, uint>::ConstIterator it;
  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it ) {
    width = TQMAX( width, fm.width( it.key() ) );
  }

  for ( int i = 0; i < mTable->numCols(); ++i )
    mTable->setColumnWidth( i, TQMAX( width + 15, mTable->columnWidth( i ) ) );
}

void CSVImportDialog::returnPressed()
{
  if ( mDelimiterBox->id( mDelimiterBox->selected() ) != 4 )
    return;

  mDelimiter = mDelimiterEdit->text();
  fillTable();
}

void CSVImportDialog::textChanged ( const TQString& )
{
  mRadioOther->setChecked ( true );
  delimiterClicked( 4 ); // other
}

void CSVImportDialog::delimiterClicked( int id )
{
  switch ( id ) {
   case 0: // comma
    mDelimiter = ",";
    break;
   case 4: // other
    mDelimiter = mDelimiterEdit->text();
    break;
   case 2: // tab
    mDelimiter = "\t";
    break;
   case 3: // space
    mDelimiter = " ";
    break;
   case 1: // semicolon
    mDelimiter = ";";
    break;
  }

  fillTable();
}

void CSVImportDialog::textquoteSelected( const TQString& mark )
{
  if ( mComboQuote->currentItem() == 2 )
    mTextQuote = 0;
  else
    mTextQuote = mark[ 0 ];

  fillTable();
}

void CSVImportDialog::lineSelected( const TQString& line )
{
  mStartLine = line.toInt() - 1;
  fillTable();
}

void CSVImportDialog::slotOk()
{
  bool assigned = false;

  for ( int column = 0; column < mTable->numCols(); ++column ) {
    TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
                                                           column ) );
    if ( item && posToType( item->currentItem() ) != Undefined )
      assigned = true;
  }

  if ( assigned )
    KDialogBase::slotOk();
  else
    KMessageBox::sorry( this, i18n( "You have to assign at least one column." ) );
}

void CSVImportDialog::applyTemplate()
{
  TQMap<uint,int> columnMap;
  TQMap<TQString, TQString> fileMap;
  TQStringList templates;

  // load all template files
  TQStringList list = TDEGlobal::dirs()->findAllResources( "data" , TQString( tdeApp->name() ) +
      "/csv-templates/*.desktop", true, true );

  for ( TQStringList::iterator it = list.begin(); it != list.end(); ++it )
  {
    TDESimpleConfig config( *it, true );

    if ( !config.hasGroup( "csv column map" ) )
	    continue;

    config.setGroup( "Misc" );
    templates.append( config.readEntry( "Name" ) );
    fileMap.insert( config.readEntry( "Name" ), *it );
  }

  // let the user chose, what to take
  bool ok = false;
  TQString tmp;
  tmp = KInputDialog::getItem( i18n( "Template Selection" ),
                  i18n( "Please select a template, that matches the CSV file:" ),
                  templates, 0, false, &ok, this );

  if ( !ok )
    return;

  TDESimpleConfig config( fileMap[ tmp ], true );
  config.setGroup( "General" );
  mDatePatternEdit->setText( config.readEntry( "DatePattern", "Y-M-D" ) );
  uint numColumns = config.readUnsignedNumEntry( "Columns" );
  mDelimiterEdit->setText( config.readEntry( "DelimiterOther" ) );
  mDelimiterBox->setButton( config.readNumEntry( "DelimiterType" ) );
  delimiterClicked( config.readNumEntry( "DelimiterType" ) );
  int quoteType = config.readNumEntry( "QuoteType" );
  mComboQuote->setCurrentItem( quoteType );
  textquoteSelected( mComboQuote->currentText() );

  // create the column map
  config.setGroup( "csv column map" );
  for ( uint i = 0; i < numColumns; ++i ) {
    int col = config.readNumEntry( TQString::number( i ) );
    columnMap.insert( i, col );
  }

  // apply the column map
  for ( uint column = 0; column < columnMap.count(); ++column ) {
    int type = columnMap[ column ];
    TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
                                                           column ) );
    if ( item )
      item->setCurrentItem( typeToPos( type ) );
  }
}

void CSVImportDialog::saveTemplate()
{
  TQString fileName = KFileDialog::getSaveFileName(
                     locateLocal( "data", TQString( tdeApp->name() ) + "/csv-templates/" ),
                     "*.desktop", this );

  if ( fileName.isEmpty() )
    return;

  if ( !fileName.contains( ".desktop" ) )
    fileName += ".desktop";

  if( TQFileInfo(fileName).exists() ) {                                                                                                   
      if(KMessageBox::questionYesNo( this, i18n("Do you want to overwrite file \"%1\"").arg(fileName) ) == KMessageBox::No)        
        return;                                                                                                                      
  }
  TQString name = KInputDialog::getText( i18n( "Template Name" ), i18n( "Please enter a name for the template:" ) );

  if ( name.isEmpty() )
    return;

  TDEConfig config( fileName );
  config.setGroup( "General" );
  config.writeEntry( "DatePattern", mDatePatternEdit->text() );
  config.writeEntry( "Columns", mTable->numCols() );
  config.writeEntry( "DelimiterType", mDelimiterBox->id( mDelimiterBox->selected() ) );
  config.writeEntry( "DelimiterOther", mDelimiterEdit->text() );
  config.writeEntry( "QuoteType", mComboQuote->currentItem() );

  config.setGroup( "Misc" );
  config.writeEntry( "Name", name );

  config.setGroup( "csv column map" );

  for ( int column = 0; column < mTable->numCols(); ++column ) {
    TQComboTableItem *item = static_cast<TQComboTableItem*>( mTable->item( 0,
                                                           column ) );
    if ( item )
      config.writeEntry( TQString::number( column ), posToType(
                         item->currentItem() ) );
    else
      config.writeEntry( TQString::number( column ), 0 );
  }

  config.sync();
}

TQString CSVImportDialog::getText( int row, int col )
{
  return mTable->text( row, col );
}

uint CSVImportDialog::posToType( int pos ) const
{
  uint counter = 0;
  TQMap<TQString, uint>::ConstIterator it;
  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
    if ( counter == (uint)pos )
      return it.data();

  return 0;
}

int CSVImportDialog::typeToPos( uint type ) const
{
  uint counter = 0;
  TQMap<TQString, uint>::ConstIterator it;
  for ( it = mTypeMap.begin(); it != mTypeMap.end(); ++it, ++counter )
    if ( it.data() == type )
      return counter;

  return -1;
}

void CSVImportDialog::ignoreDuplicatesChanged( int )
{
  fillTable();
}

void CSVImportDialog::setFile( const TQString &fileName )
{
  if ( fileName.isEmpty() )
    return;

  TQFile file( fileName );
  if ( !file.open( IO_ReadOnly ) ) {
    KMessageBox::sorry( this, i18n( "Cannot open input file." ) );
    file.close();
    return;
  }

  mFileArray = file.readAll();
  file.close();

  mClearTypeStore = true;
  clearTable();
  mTable->setNumCols( 0 );
  mTable->setNumRows( 0 );
  fillTable();
  mClearTypeStore = false;

  fillComboBox();
}

void CSVImportDialog::urlChanged( const TQString &file )
{
  bool state = !file.isEmpty();

  enableButtonOK( state );
  actionButton( User1 )->setEnabled( state );
  actionButton( User2 )->setEnabled( state );
}

void CSVImportDialog::codecChanged()
{
  fillTable();
}

#include <csvimportdialog.moc>
