/*
    Copyright (c) 2009 Tobias Koenig <tokoe@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 "qcsvreader.h"

#include <QtCore/QStringList>
#include <QtCore/QTextCodec>
#include <QtCore/QTextStream>

QCsvBuilderInterface::~QCsvBuilderInterface()
{
}

class QCsvReader::Private
{
  public:
    Private( QCsvBuilderInterface *builder )
      : mBuilder( builder ), mNotTerminated( true )
    {
      mTextQuote = QChar( '"' );
      mDelimiter = QChar( ' ' );
      mStartRow = 0;
      mCodec = QTextCodec::codecForLocale();
    }

    void emitBeginLine( uint row );
    void emitEndLine( uint row );
    void emitField( const QString &data, int row, int column );

    QCsvBuilderInterface *mBuilder;
    QTextCodec *mCodec;
    QChar mTextQuote;
    QChar mDelimiter;

    uint mStartRow;
    bool mNotTerminated;
};

void QCsvReader::Private::emitBeginLine( uint row )
{
  if ( (row - mStartRow) > 0 )
    mBuilder->beginLine();
}

void QCsvReader::Private::emitEndLine( uint row )
{
  if ( (row - mStartRow) > 0 )
    mBuilder->endLine();
}

void QCsvReader::Private::emitField( const QString &data, int row, int column )
{
  if ( (row - mStartRow) > 0 )
    mBuilder->field( data, row - mStartRow - 1, column - 1 );
}

QCsvReader::QCsvReader( QCsvBuilderInterface *builder )
  : d( new Private( builder ) )
{
  Q_ASSERT( builder );
}

QCsvReader::~QCsvReader()
{
  delete d;
}

bool QCsvReader::read( QIODevice *device )
{
  enum State
  {
    StartLine,
    QuotedField,
    QuotedFieldEnd,
    NormalField,
    EmptyField
  };

  int row, column;

  QString field;
  QChar input;
  State currentState = StartLine;

  row = column = 1;

  d->mBuilder->begin();

  if ( !device->isOpen() ) {
    d->emitBeginLine( row );
    d->mBuilder->error( "Device is not open" );
    d->emitEndLine( row );
    d->mBuilder->end();
    return false;
  }

  QTextStream inputStream( device );
  inputStream.setCodec( d->mCodec );

  /**
   * We use the following state machine to parse CSV:
   *
   * digraph {
   *   StartLine -> StartLine [label="\\r\\n"]
   *   StartLine -> QuotedField [label="Quote"]
   *   StartLine -> EmptyField [label="Delimiter"]
   *   StartLine -> NormalField [label="Other Char"]
   *
   *   QuotedField -> QuotedField [label="\\r\\n"]
   *   QuotedField -> QuotedFieldEnd [label="Quote"]
   *   QuotedField -> QuotedField [label="Delimiter"]
   *   QuotedField -> QuotedField [label="Other Char"]
   *
   *   QuotedFieldEnd -> StartLine [label="\\r\\n"]
   *   QuotedFieldEnd -> QuotedField [label="Quote"]
   *   QuotedFieldEnd -> EmptyField [label="Delimiter"]
   *   QuotedFieldEnd -> EmptyField [label="Other Char"]
   *
   *   EmptyField -> StartLine [label="\\r\\n"]
   *   EmptyField -> QuotedField [label="Quote"]
   *   EmptyField -> EmptyField [label="Delimiter"]
   *   EmptyField -> NormalField [label="Other Char"]
   *
   *   NormalField -> StartLine [label="\\r\\n"]
   *   NormalField -> NormalField [label="Quote"]
   *   NormalField -> EmptyField [label="Delimiter"]
   *   NormalField -> NormalField [label="Other Char"]
   * }
   */

  while ( !inputStream.atEnd() && d->mNotTerminated ) {
    inputStream >> input;

    switch ( currentState ) {
      case StartLine:
        if ( input == QLatin1Char( '\r' ) || input == QLatin1Char( '\n' ) ) {
          currentState = StartLine;
        } else if ( input == d->mTextQuote ) {
          d->emitBeginLine( row );
          currentState = QuotedField;
        } else if ( input == d->mDelimiter ) {
          d->emitBeginLine( row );
          d->emitField( field, row, column );
          column++;
          currentState = EmptyField;
        } else {
          d->emitBeginLine( row );
          field.append( input );
          currentState = NormalField;
        }
        break;
      case QuotedField:
        if ( input == QLatin1Char( '\r' ) || input == QLatin1Char( '\n' ) ) {
          field.append( input );
          currentState = QuotedField;
        } else if ( input == d->mTextQuote ) {
          currentState = QuotedFieldEnd;
        } else if ( input == d->mDelimiter ) {
          field.append( input );
          currentState = QuotedField;
        } else {
          field.append( input );
          currentState = QuotedField;
        }
        break;
      case QuotedFieldEnd:
        if ( input == QLatin1Char( '\r' ) || input == QLatin1Char( '\n' ) ) {
          d->emitField( field, row, column );
          field.clear();
          d->emitEndLine( row );
          column = 1;
          row++;
          currentState = StartLine;
        } else if ( input == d->mTextQuote ) {
          field.append( input );
          currentState = QuotedField;
        } else if ( input == d->mDelimiter ) {
          d->emitField( field, row, column );
          field.clear();
          column++;
          currentState = EmptyField;
        } else {
          d->emitField( field, row, column );
          field.clear();
          column++;
          field.append( input );
          currentState = EmptyField;
        }
        break;
      case NormalField:
        if ( input == QLatin1Char( '\r' ) || input == QLatin1Char( '\n' ) ) {
          d->emitField( field, row, column );
          field.clear();
          d->emitEndLine( row );
          row++;
          column = 1;
          currentState = StartLine;
        } else if ( input == d->mTextQuote ) {
          field.append( input );
          currentState = NormalField;
        } else if ( input == d->mDelimiter ) {
          d->emitField( field, row, column );
          field.clear();
          column++;
          currentState = EmptyField;
        } else {
          field.append( input );
          currentState = NormalField;
        }
        break;
      case EmptyField:
        if ( input == QLatin1Char( '\r' ) || input == QLatin1Char( '\n' ) ) {
          d->emitField( QString(), row, column );
          field.clear();
          d->emitEndLine( row );
          column = 1;
          row++;
          currentState = StartLine;
        } else if ( input == d->mTextQuote ) {
          currentState = QuotedField;
        } else if ( input == d->mDelimiter ) {
          d->emitField( QString(), row, column );
          column++;
          currentState = EmptyField;
        } else {
          field.append( input );
          currentState = NormalField;
        }
        break;
    }
  }

  if ( currentState != StartLine ) {
    if ( field.length() > 0 ) {
      d->emitField( field, row, column );
      ++row;
      field.clear();
    }
    d->emitEndLine( row );
  }

  d->mBuilder->end();

  return true;
}

void QCsvReader::setTextQuote( const QChar &textQuote )
{
  d->mTextQuote = textQuote;
}

QChar QCsvReader::textQuote() const
{
  return d->mTextQuote;
}

void QCsvReader::setDelimiter( const QChar &delimiter )
{
  d->mDelimiter = delimiter;
}

QChar QCsvReader::delimiter() const
{
  return d->mDelimiter;
}

void QCsvReader::setStartRow( uint startRow )
{
  d->mStartRow = startRow;
}

uint QCsvReader::startRow() const
{
  return d->mStartRow;
}

void QCsvReader::setTextCodec( QTextCodec *textCodec )
{
  d->mCodec = textCodec;
}

QTextCodec *QCsvReader::textCodec() const
{
  return d->mCodec;
}

void QCsvReader::terminate()
{
  d->mNotTerminated = false;
}

class QCsvStandardBuilder::Private
{
  public:
    Private()
    {
      init();
    }

    void init();

    QString mLastErrorString;
    uint mRowCount;
    uint mColumnCount;
    QList<QStringList> mRows;
};

void QCsvStandardBuilder::Private::init()
{
  mRows.clear();
  mRowCount = 0;
  mColumnCount = 0;
  mLastErrorString.clear();
}

QCsvStandardBuilder::QCsvStandardBuilder()
  : d( new Private )
{
}

QCsvStandardBuilder::~QCsvStandardBuilder()
{
  delete d;
}

QString QCsvStandardBuilder::lastErrorString() const
{
  return d->mLastErrorString;
}

uint QCsvStandardBuilder::rowCount() const
{
  return d->mRowCount;
}

uint QCsvStandardBuilder::columnCount() const
{
  return d->mColumnCount;
}

QString QCsvStandardBuilder::data( uint row, uint column ) const
{
  if ( row > d->mRowCount || column > d->mColumnCount || column >= (uint)d->mRows[ row ].count() )
    return QString();

  return d->mRows[ row ][ column ];
}

void QCsvStandardBuilder::begin()
{
  d->init();
}

void QCsvStandardBuilder::beginLine()
{
  d->mRows.append( QStringList() );
  d->mRowCount++;
}

void QCsvStandardBuilder::field( const QString &data, uint row, uint column )
{
  const uint size = d->mRows[ row ].size();
  if ( column >= size ) {
    for ( uint i = column; i < size + 1; ++i )
      d->mRows[ row ].append( QString() );
  }

  d->mRows[ row ][ column ] = data;

  d->mColumnCount = qMax( d->mColumnCount, column + 1 );
}

void QCsvStandardBuilder::endLine()
{
}

void QCsvStandardBuilder::end()
{
}

void QCsvStandardBuilder::error( const QString &errorMsg )
{
  d->mLastErrorString = errorMsg;
}

#include "qcsvreader.moc"
