/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2018 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// Qt includes
#include <QDialog>
#include <QFile>
#include <QDebug>
#include <QScrollArea>
#include <QKeyEvent>
#include <QSettings>


/////////////////////// Local includes
#include "CalculatorChemPadDlg.hpp"
#include "CalculatorChemPadGroupBox.hpp"
#include "ChemPadButton.hpp"
#include "CalculatorWnd.hpp"


namespace MsXpS
{

namespace massxpert
{


  CalculatorChemPadDlg::CalculatorChemPadDlg(CalculatorWnd *parent)
    : QDialog{static_cast<QWidget *>(parent)}, mp_parentWnd{parent}
  {
    //    m_ui.setupUi(this);

    setSizeGripEnabled(true);

    m_columnCount = 3;
    m_row         = 0;
    m_column      = 0;

    // We need to know the value of this variable, thus set it to 0.
    mp_lastGroupbox = 0;

    // Create the main vertical box layout.
    mpa_mainVBoxLayout = new QVBoxLayout;
    setLayout(mpa_mainVBoxLayout);

    // Create the scroll area.
    mpa_scrollArea = new QScrollArea();
    mpa_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    mpa_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
    mpa_scrollArea->setWidgetResizable(true);
    mpa_mainVBoxLayout->addWidget(mpa_scrollArea);

    // Create the main groupbox:
    mpa_mainGroupBox         = new QGroupBox;
    mpa_mainGroupBoxVBLayout = new QVBoxLayout;
    mpa_mainGroupBox->setLayout(mpa_mainGroupBoxVBLayout);

    // Code to test the appearance of scrollbars: works.
    // QLabel *imageLabel = new QLabel;
    // QImage image("/home/rusconi/figure.png");
    // imageLabel->setPixmap(QPixmap::fromImage(image));
    // mpa_scrollArea->setWidget(imageLabel);

    // Geometry settings are dealt with in the setup function.


    connect(this, SIGNAL(rejected()), this, SLOT(close()));
  }


  CalculatorChemPadDlg::~CalculatorChemPadDlg()
  {
  }


  void
  CalculatorChemPadDlg::keyPressEvent(QKeyEvent *event)
  {
    Qt::KeyboardModifiers modifiers = event->modifiers();

    int formulaHandling = mp_parentWnd->m_formulaHandling;

    if(modifiers & Qt::ControlModifier)
      {
        formulaHandling |= FORMULA_HANDLING_PRINT;
      }

    if(modifiers & Qt::ShiftModifier)
      {
        formulaHandling |= FORMULA_HANDLING_WITH_SPACE;
      }

    mp_parentWnd->setFormulaHandling(formulaHandling);

    return event->accept();
  }


  void
  CalculatorChemPadDlg::keyReleaseEvent(QKeyEvent *event)
  {
    CalculatorWnd *wnd = static_cast<CalculatorWnd *>(parentWidget());

    Qt::KeyboardModifiers modifiers = event->modifiers();

    int formulaHandling = wnd->m_formulaHandling;

    if(!(modifiers & Qt::ControlModifier))
      {
        formulaHandling &= ~FORMULA_HANDLING_PRINT;
      }

    if(!(modifiers & Qt::ShiftModifier))
      {
        formulaHandling &= ~FORMULA_HANDLING_WITH_SPACE;
      }

    wnd->setFormulaHandling(formulaHandling);

    return event->accept();
  }

  bool
  CalculatorChemPadDlg::parseColumnCount(QString &line)
  {
    int percentSign = line.lastIndexOf('%');
    int lineLength  = line.length();

    QString columnsString =
      line.mid(percentSign + 1, lineLength - 2 - percentSign);

    bool ok;

    int columnCount = columnsString.toInt(&ok);

    if(!columnCount && !ok)
      return false;

    m_columnCount = columnCount;

    return true;
  }


  QColor
  CalculatorChemPadDlg::parseColor(QString &line)
  {
    QStringList list = line.split("%", Qt::SkipEmptyParts);

    // The first item of the list is "color", which we have to
    // remove.
    if(list.first() != "color")
      {
        qDebug() << __FILE__ << __LINE__ << tr("Error with chemical pad line")
                 << line;

        return QColor();
      }

    list.removeFirst();

    // Now the number of items in the list should be 2.
    if(list.size() < 2)
      {
        qDebug() << __FILE__ << __LINE__ << "Error with chemical pad line"
                 << line;

        return QColor();
      }

    // The rgb specification is the second item in the list (index 1)

    QStringList rgbList = list.at(1).split(",", Qt::SkipEmptyParts);

    bool ok = false;

    // The red component of the rgb color composition
    int r = rgbList.at(0).toInt(&ok);
    if((!r && !ok) || r < 0 || r > 255)
      {
        qDebug() << __FILE__ << __LINE__
                 << tr(
                      "Error with chemical pad line: r "
                      "color component is bad:")
                 << r;

        return QColor();
      }

    // The green component of the rgb color composition
    int g = rgbList.at(1).toInt(&ok);
    if((!g && !ok) || g < 0 || g > 255)
      {
        qDebug() << __FILE__ << __LINE__
                 << tr(
                      "Error with chemical pad line: g "
                      "color component is bad:")
                 << g;

        return QColor();
      }

    // The blue component of the rgb color composition
    int b = rgbList.at(2).toInt(&ok);
    if((!b && !ok) || b < 0 || b > 255)
      {
        qDebug() << __FILE__ << __LINE__
                 << tr(
                      "Error with chemical pad line: b "
                      "color component is bad:")
                 << b;

        return QColor();
      }

    // We have our r, g and b values: construct a QColor object and
    // immediately test it.

    QColor color = QColor(r, g, b);
    if(!color.isValid())
      {
        qDebug() << tr("Error with creation of color with: ") << line;

        return QColor();
      }

    // Now make a new hash entry with the color name and the color
    // itself. The name is the first item in the string list.

    if(list.first().isEmpty())
      {
        qDebug() << __FILE__ << __LINE__
                 << tr(
                      "Error with chemical pad line: "
                      "color name cannot be empty.");

        return QColor();
      }

    m_colorHash.insert(list.first(), color);

    //    qDebug() << __FILE__ << __LINE__
    //    << "Inserting color:" << list.first() << color;

    return color;
  }


  QStringList
  CalculatorChemPadDlg::parseSeparator(QString &line)
  {
    // The line we get should be of the form
    // chempadgroup%Hexoses & Fucose
    // or
    // chempadgroup%Hexosamines && Sialic%[violet]

    QStringList list;

    list = line.split("%", Qt::SkipEmptyParts);

    // The first item is the "chempadgroup" string.
    list.removeFirst();

    // The now-first item is the string to be used as the title of the
    // group box widget. If there is a second item, then it is the
    // color of the background of the group box widget. This last item
    // is optional, it is of the form [violet].

    if(list.size() > 1)
      {
        list[1].remove('[');
        list[1].remove(']');

        // Remove the newline character.
        list[1].remove(list.at(1).size() - 1, 1);
      }

    return list;
  }


  ChemPadButton *
  CalculatorChemPadDlg::parseKey(QString &line, QStringList &list)
  {
    CalculatorWnd *wnd    = static_cast<CalculatorWnd *>(parentWidget());
    ChemPadButton *button = 0;

    list = line.split("%", Qt::SkipEmptyParts);

    // The first item of the list is "chempadkey", which we have to
    // remove.
    if(list.first() != "chempadkey")
      {
        qDebug() << __FILE__ << __LINE__ << "Error with chemical pad line"
                 << line;

        return 0;
      }

    list.removeFirst();

    // Now the number of items in the list should be 3 or 4.
    if(list.size() < 3)
      {
        qDebug() << __FILE__ << __LINE__ << "Error with chemical pad line"
                 << line;

        return 0;
      }
    else if(list.size() == 3)
      {
        // Not color was defined.

        button = new ChemPadButton(list.at(0), list.at(1), wnd, this);

        button->setToolTip(list.at(2));
      }
    else if(list.size() == 4)
      {
        // A set of colors was defined. We have to parse it.
        // The format is : [bgHex,fgHex]

        QString colorSpec = list.last();

        // Remove the newline character.
        colorSpec.remove(colorSpec.size() - 1, 1);

        if(colorSpec.at(0) != '[')
          {
            qDebug() << __FILE__ << __LINE__
                     << "Error with button color specification:" << colorSpec;

            return 0;
          }
        else
          colorSpec.remove(0, 1);

        if(colorSpec.at(colorSpec.size() - 1) != ']')
          {
            qDebug() << __FILE__ << __LINE__
                     << "Error with button color specification:" << colorSpec;

            return 0;
          }
        else
          colorSpec.remove(colorSpec.size() - 1, 1);

        // We are left with "bgHex,fgHex"

        QStringList colorList = colorSpec.split(",", Qt::SkipEmptyParts);
        if(colorList.size() != 2)
          {
            qDebug() << __FILE__ << __LINE__
                     << "Error with button color specification:" << colorSpec;

            return 0;
          }

        // First item in the list is the background color and second
        // is the foreground color. These are the names of the
        // colors. We have to convert these into QColor instances.

        QColor bgColor = m_colorHash.value(colorList.at(0));

        if(!bgColor.isValid())
          {
            // The color might be one Qt-defined color.

            bgColor = QColor(colorList.at(0));

            if(!bgColor.isValid())
              {
                qDebug() << __FILE__ << __LINE__
                         << "Error with button color specification:"
                         << colorList.at(0);

                return 0;
              }
          }

        QColor fgColor = m_colorHash.value(colorList.at(1));

        if(!fgColor.isValid())
          {
            // The color might be one Qt-defined color.

            fgColor = QColor(colorList.at(1));

            if(!fgColor.isValid())
              {
                qDebug() << __FILE__ << __LINE__
                         << "Error with button color specification:"
                         << colorList.at(1);

                return 0;
              }
          }

        // At this point we have both back-fore-ground colors.

        button = new ChemPadButton(
          list.at(0), list.at(1), wnd, this, bgColor, fgColor);

        button->setToolTip(list.at(2));
      }

    return button;
  }


  bool
  CalculatorChemPadDlg::setup(QString &filePath, const QString &polChemDefName)
  {
    QFile file(filePath);
    if(!file.open(QFile::ReadOnly))
      {
        qDebug() << "Chemical pad's configuration file could not be opened.";

        return false;
      }

    if(!parseFile(filePath))
      {
        qDebug() << "Failed to parse the configuration "
                    "file for the chemical pad";

        return false;
      }

    QSettings settings(mp_parentWnd->configSettingsFilePath(),
                       QSettings::IniFormat);
    settings.beginGroup("calculator_chem_pad_dlg");
    QString setting = QString("%1-%2").arg(polChemDefName).arg("geometry");
    restoreGeometry(settings.value(setting).toByteArray());
    settings.endGroup();

    return true;
  }


  bool
  CalculatorChemPadDlg::parseFile(QString &filePath)
  {
    qint64 lineLength;
    QString line;
    char buffer[1024];

    QFile file(filePath);
    if(!file.open(QFile::ReadOnly))
      return false;

    // The file we have to parse is like this:
    //
    // chempad_columns%3

    // color%bgHex%125,125,125

    // chempadgroup%Hexoses & Fucose

    // chempadkey%protonate%+H1%adds a proton
    // chempadkey%hydrate%+H2O1%adds a water molecule
    // chempadkey%Res-1bRE-Hexose%C6H11O6%residue Hexose (1bRE)%[bgHex,fgHex]
    //
    // Note that any line starting with something else than 'chempad' is
    // ignored.

    lineLength = file.readLine(buffer, sizeof(buffer));

    while(lineLength != -1)
      {
        // The line is now in buffer, and we want to convert
        // it to Unicode by setting it in a QString.
        line = buffer;

        //      qDebug () << __FILE__ << __LINE__
        //		       << __FILE__ << __LINE__ << line;

        if(line.startsWith("chempad", Qt::CaseSensitive))
          {
            if(line.startsWith("chempad_columns%", Qt::CaseSensitive))
              {
                bool result = parseColumnCount(line);

                if(!result)
                  {
                    qDebug()
                      << "Failed parsing the column count. Setting to 3.";

                    // The default value of 3 remains valid.
                    m_columnCount = 3;
                  }
              }
            else if(line.startsWith("chempadgroup", Qt::CaseSensitive))
              {
                // One chemical pad separation is being parsed. This
                // means that one groupbox should be created by the
                // separator string returned below.

                QStringList list = parseSeparator(line);

                QString separator = list.first();

                CalculatorChemPadGroupBox *groupbox =
                  new CalculatorChemPadGroupBox(separator, mpa_scrollArea);

                mp_lastGroupbox = groupbox;

                mpa_mainGroupBoxVBLayout->addWidget(
                  static_cast<QWidget *>(groupbox));

                groupbox->setCheckable(true);
                groupbox->show();

                // Now, if the list returned by th call to
                // parseSeparator contains a second item, then that item
                // is a string containing a color to be used to draw the
                // background of the groupbox.

                if(list.size() > 1)
                  {
                    QColor color = m_colorHash.value(list.at(1));

                    if(!color.isValid())
                      {
                        // The color might be one Qt-defined color.

                        color = QColor(list.at(1));

                        if(!color.isValid())
                          {
                            qDebug() << "Error with group color specification:"
                                     << list.at(1);

                            return 0;
                          }
                      }

                    // OK, we have a correct color specification. Use it.

                    QString colorString = QString("rgb(%1,%2,%3)")
                                            .arg(color.red())
                                            .arg(color.green())
                                            .arg(color.blue());

                    mp_lastGroupbox->setStyleSheet(
                      QString("QGroupBox {background: %1}").arg(colorString));
                  }


                // Make sure we can hide the groupbox contents upon
                // unchecking the checkbox.

                connect(groupbox,
                        SIGNAL(toggled(bool)),
                        groupbox,
                        SLOT(toggled(bool)));

                // Reset the row and the column!
                m_row    = 0;
                m_column = 0;
              }
            else
              {
                if(!line.startsWith("chempadkey", Qt::CaseSensitive))
                  {
                    qDebug() << __FILE__ << __LINE__
                             << tr("Error parsing the chemPad.conf file.");
                    return false;
                  }

                QStringList stringList;

                ChemPadButton *button = parseKey(line, stringList);

                if(!button)
                  qDebug() << "Failed parsing the key:" << line;


                // OK, now that we have the button, we can set it to the
                // last groupbox that was created. NOTE HOWEVER, that
                // one author who would forget to create a group
                // division in the file before defining the first
                // chemical pad button, would expect the chemical pad to
                // work nonetheless. Thus we have to test that
                // hypothesis. If no section groupbox was ever
                // configured before the current button, then create
                // one.
                if(!mp_lastGroupbox)
                  {
                    CalculatorChemPadGroupBox *groupbox =
                      new CalculatorChemPadGroupBox(tr("No section title"),
                                                    mpa_scrollArea);

                    mp_lastGroupbox = groupbox;

                    mpa_mainGroupBoxVBLayout->addWidget(
                      static_cast<QWidget *>(groupbox));

                    groupbox->setCheckable(true);
                    groupbox->show();
                  }

                mp_lastGroupbox->addChemPadButton(button, m_row, m_column);
                button->show();

                ++m_column;

                // Remember, columns are numbered like indexes: starting from 0
                //(thus the '=' sign along with '>' below).
                if(m_column >= m_columnCount)
                  {
                    m_column = 0;
                    ++m_row;
                  }
              }
          }
        else if(line.startsWith("color%", Qt::CaseSensitive))
          {
            QColor color = parseColor(line);

            if(!color.isValid())
              {
                qDebug() << __FILE__ << __LINE__
                         << "Failed parsing the color:" << line;

                return false;
              }
          }

        lineLength = file.readLine(buffer, sizeof(buffer));
        continue;
      }

    mpa_scrollArea->setWidget(mpa_mainGroupBox);

    return true;
  }


  ////////////////////////////// SLOTS ///////////////////////////////
  void
  CalculatorChemPadDlg::close()
  {
    // Before closing set the parent checkbox to proper state.

    CalculatorWnd *wnd = static_cast<CalculatorWnd *>(parentWidget());

    wnd->chemPadDlgClosed();
  }

} // namespace massxpert

} // namespace MsXpS
