/* This file is part of the KDE project
   Copyright (C) 1999 David Faure <faure@kde.org>
   Copyright (C) 2004 Nicolas GOUTTE <goutte@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 <csvimportdialogui.h>
#include <csvimportdialog.h>

#include <tqtable.h>
#include <tqcheckbox.h>
#include <tqcursor.h>
#include <tqlineedit.h>
#include <tqcombobox.h>
#include <tqspinbox.h>
#include <tqtextstream.h>
#include <tqbuttongroup.h>
#include <tqpushbutton.h>
#include <tqradiobutton.h>
#include <tqtextcodec.h>

#include <tdeapplication.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <kcombobox.h>
#include <tdemessagebox.h>
#include <kcharsets.h>


CSVImportDialog::CSVImportDialog(TQWidget* parent, TQByteArray& fileArray)
    : KDialogBase(parent, 0, true, TQString(), Ok|Cancel, No, true),
      m_dialog(new DialogUI(this)),
      m_adjustRows(false),
      m_adjustCols(false),
      m_startRow(0),
      m_startCol(0),
      m_endRow(-1),
      m_endCol(-1),
      m_textquote('"'),
      m_delimiter(","),
      m_ignoreDups(false),
      m_fileArray(fileArray),
      m_codec( TQTextCodec::codecForName( "UTF-8" ) )
{
    setCaption( i18n( "Import Data" ) );
    kapp->restoreOverrideCursor();

    TQStringList encodings;
    encodings << i18n( "Descriptive encoding name", "Recommended ( %1 )" ).arg( "UTF-8" );
    encodings << i18n( "Descriptive encoding name", "Locale ( %1 )" ).arg( TQTextCodec::codecForLocale()->name() );
    encodings += TDEGlobal::charsets()->descriptiveEncodingNames();
    // Add a few non-standard encodings, which might be useful for text files
    const TQString description(i18n("Descriptive encoding name","Other ( %1 )"));
    encodings << description.arg("Apple Roman"); // Apple
    encodings << description.arg("IBM 850") << description.arg("IBM 866"); // MS DOS
    encodings << description.arg("CP 1258"); // Windows
    m_dialog->comboBoxEncoding->insertStringList(encodings);

    m_formatList << i18n( "Text" );
    m_formatList << i18n( "Number" );
    //m_formatList << i18n( "Currency" );
    //m_formatList << i18n( "Date" );
    m_formatList << i18n( "Decimal Comma Number" );
    m_formatList << i18n( "Decimal Point Number" );
    m_dialog->m_formatComboBox->insertStringList( m_formatList );

    m_dialog->m_sheet->setReadOnly( true );

    fillTable();

    //resize(sizeHint());
    resize( 600, 400 ); // Try to show as much as possible of the table view
    setMainWidget(m_dialog);

    m_dialog->m_sheet->setSelectionMode( TQTable::Multi );

    connect(m_dialog->m_formatComboBox, TQT_SIGNAL(activated( const TQString& )),
            this, TQT_SLOT(formatChanged( const TQString& )));
    connect(m_dialog->m_delimiterBox, TQT_SIGNAL(clicked(int)),
            this, TQT_SLOT(delimiterClicked(int)));
    connect(m_dialog->m_delimiterEdit, TQT_SIGNAL(returnPressed()),
            this, TQT_SLOT(returnPressed()));
    connect(m_dialog->m_delimiterEdit, TQT_SIGNAL(textChanged ( const TQString & )),
            this, TQT_SLOT(formatChanged ( const TQString & ) ));
    connect(m_dialog->m_comboQuote, TQT_SIGNAL(activated(const TQString &)),
            this, TQT_SLOT(textquoteSelected(const TQString &)));
    connect(m_dialog->m_sheet, TQT_SIGNAL(currentChanged(int, int)),
            this, TQT_SLOT(currentCellChanged(int, int)));
    connect(m_dialog->m_ignoreDuplicates, TQT_SIGNAL(stateChanged(int)),
            this, TQT_SLOT(ignoreDuplicatesChanged(int)));
    connect(m_dialog->m_updateButton, TQT_SIGNAL(clicked()),
            this, TQT_SLOT(updateClicked()));
    connect(m_dialog->comboBoxEncoding, TQT_SIGNAL(textChanged ( const TQString & )),
            this, TQT_SLOT(encodingChanged ( const TQString & ) ));
}


CSVImportDialog::~CSVImportDialog()
{
    kapp->setOverrideCursor(TQt::waitCursor);
}


// ----------------------------------------------------------------
//                       public methods


bool CSVImportDialog::firstRowContainHeaders()
{
    return m_dialog->m_firstRowHeader->isChecked();
}


bool CSVImportDialog::firstColContainHeaders()
{
    return m_dialog->m_firstColHeader->isChecked();
}


int CSVImportDialog::rows()
{
    int rows = m_dialog->m_sheet->numRows();

    if ( m_endRow >= 0 )
	rows = m_endRow - m_startRow + 1;

    return rows;
}


int CSVImportDialog::cols()
{
    int cols = m_dialog->m_sheet->numCols();

    if ( m_endCol >= 0 )
	cols = m_endCol - m_startCol + 1;

    return cols;
}


TQString CSVImportDialog::text(int row, int col)
{
    // Check for overflow.
    if ( row >= rows() || col >= cols())
	return TQString();

    return m_dialog->m_sheet->text( row - m_startRow, col - m_startCol );
}


// ----------------------------------------------------------------


void CSVImportDialog::fillTable( )
{
    int row, column;
    bool lastCharDelimiter = false;
    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;

    kapp->setOverrideCursor(TQt::waitCursor);

    for (row = 0; row < m_dialog->m_sheet->numRows(); ++row)
        for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
            m_dialog->m_sheet->clearCell(row, column);

    int maxColumn = 1;
    row = column = 1;
    TQTextStream inputStream(m_fileArray, IO_ReadOnly);
    kdDebug(30501) << "Encoding: " << m_codec->name() << endl;
    inputStream.setCodec( m_codec );

    bool lastCharWasCr = false; // Last character was a Carriage Return
    while (!inputStream.atEnd()) 
    {
        inputStream >> x; // read one char

        // ### TODO: we should perhaps skip all other control characters
        if ( x == '\r' )
        {
            // We have a Carriage Return, assume that its role is the one of a LineFeed
            lastCharWasCr = true;
            x = '\n'; // Replace by Line Feed
        }
        else if ( x == '\n' && lastCharWasCr )
        {
            // The end of line was already handled by the Carriage Return, so do nothing for this character
            lastCharWasCr = false;
            continue;
        }
        else if ( x == TQChar( 0xc ) )
        {
            // We have a FormFeed, skip it
            lastCharWasCr = false;
            continue;
        }
        else
        {
            lastCharWasCr = false;
        }

        if ( column > maxColumn )
          maxColumn = column;

        switch (state)
        {
         case S_START :
            if (x == m_textquote)
            {
                state = S_QUOTED_FIELD;
            }
            else if (x == m_delimiter)
            {
                if ((m_ignoreDups == false) || (lastCharDelimiter == false))
                    ++column;
                lastCharDelimiter = true;
            }
            else if (x == '\n')
            {
                ++row;
                column = 1;
                if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
                  break;
            }
            else
            {
                field += x;
                state = S_MAYBE_NORMAL_FIELD;
            }
            break;
         case S_QUOTED_FIELD :
            if (x == m_textquote)
            {
                state = S_MAYBE_END_OF_QUOTED_FIELD;
            }
            else if (x == '\n')
            {
                setText(row - m_startRow, column - m_startCol, field);
                field = TQString();

                ++row;
                column = 1;
                if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
                  break;

                state = S_START;
            }
            else
            {
                field += x;
            }
            break;
         case S_MAYBE_END_OF_QUOTED_FIELD :
            if (x == m_textquote)
            {
                field += x;
                state = S_QUOTED_FIELD;
            }
            else if (x == m_delimiter || x == '\n')
            {
                setText(row - m_startRow, column - m_startCol, field);
                field = TQString();
                if (x == '\n')
                {
                    ++row;
                    column = 1;
                    if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
                      break;
                }
                else
                {
                    if ((m_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 == m_delimiter || x == '\n')
            {
                setText(row - m_startRow, column - m_startCol, field);
                field = TQString();
                if (x == '\n')
                {
                    ++row;
                    column = 1;
                    if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
                      break;
                }
                else
                {
                    if ((m_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 == m_textquote)
            {
                field = TQString();
                state = S_QUOTED_FIELD;
                break;
            }
         case S_NORMAL_FIELD :
            if (x == m_delimiter || x == '\n')
            {
                setText(row - m_startRow, column - m_startCol, field);
                field = TQString();
                if (x == '\n')
                {
                    ++row;
                    column = 1;
                    if ( row > ( m_endRow - m_startRow ) && m_endRow >= 0 )
                      break;
                }
                else
                {
                    if ((m_ignoreDups == false) || (lastCharDelimiter == false))
                        ++column;
                    lastCharDelimiter = true;
                }
                state = S_START;
            }
            else
            {
                field += x;
            }
        }
        if (x != m_delimiter)
          lastCharDelimiter = false;
    }

    if ( !field.isEmpty() )
    {
      // the last line of the file had not any line end
      setText(row - m_startRow, column - m_startCol, field);
      ++row;
      field = TQString();
    }
    
    m_adjustCols = true;
    adjustRows( row - m_startRow );
    adjustCols( maxColumn - m_startCol );
    m_dialog->m_colEnd->setMaxValue( maxColumn );
    if ( m_endCol == -1 )
      m_dialog->m_colEnd->setValue( maxColumn );
    

    for (column = 0; column < m_dialog->m_sheet->numCols(); ++column)
    {
        const TQString header = m_dialog->m_sheet->horizontalHeader()->label(column);
        if ( m_formatList.find( header ) == m_formatList.end() )
            m_dialog->m_sheet->horizontalHeader()->setLabel(column, i18n("Text"));

        m_dialog->m_sheet->adjustColumn(column);
    }
    fillComboBox();

    kapp->restoreOverrideCursor();
}

void CSVImportDialog::fillComboBox()
{
  if ( m_endRow == -1 )
    m_dialog->m_rowEnd->setValue( m_dialog->m_sheet->numRows() );  
  else
    m_dialog->m_rowEnd->setValue( m_endRow );

  if ( m_endCol == -1 )
    m_dialog->m_colEnd->setValue( m_dialog->m_sheet->numCols() );
  else
    m_dialog->m_colEnd->setValue( m_endCol );  

  m_dialog->m_rowEnd->setMinValue( 1 );
  m_dialog->m_colEnd->setMinValue( 1 );
  m_dialog->m_rowEnd->setMaxValue( m_dialog->m_sheet->numRows() );
  m_dialog->m_colEnd->setMaxValue( m_dialog->m_sheet->numCols() );

  m_dialog->m_rowStart->setMinValue( 1 );
  m_dialog->m_colStart->setMinValue( 1 );
  m_dialog->m_rowStart->setMaxValue( m_dialog->m_sheet->numRows() );
  m_dialog->m_colStart->setMaxValue( m_dialog->m_sheet->numCols() );
}

int CSVImportDialog::headerType(int col)
{
    TQString header = m_dialog->m_sheet->horizontalHeader()->label(col);
    
    if (header == i18n("Text"))
        return TEXT;
    else if (header == i18n("Number"))
        return NUMBER;
    else if (header == i18n("Currency"))
        return CURRENCY;
    else if ( header == i18n( "Date" ) )
        return DATE;
    else if ( header == i18n( "Decimal Comma Number" ) )
        return COMMANUMBER;
    else if ( header == i18n( "Decimal Point Number" ) )
        return POINTNUMBER;
    else
        return TEXT; // Should not happen
}

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

    if ( ( row > ( m_endRow - m_startRow ) && m_endRow > 0 ) || ( col > ( m_endCol - m_startCol ) && m_endCol > 0 ) )
      return;

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

    if ( m_dialog->m_sheet->numCols() < col )
    {
        m_dialog->m_sheet->setNumCols( col );
        m_adjustCols = true;
    }

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

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

void CSVImportDialog::adjustCols(int iCols)
{
    if (m_adjustCols) 
    {  
        m_dialog->m_sheet->setNumCols( iCols );
        m_adjustCols = false;

        if ( m_endCol == -1 )
        {
          if ( iCols > ( m_endCol - m_startCol ) )
            iCols = m_endCol - m_startCol;

          m_dialog->m_sheet->setNumCols( iCols );
        }
    }
}

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

    m_delimiter = m_dialog->m_delimiterEdit->text();
    fillTable();
}

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

void CSVImportDialog::formatChanged( const TQString& newValue )
{
    //kdDebug(30501) << "CSVImportDialog::formatChanged:" << newValue << endl;
    for ( int i = 0; i < m_dialog->m_sheet->numSelections(); ++i )
    {
        TQTableSelection select ( m_dialog->m_sheet->selection( i ) );
        for ( int j = select.leftCol(); j <= select.rightCol() ; ++j )
        {
            m_dialog->m_sheet->horizontalHeader()->setLabel( j, newValue );
            
        }
    }
}

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

    fillTable();
}

void CSVImportDialog::textquoteSelected(const TQString& mark)
{
    if (mark == i18n("None"))
        m_textquote = 0;
    else
        m_textquote = mark[0];

    fillTable();
}

void CSVImportDialog::updateClicked()
{
  if ( !checkUpdateRange() )
    return;

  m_startRow = m_dialog->m_rowStart->value() - 1;
  m_endRow   = m_dialog->m_rowEnd->value();

  m_startCol  = m_dialog->m_colStart->value() - 1;
  m_endCol    = m_dialog->m_colEnd->value();

  fillTable();
}

bool CSVImportDialog::checkUpdateRange()
{
  if ( ( m_dialog->m_rowStart->value() > m_dialog->m_rowEnd->value() ) 
       || ( m_dialog->m_colStart->value() > m_dialog->m_colEnd->value() ) )
  {
    KMessageBox::error( this, i18n( "Please check the ranges you specified. The start value must be lower than the end value." ) );
    return false;
  }

  return true;
}

void CSVImportDialog::currentCellChanged(int, int col)
{
    const TQString header = m_dialog->m_sheet->horizontalHeader()->label(col);
    m_dialog->m_formatComboBox->setCurrentText( header );
}

void CSVImportDialog::ignoreDuplicatesChanged(int)
{
  if (m_dialog->m_ignoreDuplicates->isChecked())
    m_ignoreDups = true;
  else
    m_ignoreDups = false;
  fillTable();
}

TQTextCodec* CSVImportDialog::getCodec(void) const
{
    const TQString strCodec( TDEGlobal::charsets()->encodingForName( m_dialog->comboBoxEncoding->currentText() ) );
    kdDebug(30502) << "Encoding: " << strCodec << endl;

    bool ok = false;
    TQTextCodec* codec = TQTextCodec::codecForName( strCodec.utf8() );

    // If TQTextCodec has not found a valid encoding, so try with KCharsets.
    if ( codec )
    {
        ok = true;
    }
    else
    {
        codec = TDEGlobal::charsets()->codecForName( strCodec, ok );
    }

    // Still nothing?
    if ( !codec || !ok )
    {
        // Default: UTF-8
        kdWarning(30502) << "Cannot find encoding:" << strCodec << endl;
        // ### TODO: what parent to use?
        KMessageBox::error( 0, i18n("Cannot find encoding: %1").arg( strCodec ) );
        return 0;
    }

    return codec;
}

void CSVImportDialog::encodingChanged ( const TQString & )
{
    TQTextCodec* codec = getCodec();

    if ( codec )
    {
        m_codec = codec;
        fillTable();
    }
}


#include <csvimportdialog.moc>