/* BEGIN software license
 *
 * MsXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 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 formula_handling = static_cast<int>(mp_parentWnd->m_formulaHandling);

  if(modifiers & Qt::ControlModifier)
    {
      formula_handling |= static_cast<int>(FormulaHandling::PRINT);
    }

  if(modifiers & Qt::ShiftModifier)
    {
      formula_handling |= static_cast<int>(FormulaHandling::WITH_SPACE);
    }

  mp_parentWnd->setFormulaHandling(
    static_cast<FormulaHandling>(formula_handling));

  return event->accept();
}


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

  int formula_handling = static_cast<int>(mp_parentWnd->m_formulaHandling);

  if(!(modifiers & Qt::ControlModifier))
    {
      formula_handling &= ~static_cast<int>(FormulaHandling::PRINT);
    }

  if(!(modifiers & Qt::ShiftModifier))
    {
      formula_handling &= ~static_cast<int>(FormulaHandling::WITH_SPACE);
    }

  mp_parentWnd->setFormulaHandling(
    static_cast<FormulaHandling>(formula_handling));

  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)
{
  setWindowIcon(qApp->windowIcon());

  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
