/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


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


/////////////////////// Local includes
#include "application.hpp"
#include "calculatorChemPadDlg.hpp"
#include "calculatorChemPadGroupBox.hpp"
#include "chemPadButton.hpp"
#include "calculatorWnd.hpp"


namespace massXpert
{

  CalculatorChemPadDlg::CalculatorChemPadDlg(QWidget *parent)
    : QDialog(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)
  {
    CalculatorWnd *wnd = static_cast<CalculatorWnd *>(parentWidget());
   
    Qt::KeyboardModifiers modifiers = event->modifiers();

    int formulaHandling = wnd->m_formulaHandling;

    if (modifiers & Qt::ControlModifier)
      {
	formulaHandling |= MXP_FORMULA_HANDLING_PRINT;
      }
    
    if (modifiers & Qt::ShiftModifier)
      {
	formulaHandling |= MXP_FORMULA_HANDLING_WITH_SPACE;
      }
    
      wnd->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 &= ~MXP_FORMULA_HANDLING_PRINT;
      }
    
    if (!(modifiers & Qt::ShiftModifier))
      {
	formulaHandling &= ~MXP_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("%", QString::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(",", QString::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("%", QString::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("%", QString::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 (",", QString::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 
    (static_cast<Application *>(qApp)->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 chem_pad.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
