/*
    libodsstream is a library to read and write ODS documents as streams
    Copyright (C) 2017  Olivier Langella <Olivier.Langella@moulon.inra.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/>.

*/
/**
 * \file ods2csv.cpp
 * \date 12/1/2017
 * \author Olivier Langella
 * \brief convert ods files to TSV
 */

#include "ods2csv.h"
#include <QCommandLineParser>
#include <QDateTime>
#include <QTimer>
#include <QFile>
#include <QFileInfo>
#include <QBuffer>
//#include <QDataStream>
#include <odsstream/odsdocreader.h>
#include <odsstream/odsexception.h>
#include <odsstream/tsvdirectorywriter.h>
#include <odsstream/tsvoutputstream.h>

class CustomHandler : public OdsDocHandlerInterface
{
  public:
  CustomHandler()
  {
  }

  ~CustomHandler()
  {
    delete _p_writer;
  }


  void
  setOutputDirectory(const QString &output_directory)
  {
    QFileInfo fi(output_directory);
    _p_writer = new TsvDirectoryWriter(QDir(output_directory));
    _p_writer->setSeparator(_separator);
  }

  void
  setOutputStream(QTextStream &output_stream)
  {
    TsvOutputStream *p_tsv = new TsvOutputStream(output_stream);

    p_tsv->setNoSheetName(true);
    _p_writer = p_tsv;
    _p_writer->setSeparator(_separator);
    _p_writer->setQuoteStrings(m_quoteStrings);
  }

  void
  extractSheet(const QString &sheetname)
  {
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    _sheetname = sheetname;
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  }

  void
  setSeparator(TsvSeparator separator)
  {
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
    _separator = separator;
    qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
  }


  void
  setQuoteStrings(bool quote_strings)
  {
    m_quoteStrings = quote_strings;
  }
  /**
   * callback that indicates the begining of a data sheet. Override it in
   * order to retrieve information about the current data sheet.
   *
   */
  virtual void
  startSheet(const QString &sheet_name)
  {
    _extract = false;
    if(_sheetname.isEmpty())
      {
        _extract = true;
      }
    else if(_sheetname == sheet_name)
      {
        _extract = true;
      }
    if(_extract)
      {
        _p_writer->writeSheet(sheet_name);
      }
  };

  /**
   * callback that indicates the end of the current data sheet. Override it if
   * needed
   */
  virtual void endSheet(){
    //_p_writer-> endSheet();
  };

  /**
   * callback that indicates a new line start. Override it if needed.
   */

  virtual void startLine(){
    //_p_writer-> writeLine();
  };

  /**
   * callback that indicates a line ending. Override it if needed.
   */

  virtual void
  endLine()
  {
    if(_extract)
      {
        _p_writer->writeLine();
      }
  };

  /**
   * callback that report the content of the current cell in a dedicated Cell
   * object. Override it if you need to retrieve cell content.
   */
  virtual void
  setCell(const OdsCell &cell)
  {
    if(_extract)
      {
        qDebug() << "CustomHandler::setCell " << cell.toString();
        if(cell.isBoolean())
          {
            _p_writer->writeCell(cell.getBooleanValue());
          }
        else if(cell.isDate())
          {
            _p_writer->writeCell(cell.getDateTimeValue());
          }
        else if(cell.isDouble())
          {
            _p_writer->writeCell(cell.getDoubleValue());
          }
        else if(cell.isEmpty())
          {
            _p_writer->writeEmptyCell();
          }
        else
          {
            _p_writer->writeCell(cell.toString());
          }
      }
  };

  /**
   * callback that report the end of the ODS document. Override it if you need
   * to know that reading is finished.
   */
  virtual void
  endDocument()
  {
    _p_writer->close();
  };

  private:
  TsvDirectoryWriter *_p_writer = nullptr;
  QString _sheetname;
  bool _extract           = true;
  TsvSeparator _separator = TsvSeparator::tab;
  bool m_quoteStrings     = false;
};

Ods2Csv::Ods2Csv(QObject *parent) : QObject(parent)
{
  // get the instance of the main application
  app = QCoreApplication::instance();
  // setup everything here
  // create any global objects
  // setup debug and warning mode
}

// 10ms after the application starts this method will run
// all QT messaging is running at this point so threads, signals and slots
// will all work as expected.
void
Ods2Csv::run()
{

  QTextStream errorStream(stderr, QIODevice::WriteOnly);
  QTextStream outputStream(stdout, QIODevice::WriteOnly);

  try
    {
      qDebug() << "Ods2Csv::run() begin";
      QCommandLineParser parser;

      // throw pappso::PappsoException("test");
      parser.setApplicationDescription(
        tr("ods2tsv converts Open Document Spreadsheet to TSV files in a "
           "directory\nif no ODS file is given, ods2tsv reads the standard "
           "input\nif no directory is given, ods2tsv writes TSV to standard "
           "output"));
      parser.addHelpOption();
      parser.addVersionOption();
      parser.addPositionalArgument(
        "ODS file",
        QCoreApplication::translate("main", "ODS file to convert."));

      QCommandLineOption quoteStringsOption(
        QStringList() << "q"
                      << "quote-strings",
        tr("surround each text value with quotes"));
      parser.addOption(quoteStringsOption);

      QCommandLineOption sheetOption(QStringList() << "sheet",
                                     tr("sheet name to extract from ODS file"),
                                     "sheet name");
      parser.addOption(sheetOption);

      QCommandLineOption separatorOption(
        QStringList() << "s"
                      << "separator",
        tr("separator character to use. Please choose one of : tab (default), "
           "comma, semicolon"),
        "separator",
        "tab");
      parser.addOption(separatorOption);

      QCommandLineOption directoryOption(
        QStringList() << "d"
                      << "directory",
        tr("output directory containing TSV files (one for each sheet)"),
        "directory");
      parser.addOption(directoryOption);


      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
      // Process the actual command line arguments given by the user
      parser.process(*app);

      TsvSeparator separator = TsvSeparator::tab;
      QString separator_str  = parser.value(separatorOption);
      if(separator_str == "comma")
        {
          separator = TsvSeparator::comma;
        }
      else
        {
          if(separator_str == "semicolon")
            {
              separator = TsvSeparator::semicolon;
            }
        }
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;
      QString directory = parser.value(directoryOption);

      QString sheetname = parser.value(sheetOption);
      // QCoreApplication * app(this);
      // Add your main code here
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;

      QString ods_file;
      QStringList ods_list = parser.positionalArguments();
      if(ods_list.size() > 0)
        {
          ods_file = ods_list[0];
          QFileInfo fi(ods_file);
          if(!fi.exists())
            {
              throw OdsException(QObject::tr("file %1 does not exists")
                                   .arg(fi.absoluteFilePath()));
            }
        }
      qDebug() << __FILE__ << " " << __FUNCTION__ << " " << __LINE__;

      CustomHandler handler;
      handler.setSeparator(separator);
      handler.extractSheet(sheetname);
      handler.setQuoteStrings(parser.isSet(quoteStringsOption));
      qDebug();
      if(directory.isEmpty())
        {
          handler.setOutputStream(outputStream);
        }
      else
        {
          handler.setOutputDirectory(directory);
        }

      OdsDocReader reader(handler);
      qDebug();

      if(ods_file.isEmpty())
        {
          // throw OdsException(QObject::tr("ODS file is missing"));
          // zip file can not be parsed as stream :
          // we need to read all data before reading content
          QFile input_stream;
          if(!input_stream.open(stdin, QIODevice::ReadOnly))
            {
              throw OdsException(QObject::tr("unable to read stdin"));
            }
          QByteArray data(input_stream.readAll());
          input_stream.close();

          QBuffer buffer(&data);
          reader.parse(&buffer);
        }
      else
        {
          qDebug();
          QFile file(ods_file);
          reader.parse(file);
          qDebug();
        }
    }
  catch(OdsException &error)
    {
      qDebug();
      errorStream << "Oops! an error occurred in Ods2Csv. Don't Panic :"
                  << Qt::endl;
      errorStream << error.qwhat() << Qt::endl;
      exit(1);
      app->exit(1);
    }

  catch(std::exception &error)
    {
      qDebug();
      errorStream << "Oops! an error occurred in Ods2Csv. Don't Panic :"
                  << Qt::endl;
      errorStream << error.what() << Qt::endl;
      exit(1);
      app->exit(1);
    }


  // you must call quit when complete or the program will stay in the
  // messaging loop
  quit();
}

// call this routine to quit the application
void
Ods2Csv::quit()
{
  // you can do some cleanup here
  // then do emit finished to signal CoreApplication to quit
  emit finished();
}

// shortly after quit is called the CoreApplication will signal this routine
// this is a good place to delete any objects that were created in the
// constructor and/or to stop any threads
void
Ods2Csv::aboutToQuitApp()
{
  // stop threads
  // sleep(1);   // wait for threads to stop.
  // delete any objects
}


int
main(int argc, char **argv)
{
  qSetMessagePattern(QString("%{file}@%{line}, %{function}(): %{message}"));
  // QTextStream consoleErr(stderr);
  // QTextStream consoleOut(stdout, QIODevice::WriteOnly);
  // ConsoleOut::setCout(new QTextStream(stdout, QIODevice::WriteOnly));
  // ConsoleOut::setCerr(new QTextStream(stderr, QIODevice::WriteOnly));
  qDebug() << "main begin";
  QCoreApplication app(argc, argv);
  qDebug() << "main 1";
  QCoreApplication::setApplicationName("ods2tsv");
  QCoreApplication::setApplicationVersion(LIBODSSTREAM_VERSION);
  QLocale::setDefault(QLocale::system());

  // create the main class
  Ods2Csv myMain;
  // connect up the signals
  QObject::connect(&myMain, SIGNAL(finished()), &app, SLOT(quit()));
  QObject::connect(
    &app, SIGNAL(aboutToQuit()), &myMain, SLOT(aboutToQuitApp()));
  qDebug() << "main 2";


  // This code will start the messaging engine in QT and in
  // 10ms it will start the execution in the MainClass.run routine;
  QTimer::singleShot(10, &myMain, SLOT(run()));
  return app.exec();
}
