/**
 * \file odsstream/tsvreader.cpp
 * \date 11/11/2018
 * \author Olivier Langella
 * \brief parser for text files (tsv, csv)
 */

/*******************************************************************************
 * Copyright (c) 2018 Olivier Langella <Olivier.Langella@u-psud.fr>.
 *
 * This file is part of the libodsstream library.
 *
 *    libodsstream is a library to read and write ODS documents as streams
 *    Copyright (C) 2018  Olivier Langella <Olivier.Langella@u-psud.fr>
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as published
 *by the Free Software Foundation, either version 3 of the License, or (at your
 *option) any later version.
 *
 *    This program 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 Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contributors:
 *     Olivier Langella <Olivier.Langella@u-psud.fr> - initial API and
 *implementation
 ******************************************************************************/

#include "tsvreader.h"
#include "odsexception.h"
#include <QFileInfo>
#include <QChar>
#include <QDebug>
#include "reader/odscell.h"

TsvReader::TsvReader(OdsDocHandlerInterface &handler) : m_handler(handler)
{
}

TsvReader::~TsvReader()
{
}
void
TsvReader::setSeparator(TsvSeparator separator)
{

  switch(separator)
    {
      case TsvSeparator::tab:
        m_separator = '\t';
        break;
      case TsvSeparator::comma:
        m_separator = ',';
        break;
      case TsvSeparator::semicolon:
        m_separator = ';';
        break;
    }
}

void
TsvReader::parse(QFile &tsvFile)
{
  qDebug() << tsvFile.fileName();
  QFileInfo tsv_file_info(tsvFile.fileName());
  if(!tsv_file_info.exists())
    {
      throw OdsException(QObject::tr("TSV file %1 does not exists")
                           .arg(tsv_file_info.absoluteFilePath()));
    }
  if(!tsv_file_info.isReadable())
    {
      throw OdsException(QObject::tr("TSV file %1 is not readable")
                           .arg(tsv_file_info.absoluteFilePath()));
    }
  qDebug() << tsv_file_info.absoluteFilePath();
  m_handler.startSheet(tsv_file_info.baseName());
  if(!tsvFile.open(QIODevice::ReadOnly | QFile::Text))
    {
      throw OdsException(QObject::tr("Unable to read TSV file %1")
                           .arg(tsv_file_info.absoluteFilePath()));
    }
  QTextStream textStreamInput(&tsvFile);
  parse(textStreamInput);
  tsvFile.close();
  qDebug();
}

void
TsvReader::parse(QIODevice *p_inputstream)
{

  QTextStream textStreamInput(p_inputstream);
  while(readCsvRow(textStreamInput))
    {
    }
  m_handler.endDocument();
}

void
TsvReader::parse(QTextStream &textStreamInput)
{
  while(readCsvRow(textStreamInput))
    {
    }
  m_handler.endDocument();
}

bool
TsvReader::readCsvRow(QTextStream &textStreamInput)
{
  qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  m_columnNumber = 0;
  m_handler.startLine();
  m_rowNumber++;
  static const int delta[][5] = {
    //  ,    "   \n    ?  eof
    {1, 2, -1, 0, -1}, // 0: parsing (store char)
    {1, 2, -1, 0, -1}, // 1: parsing (store column)
    {3, 4, 3, 3, -2},  // 2: quote entered (no-op)
    {3, 4, 3, 3, -2},  // 3: parsing inside quotes (store char)
    {1, 3, -1, 0, -1}, // 4: quote exited (no-op)
                       // -1: end of row, store column, success
                       // -2: eof inside quotes
  };
  qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  if(textStreamInput.atEnd())
    {
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
      return false;
    }

  int state = 0, t, previous_state;
  QChar ch;
  QString cell;

  try
    {
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
      while(state >= 0)
        {
          qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__ << " "
                   << cell;
          if(textStreamInput.atEnd())
            t = 4;
          else
            {
              textStreamInput >> ch;
              if(ch == m_separator)
                t = 0;
              else if(ch == '\"')
                t = 1;
              else if(ch == '\n')
                t = 2;
              else
                t = 3;
            }

          previous_state = state;
          state          = delta[state][t];

          switch(state)
            {
              case 0:
              case 3:
                cell += ch;
                break;
              case -1:
              case 1:
                OdsCell odsCell;
                m_columnNumber++;
                qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__
                         << " " << cell;
                if((previous_state == 2) || (previous_state == 3))
                  {
                    odsCell.setOfficeValueType("string");
                    odsCell.setValueString(cell);
                  }
                else
                  {
                    odsCell.setValueOfUndefinedType(cell);
                  }
                m_handler.setCell(odsCell);
                cell = "";
                break;
            }
        }


      if(state == -2)
        throw OdsException(
          QObject::tr("End-of-file found while inside quotes."));
    }
  catch(OdsException &error)
    {
      throw OdsException(QObject::tr("Text parse error at line %1, column %2:\n"
                                     "%3")
                           .arg(m_rowNumber)
                           .arg(m_columnNumber)
                           .arg(error.qwhat()));
    }

  catch(std::exception &error)
    {
      throw OdsException(QObject::tr("Text parse error at line %1, column %2:\n"
                                     "%3")
                           .arg(m_rowNumber)
                           .arg(m_columnNumber)
                           .arg(error.what()));
    }

  m_handler.endLine();
  qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  return true;
}
