/***************************************************************************
    copyright            : (C) 2009 by Kashyap R Puranik
    email                : kashthealien@gmail.com
 ***************************************************************************/
/***************************************************************************
 *                                                                         *
 *   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 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/
#include "concentrationCalculator.h"
#include <QPainter>
#include <QFontMetrics>
#include <QApplication>
#include <QGridLayout>
#include <QPushButton>
#include <QRadioButton>
#include <KTextEdit>
#include <QSlider>
#include <KIntSpinBox>
#include <KComboBox>
#include <QSizeF>
#include <QLabel>
#include <Plasma/ComboBox>
#include <Plasma/LineEdit>
#include <Plasma/Label>
#include <Plasma/Frame>
#include <Plasma/GroupBox>
#include <Plasma/RadioButton>
#include <Plasma/SpinBox>
#include <Plasma/Slider>
#include <Plasma/PushButton>
#include <QGraphicsGridLayout>
#include <QGraphicsLinearLayout>

#include <plasma/svg.h>
#include <plasma/theme.h>

#include <KConfigDialog>

using namespace KUnitConversion;

concentrationCalculator::concentrationCalculator(QObject *parent, const QVariantList &args)
: Plasma::PopupApplet(parent, args)
, m_widget(0)
{
    m_converter = new Converter( this );
    setAspectRatioMode(Plasma::IgnoreAspectRatio);
    setPopupIcon("accessories-calculator");
    setHasConfigurationInterface(true);
    setAssociatedApplication("kalzium");
    resize(700, 400);
}

concentrationCalculator::~concentrationCalculator()
{
    if (hasFailedToLaunch()) {
        // Do some cleanup here
    } else {
        // Save settings
    }
}

void concentrationCalculator::init()
{
    configChanged();
}

void concentrationCalculator::configChanged()
{
    KConfigGroup cg = config();

    m_soluteMass = cg.readEntry("soluteMass",true);
    m_solventVolume = cg.readEntry("solventVolume",true);
}

QGraphicsWidget *concentrationCalculator::graphicsWidget()
{
//FIXME:
// 1.> Currently the spin boxes are integer, please convert them into double
// and uncomment certain lines of code which say 'setDecimals(4)'

	if (!m_widget) {
		m_widget = new QGraphicsWidget(this);

		// setup the label
	    Plasma::Frame *pHeader = new Plasma::Frame(this);
	    pHeader->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
	    pHeader->setText(i18n("Concentration Calculator"));

	    //setup the layout
	    QGraphicsLinearLayout *pVLayout = new QGraphicsLinearLayout(Qt::Vertical,m_widget);
	    Plasma::GroupBox *pGroupBox1 = new Plasma::GroupBox(this);
		QGraphicsGridLayout *pGridLayout = new QGraphicsGridLayout(pGroupBox1);
		pGridLayout->addItem(pHeader, 0, 0, 1, 5);
		pVLayout->addItem(pGroupBox1);

		// Set up the user interface

		// 1 Calculation mode

		Plasma::Label *calcModeLabel = new Plasma::Label(this);
		calcModeLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
		calcModeLabel->setText(i18n("Calculation Mode:"));

		m_calculationMode = new Plasma::ComboBox(this);

		m_calculationMode->setZValue(3);
		m_calculationMode->nativeWidget()->insertItems(0, QStringList()
		 << i18n("Amount Solute")
		 << i18n("Molar Mass of Solute")
		 << i18n("Equivalent Mass")
		 << i18n("Amount Solvent")
		 << i18n("Molar Mass of Solvent")
		 << i18n("Concentration")
		);

		pGridLayout->addItem(calcModeLabel, 1, 0);
		pGridLayout->addItem(m_calculationMode, 1, 1);
		// 2 amount solute

		Plasma::Label *amtSltLabel = new Plasma::Label(this);
	    amtSltLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    amtSltLabel->setText(i18n("Amount of solute:"));

	    m_amountSolute = new Plasma::SpinBox(this);
	    m_amountSolute->nativeWidget()->setMaximum(1000000000);
	    //amtSolute->setDecimals(4);
        m_amountSolute->setMaximum(1e+09);

	    m_amountSoluteType = new Plasma::ComboBox(this);
	    m_amountSoluteType->setZValue(2);
        m_amountSoluteType->nativeWidget()->insertItems(0, QStringList()
         << i18n("Mass")
         << i18n("volume")
         << i18n("moles")
        );

   	    m_amountSoluteUnit = new Plasma::ComboBox(this);
	    m_amountSoluteUnit->setZValue(6);
	    m_amountSoluteUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("grams")
         << i18n("tons")
         << i18n("carats")
         << i18n("pounds")
         << i18n("ounces")
         << i18n("troy ounces")
        );

	    pGridLayout->addItem(amtSltLabel, 2, 0);
	    pGridLayout->addItem(m_amountSolute, 2, 1);
	    pGridLayout->addItem(m_amountSoluteType, 2, 3);
	    pGridLayout->addItem(m_amountSoluteUnit, 2, 2);
	    // 3 molar mass solute

	    Plasma::Label *molarMassLabel = new Plasma::Label(this);
	    molarMassLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    molarMassLabel->setText(i18n("Molar mass of solute:"));

	    m_molarMass = new Plasma::SpinBox(this);
	    m_molarMass->nativeWidget()->setMaximum(1000000000);
        //m_MolarMass->setDecimals(4);
        m_molarMass->setMaximum(1e+09);

	    Plasma::Label *molarMassUnit = new Plasma::Label(this);
	    molarMassUnit->nativeWidget()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	    molarMassUnit->setText(i18n("u (mass)"));

	    pGridLayout->addItem(molarMassLabel, 3, 0);
	    pGridLayout->addItem(m_molarMass, 3, 1);
	    pGridLayout->addItem(molarMassUnit, 3, 2);

	    // 4 equivalent mass solute
	    Plasma::Label *eqtMassLabel = new Plasma::Label(this);
	    eqtMassLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    eqtMassLabel->setText(i18n("Equivalent mass of solute:"));

	    m_eqtMass = new Plasma::SpinBox(this);
	    m_eqtMass->nativeWidget()->setMaximum(1000000000);

	    //m_eqtMass->setDecimals(4);
        m_eqtMass->setMaximum(1e+09);

	    Plasma::Label *eqtMassUnit = new Plasma::Label(this);
	    eqtMassUnit->nativeWidget()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	    eqtMassUnit->setText(i18n("u (mass)"));

		pGridLayout->addItem(eqtMassLabel, 4, 0);
	    pGridLayout->addItem(m_eqtMass, 4, 1);
	    pGridLayout->addItem(eqtMassUnit, 4, 2);

		// 5 density solute

		Plasma::Label *densitySoluteLabel = new Plasma::Label(this);
	    densitySoluteLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    densitySoluteLabel->setText(i18n("Density of solute:"));

	    m_densitySolute = new Plasma::SpinBox(this);
	    m_densitySolute->nativeWidget()->setMaximum(1000000000);
        //m_densitySolute->setDecimals(4);
        m_densitySolute->setMaximum(1e+09);

  	    m_densitySoluteUnit = new Plasma::ComboBox(this);
	    m_densitySoluteUnit->setZValue(5);
	    m_densitySoluteUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("grams per liter")
         << i18n("grams per milliliter")
         << i18n("kilograms per cubic meter")
         << i18n("kilograms per liter")
         << i18n("ounces per cubic inch")
         << i18n("ounces per cubic foot")
         << i18n("pounds per cubic inch")
         << i18n("pounds per cubic foot")
         << i18n("pounds per cubic yard")
        );
	    pGridLayout->addItem(densitySoluteLabel, 5, 0);
	    pGridLayout->addItem(m_densitySolute, 5, 1);
	    pGridLayout->addItem(m_densitySoluteUnit, 5, 2);

		// 6 amount solvent
		Plasma::Label *amtSlvtLabel = new Plasma::Label(this);
	    amtSlvtLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    amtSlvtLabel->setText(i18n("Amount of solvent:"));

	    m_amountSolvent = new Plasma::SpinBox(this);
	    m_amountSolvent->nativeWidget()->setMaximum(1000000000);
        //m_amountSolvent->setDecimals(4);
        m_amountSolvent->setMaximum(1e+09);

	    m_amountSolventType = new Plasma::ComboBox(this);
	    m_amountSolventType->setZValue(2);
	    m_amountSolventType->nativeWidget()->insertItems(0, QStringList()
         << i18n("volume")
         << i18n("Mass")
         << i18n("moles")
        );
        m_amountSolventType->setZValue(3);

   	    m_amountSolventUnit = new Plasma::ComboBox(this);
	    m_amountSolventUnit->setZValue(4);
	    m_amountSolventUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("liter")
         << i18n("cubic meters")
         << i18n("cubic feet")
         << i18n("cubic inch")
         << i18n("cubic mile")
         << i18n("fluid ounce")
         << i18n("cups")
         << i18n("gallons")
         << i18n("pints")
        );

	    pGridLayout->addItem(amtSlvtLabel, 6, 0);
	    pGridLayout->addItem(m_amountSolvent, 6, 1);
	    pGridLayout->addItem(m_amountSolventType, 6, 3);
	    pGridLayout->addItem(m_amountSolventUnit, 6, 2);

	    // 7 molar mass solvent

	    Plasma::Label *molarMassSolvtLabel = new Plasma::Label(this);
	    molarMassSolvtLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    molarMassSolvtLabel->setText(i18n("Molar mass of solvent:"));

	    m_molarMassSolvent = new Plasma::SpinBox(this);
		m_molarMassSolvent->nativeWidget()->setMaximum(1000000000);
        //m_MolarMassSolvent->setDecimals(4);
        m_molarMassSolvent->setMaximum(1e+09);

	    Plasma::Label *molarMassSolvtUnit = new Plasma::Label(this);
	    molarMassSolvtUnit->nativeWidget()->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
	    molarMassSolvtUnit->setText(i18n("u (mass)"));

	    pGridLayout->addItem(molarMassSolvtLabel, 7, 0);
	    pGridLayout->addItem(m_molarMassSolvent, 7, 1);
	    pGridLayout->addItem(molarMassSolvtUnit, 7, 2);

	    // 8 density of solvent

	    Plasma::Label *densitySolventLabel = new Plasma::Label(this);
	    densitySolventLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    densitySolventLabel->setText(i18n("Density of solvent:"));

	    m_densitySolvent = new Plasma::SpinBox(this);
	    m_densitySolvent->nativeWidget()->setMaximum(1000000000);
        //m_densitySolvent->setDecimals(4);
        m_densitySolvent->setMaximum(1e+09);

  	    m_densitySolventUnit = new Plasma::ComboBox(this);
	    m_densitySolventUnit->setZValue(3);
	    m_densitySolventUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("grams per liter")
         << i18n("grams per milliliter")
         << i18n("kilograms per cubic meter")
         << i18n("kilograms per liter")
         << i18n("ounces per cubic inch")
         << i18n("ounces per cubic foot")
         << i18n("pounds per cubic inch")
         << i18n("pounds per cubic foot")
         << i18n("pounds per cubic yard")
        );

   	    pGridLayout->addItem(densitySolventLabel, 8, 0);
	    pGridLayout->addItem(m_densitySolvent, 8, 1);
	    pGridLayout->addItem(m_densitySolventUnit, 8, 2);

	    // 9 Concentration

		Plasma::Label *concentrationLabel = new Plasma::Label(this);
	    concentrationLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    concentrationLabel->setText(i18n("Concentration:"));

		m_concentration = new Plasma::SpinBox(this);
		m_concentration->nativeWidget()->setMaximum(1000000000);
		//m_concentration->setDecimals(4);
		m_concentration->setMaximum(1e+09);

		m_concentrationUnit = new Plasma::ComboBox(this);
	    m_concentrationUnit->setZValue(2);
	    m_concentrationUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("molar")
         << i18n("Normal")
         << i18n("molal")
         << i18n("% ( mass )")
         << i18n("% ( volume )")
         << i18n("% ( moles )")
        );

	    pGridLayout->addItem(concentrationLabel, 9, 0);
	    pGridLayout->addItem(m_concentration, 9, 1);
	    pGridLayout->addItem(m_concentrationUnit, 9, 2);

	    // 11 reset
	    m_reset = new Plasma::PushButton(this);
	    m_reset->setText(i18n("Reset"));
	    pGridLayout->addItem(m_reset, 10, 0);

	    // 10 Results
	    m_error = new Plasma::Label(this);
	    pGridLayout->addItem(m_error, 10, 1, 4, 1);



	    // Done adding elements to the UI, now initialise
	    reset();

	    // Connect signals with slots ( when a change of selection in the UI takes place,
	    // corresponding quantity should be updated in the class. )
	    connect(m_amountSolute, SIGNAL(valueChanged(int)),
	            this, SLOT(amountSoluteChanged()));
	    connect(m_amountSoluteType->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(amountSoluteTypeChanged()));
	    connect(m_amountSoluteUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(amountSoluteChanged()));
	    connect(m_molarMass, SIGNAL(valueChanged(int)),
	            this, SLOT(molarMassChanged(int)));
	    connect(m_eqtMass, SIGNAL(valueChanged(int)),
	            this, SLOT(eqtMassChanged(int)));
	    connect(m_densitySolute, SIGNAL(valueChanged(int)),
	            this, SLOT(densitySoluteChanged()));
	    connect(m_densitySoluteUnit->nativeWidget(),  SIGNAL(activated(int)),
	            this, SLOT(densitySoluteChanged()));
	    connect(m_amountSolvent, SIGNAL(valueChanged(int)),
	            this, SLOT(amountSolventChanged()));
	    connect(m_amountSolventType->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(amountSolventTypeChanged()));
	    connect(m_amountSolventUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(amountSolventChanged()));
	    connect(m_amountSolventUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(amountSolventChanged()));
	    connect(m_molarMassSolvent, SIGNAL(valueChanged(int)),
	            this, SLOT(molarMassSolventChanged(int)));
	    connect(m_densitySolvent, SIGNAL(valueChanged(int)),
	            this, SLOT(densitySolventChanged()));
	    connect(m_densitySolventUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(densitySolventChanged()));
	    connect(m_concentration, SIGNAL(valueChanged(int)),
	            this, SLOT(concentrationChanged(int)));
	    connect(m_concentrationUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(concentrationChanged(int)));
		connect(m_calculationMode->nativeWidget(), SIGNAL(activated(int)),
				this, SLOT(setMode(int)));
		connect(m_reset, SIGNAL(clicked()),
				this, SLOT(reset()));
	    /**************************************************************************/
	    //              concentration Calculator setup complete
	    /**************************************************************************/
	}
    return m_widget;
}

void concentrationCalculator::reset()
{
	/**************************************************************************/
	//                       concentration Calculator set up
	/**************************************************************************/
	error(RESET_CONC_MESG);
	// initialise the initially selected values
	m_amountSolute            -> setValue(117.0);
    m_molarMass            -> setValue(58.5);
    m_eqtMass              -> setValue(58.5);
    m_densitySolute        -> setValue(2.7);
    m_amountSolvent           -> setValue(1.0);
    m_molarMassSolvent     -> setValue(18.0);
    m_densitySolvent       -> setValue(1000.0);
    m_concentration        -> setValue(2.0);
    m_mode = 5;
    // Setup of the UI done

    // Initialise values
    m_AmtSolute = Value(117.0, "grams");
    m_AmtSolvent = Value(1.0, "liter");
    m_MolarMass = 58.5;
    m_EqtMass = 58.5;
    m_MolesSolute = 2.0;
    m_MolesSolvent = 55.5;
    m_MolarMassSolvent = 18.0;
    m_DensitySolute = Value(2.7, "grams per milliliter");
    m_Concentration = 2.0;
    m_DensitySolvent = Value(1000.0, "grams per liter");

    m_amountSoluteType	 ->nativeWidget()->setCurrentIndex(0);
    m_amountSolventType  ->nativeWidget()->setCurrentIndex(0);
    m_amountSoluteUnit	 ->nativeWidget()->setCurrentIndex(0);
    m_amountSolventUnit	 ->nativeWidget()->setCurrentIndex(0);
    m_densitySolventUnit ->nativeWidget()->setCurrentIndex(0);
    m_densitySoluteUnit  ->nativeWidget()->setCurrentIndex(0);
    m_concentrationUnit	 ->nativeWidget()->setCurrentIndex(0);
	m_calculationMode	 ->nativeWidget()->setCurrentIndex(5);

	setMode(5);
	calculate();
	// Initialisation of values done
}
// Calculates the amount of solute
void concentrationCalculator::calculateAmountSolute()
{
    int type1 = m_concentrationUnit->nativeWidget()-> currentIndex();
    int type2 = m_amountSoluteType->nativeWidget()-> currentIndex();

    double molesSolute , eqtsSolute, massSolute, volSolute;     // variables
    int mode = 0;
    /*
     * mode = 1 ( molesSolute) mode = 2 eqtsSolute, mode = 3 mass, 4 volume
     */
    // Calculate the number of moles of the solute
    switch (type1) {
        // calculate the number of moles of solute
	    case 0: // molarity specified
	        molesSolute = m_Concentration * volumeSolvent();
	        mode = 1;
	        break;
	        // Calculate the number of equivalents of solute
	    case 1: // Normality specified
	        eqtsSolute = m_Concentration * volumeSolvent();
	        mode = 2;
	        break;
	        // Calculate the number of moles of solute
	    case 2: // molality specified
	        molesSolute = m_Concentration * massSolvent() / 1000.0;
	        mode = 1;
	        break;
	        // Calculate the mass of solute
	    case 3: // mass percentage specified
	        if (m_Concentration >= 100.0) {
	            error(PERCENTAGE_ZERO);
	        }
	        massSolute = m_Concentration / (100.0 - m_Concentration) * massSolvent();
	        mode = 3;
	        break;
	        // Calculate the volume of solute
	    case 4: // volume percentage specified
	        if (m_Concentration >= 100.0) {
	            error(PERCENTAGE_ZERO);
	        }
	        volSolute = m_Concentration / (100.0 - m_Concentration) * volumeSolvent();
	        mode = 4;
	        break;
	        // Calculate the moles of solute
	    case 5: //mole percentage specified
	        if (m_Concentration >= 100.0) {
	            error(PERCENTAGE_ZERO);
	        }
	        molesSolute = m_Concentration / (100.0 - m_Concentration) * molesSolvent();
	        mode = 1;
	        break;
	    default:
	        break;
    }

    // We have the amount of solvent in some form ( moles, equivalents, mass, volume etc )
    // Now we have to present it in the UI
    switch (type2) {
    case 0: // amount should be specified interms of mass
        switch (mode) {
        case 1: // we should get mass from moles
            massSolute = molesSolute * m_MolarMass;
            break;
        case 2: // we should obtain mass from number of equivalents
            massSolute = eqtsSolute * m_EqtMass;
            break;
        case 3: // we already know the mass of the solute
            break;
        case 4: // we should get the mass from volume
            massSolute = volSolute * densitySolute();
            break;
        }
        // update mass of solute
        m_AmtSolute = Value(massSolute, "grams");
        m_AmtSolute = (m_converter -> convert(m_AmtSolute , \
                       m_amountSoluteUnit->nativeWidget()->currentText()));
        m_amountSolute -> setValue(m_AmtSolute.number());
        break;

    case 1: // amount should be specified in terms of volume
        // validate density
        if (densitySolute() == 0) {
            error(DENSITY_ZERO);
            return;
        }
        switch (mode) {
        case 1: // we should get the volume from moles
            volSolute = molesSolute * m_MolarMass / densitySolute();
            break;
        case 2: // we should get the volume from equivalents
            volSolute = eqtsSolute * m_EqtMass / densitySolute();
            break;
        case 3: // we should get the volume from mass
            volSolute = massSolute / densitySolute();
            break;
        case 4: // we already know the volume
            break;
        }
        // update volume of solute
        m_AmtSolute = Value(volSolute, "liters");
        m_AmtSolute = (m_converter-> convert(m_AmtSolute , \
                       m_amountSoluteUnit->nativeWidget()->currentText()));
        m_amountSolute -> setValue(m_AmtSolute.number());
        break;

    case 2: // amount should be specified in terms of moles
        switch (mode) {
        case 1: // we already know the moles of solute
            break;
        case 2: // we should obtain moles from equivalents ( not possible )
            molesSolute = 0.0;
            break;
        case 3: // we should obtain moles from mass
            molesSolute = massSolute / m_MolarMass;
            break;
        case 4: // we should obtain moles from volume
            molesSolute = volSolute * densitySolute() / m_MolarMass;
            break;
        }
        // Update the number of moles
        m_MolesSolute = molesSolute;
        m_amountSolute -> setValue(molesSolute);
        break;
    }
    return;
}

// Calculates the molar mass
void concentrationCalculator::calculateMolarMass()
{
    // molarity / molality / mole fraction required
    int type = m_concentrationUnit->nativeWidget() ->currentIndex();
    int type2 = m_amountSolventType->nativeWidget() ->currentIndex();
    double numMoles;
    switch (type) {
    case 0:     //molarity specified
        // number of moles = volume * concentration
        numMoles = volumeSolvent() * m_Concentration;
        break;
    case 1:     // cannot be calculated ( insufficient data )
        error(INSUFFICIENT_DATA_MOLE);
        return;
        break;
    case 2:     // molality specified
        numMoles = massSolvent() / 1000.0 * m_Concentration;
        break;
    case 3:     // cannot be calculated ( insufficient data )
    case 4:
        error(INSUFFICIENT_DATA_MOLE);
        return;
        break;
    case 5:     // mole fraction specified
        numMoles = m_Concentration / (100.0 - m_Concentration) * molesSolvent();
        break;
    }

    if (type2 == 2) { // amount of solute is specified in moles, cannot calculate
        error(INSUFFICIENT_DATA_MOLES);
        return;
    } else {
        if (numMoles == 0.0) {
            error(MOLES_ZERO);
            return;
        }
        m_MolarMass = massSolute() / numMoles;
        m_molarMass->setValue(m_MolarMass);
    }
}

// Calculates the equivalent mass
void concentrationCalculator::calculateEqtMass()
{
    // Normality required
    int type = m_concentrationUnit->nativeWidget() ->currentIndex();
    int type2 = m_amountSoluteType->nativeWidget() ->currentIndex();

    double numEqts;
    switch (type) {
        // Normality required
    case 0: // molarity not sufficient
        error(INSUFFICIENT_DATA_EQT);
        return;
        break;
    case 1: // normality specified
        numEqts = volumeSolvent() * m_Concentration;
        break;
    case 2:
    case 3:
    case 4:
    case 5:
        error(INSUFFICIENT_DATA_EQT);
        return;
        break;
    }

    if (type2 == 2) {    // Amount of solute specified in moles, cannot calculate
        error(INSUFFICIENT_DATA_MOLES);
    } else {
        if (numEqts == 0.0) {
            error(EQTS_ZERO);
            return;
        }
        m_EqtMass = massSolute() / numEqts;
        m_eqtMass->setValue(m_EqtMass);
    }
    return;
}

// Calculates the calculate molar mass of the solvent
void concentrationCalculator::calculateMolarMassSolvent()
{
    // molarity / molality / mole fraction required
    int type = m_concentrationUnit->nativeWidget() ->currentIndex();
    int type2 = m_amountSolventType->nativeWidget() ->currentIndex();
    double numMoles;
    switch (type) {
    case 0:         // molarity specified
    case 1:         // normality specified
    case 2:         // molality specified
    case 3:         // mass fraction specified
    case 4:         // volume fraction specified
        error(INSUFFICIENT_DATA_SOLVENT);
        return;
        break;      // cannot be calculated ( insufficient data )
    case 5:         // mole fraction specified
        numMoles = (100.0 - m_Concentration) / m_Concentration * molesSolute();
        break;
    }

    if (type2 == 2) {    // amount specified in moles
        error(INSUFFICIENT_DATA_MOLES);
    } else {
        m_MolarMassSolvent = massSolvent() / numMoles;
        m_molarMassSolvent->setValue(m_MolarMassSolvent);
    }

    return;
}

// Calculates the amount of solvent
void concentrationCalculator::calculateAmountSolvent()
{
    int type1 = m_concentrationUnit->nativeWidget()-> currentIndex();
    int type2 = m_amountSolventType->nativeWidget()-> currentIndex();

    double moleSolvent , massSolvent, volSolvent;

    int mode = 0;               // Indicates the mode in which we have calculated the amount of solvent
    /*
     * mode = 1 ( molessolvent) mode = 2 eqtssolvent, mode = 3 mass, 4 volume
     */
    // Calculate the number of moles of the solvent
    if (m_Concentration == 0.0) {
        error(CONC_ZERO);
        return;
    }

    switch (type1) {
        // calculate the number of moles of solvent
    case 0: // molarity specified
        volSolvent = molesSolute() / m_Concentration;
        mode = 3;
        break;
        // Calculate the number of equivalents of solvent
    case 1: // Normality specified
        volSolvent = eqtsSolute() / m_Concentration;
        mode = 3;
        break;
        // Calculate the number of moles of solvent
    case 2: // molality specified
        massSolvent = molesSolute() * 1000.0 / m_Concentration;
        mode = 2;
        break;
        // Calculate the mass of solvent
    case 3: // mass percentage specified
        massSolvent = (100.0 - m_Concentration) / m_Concentration;
        massSolvent *= massSolute();
        mode = 2;
        break;
        // Calculate the volume of solvent
    case 4: // volume percentage specified
        volSolvent = (100.0 - m_Concentration) / m_Concentration;
        volSolvent *= volumeSolute();
        mode = 3;
        break;
        // Calculate the moles of solvent
    case 5: //mole percentage specified
        moleSolvent = (100.0 - m_Concentration) / m_Concentration;
        moleSolvent *= molesSolute();
        mode = 1;
        break;
    default:
        break;
    }

    // We have the amount of solvent in some form ( moles, equivalents, mass, volume etc )
    // Now we have to present it in the UI
    if (densitySolvent() == 0.0) {
        error(DENSITY_ZERO);
        return;
    }
    if (m_MolarMassSolvent == 0.0) {
        error(MOLAR_SOLVENT_ZERO);
        return;
    }
    switch (type2) {
    case 0: // amount should be specified interms of volume
        switch (mode) {
        case 1: // obtain volume from moles
            volSolvent = moleSolvent * m_MolarMassSolvent / densitySolvent();
            break;
        case 2: // obtain volume from mass
            volSolvent = massSolvent / densitySolvent();
            break;
        case 3: // volume already known
            break;
        }
        m_AmtSolvent = Value(volSolvent, "liters");
        m_AmtSolvent = (m_converter->convert(m_AmtSolvent , \
                        m_amountSolventUnit->nativeWidget()->currentText()));
        m_amountSolvent->setValue(m_AmtSolvent.number());
        break;

    case 1: // amount should be specified in terms of mass
        switch (mode) {
        case 1: // obtain mass from moles
            massSolvent = moleSolvent / m_MolarMassSolvent;
            break;
        case 2: // mass already known
            break;
        case 3: // obtain mass from volume
            massSolvent = volSolvent / densitySolvent();
            break;
        }
        m_AmtSolvent = Value(massSolvent, "grams");
        m_AmtSolvent = (m_converter-> convert(m_AmtSolvent , \
                        m_amountSolventUnit->nativeWidget()->currentText()));
        m_amountSolvent->setValue(m_AmtSolvent.number());
        break;

    case 2: // amount should be specified in terms of moles
        switch (mode) {
        case 1: // moles already known
            break;
        case 2: // obtain moles from mass
            moleSolvent = massSolvent / m_MolarMassSolvent;
            break;
        case 3: // obtain moles from volume
            moleSolvent = volSolvent * densitySolvent() / m_MolarMassSolvent;
            break;
        }
        m_MolesSolvent =  moleSolvent;
        m_amountSolvent->setValue(moleSolvent);
        break;
    }
    return;
}

// calculates the concentration
void concentrationCalculator::calculateConcentration()
{
    int type = m_concentrationUnit->nativeWidget()-> currentIndex();

    if (volumeSolvent() == 0.0) {
        error(VOLUME_ZERO);
        return;
    }
    if (massSolvent() == 0.0) {
        error(MASS_ZERO);
        return;
    }
    if (molesSolvent() == 0.0) {
        error(MOLES_ZERO);
        return;
    }
    switch (type) {
    case 0:     // molarity
        m_Concentration = molesSolute() / volumeSolvent();
        break;
    case 1:     // normality
        m_Concentration = eqtsSolute() / volumeSolvent();
        break;
    case 2:     // molality
        m_Concentration = molesSolute() * 1000.0 / massSolvent();
        break;
    case 3:     // mass fraction
        m_Concentration = massSolute() / (massSolute() + massSolvent()) * 100.0;
        break;
    case 4:     // volume fraction
        m_Concentration = volumeSolute() / (volumeSolute() + volumeSolvent()) * 100.0;
        break;
    case 5:     // mole fraction
        m_Concentration = molesSolute() / (molesSolute() + molesSolvent()) * 100.0;
        break;
    default:
        break;
    }
    m_concentration->setValue(m_Concentration);
    return;
}

double concentrationCalculator::volumeSolvent()
{
    int type = m_amountSolventType->nativeWidget()-> currentIndex();
    double volume;
    switch (type) {
    case 0:
        volume = (m_converter-> convert(m_AmtSolvent , "liter")).number();
        break;
    case 1:
        volume = massSolvent() / densitySolvent();
        break;
    case 2:
        volume = massSolvent() / densitySolvent();
    default:
        break;
    }
    return volume;
}

double concentrationCalculator::molesSolvent()
{
    int type = m_amountSolventType->nativeWidget()-> currentIndex();

    double moles;
    switch (type) {
    case 0:
        moles = massSolvent() / m_MolarMassSolvent;
        break;
    case 1:
        moles = massSolvent() / m_MolarMassSolvent;
        break;
    case 2:
        moles = m_MolesSolvent;
        break;
    default:
        break;
    }
    return moles;

}
double concentrationCalculator::massSolvent()
{
    int type = m_amountSolventType->nativeWidget()-> currentIndex();
    double mass;
    switch (type) {
    case 0:
        mass = volumeSolvent() * densitySolvent();
        break;
    case 1:
        mass = (m_converter-> convert(m_AmtSolvent, "gram")) .number();
        break;
    case 2:
        mass = m_MolesSolvent * m_MolarMassSolvent;
        break;
    default:
        break;
    }
    return mass;
}

double concentrationCalculator::densitySolvent()
{
    return ((m_converter-> convert(m_DensitySolvent, "grams per liter")).number());
}

double concentrationCalculator::volumeSolute()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();
    double volume;
    switch (type) {
    case 0:
        volume = massSolute() / densitySolute();
        break;
    case 1:
        volume = (m_converter-> convert(m_AmtSolute , "liter")).number();
        break;
    case 2:
        volume = massSolute() / densitySolute();
    default:
        break;
    }
    return volume;
}

double concentrationCalculator::molesSolute()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();

    double moles;
    if (m_MolarMass == 0.0) {
        error(MOLAR_MASS_ZERO);
        return 1.0;
    }
    switch (type) {
    case 0:
        moles = massSolute() / m_MolarMass;
        break;
    case 1:
        moles = massSolute() / m_MolarMass;
        break;
    case 2:
        moles = m_MolesSolute;
        break;
    default:
        break;
    }
    return moles;
}

double concentrationCalculator::eqtsSolute()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();
    double eqts;
    if (m_EqtMass == 0.0) {
        error(EQT_MASS_ZERO);
        return 1.0;
    }
    switch (type) {
    case 0:
        eqts = massSolute() / m_EqtMass;
        break;
    case 1:
        eqts = massSolute() / m_EqtMass;
        break;
    case 2:
        // Cannot be calculated
        error(INSUFFICIENT_DATA_MOLES);
        eqts = 1.0;
        break;
    default:
        break;
    }
    return eqts;

}

double concentrationCalculator::massSolute()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();
    double mass;
    switch (type) {
    case 0:
        mass = (m_converter-> convert(m_AmtSolute, "gram")) .number();
        break;
    case 1:
        mass = volumeSolute() * densitySolute();
        break;
    case 2:
        mass = m_MolesSolute * m_MolarMass;
        break;
    default:
        break;
    }
    return mass;
}

double concentrationCalculator::densitySolute()
{
    return ((m_converter-> convert(m_DensitySolute, "grams per liter")).number());
}


// occurs when the amount of solute is changed
void concentrationCalculator::amountSoluteChanged()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();
    switch (type) {
    	case 0:
    	case 1:
			m_AmtSolute = Value(m_amountSolute -> value(), m_amountSoluteUnit -> nativeWidget()->currentText());
			break;
    	case 2:
           m_MolesSolute = m_amountSolute -> value();
           break;
    }
    calculate();
}

// occurs when the type in which amount of solute is specified is changed
void concentrationCalculator::amountSoluteTypeChanged()
{
    int type = m_amountSoluteType -> nativeWidget()->currentIndex();
    if (type == 0) {         // amount of solute specified in terms of mass
        m_amountSoluteUnit->nativeWidget()->clear();
   	    m_amountSoluteUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("grams")
         << i18n("tons")
         << i18n("carats")
         << i18n("pounds")
         << i18n("ounces")
         << i18n("troy ounces")
        );
        m_amountSoluteUnit -> show();
        m_AmtSolute = Value(m_amountSolute -> value(), m_amountSoluteUnit -> nativeWidget()->currentText());
    } else if (type == 1) { // amount of solute is specified in terms of volume
        m_amountSoluteUnit->nativeWidget()->clear();
	    m_amountSoluteUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("liter")
         << i18n("cubic meters")
         << i18n("cubic feet")
         << i18n("cubic inch")
         << i18n("cubic mile")
         << i18n("fluid ounce")
         << i18n("cups")
         << i18n("gallons")
         << i18n("pints")
        );
        m_amountSoluteUnit-> show();
        m_AmtSolute = Value(m_amountSolute -> value(), m_amountSoluteUnit -> nativeWidget()->currentText());
    } else {                 // amount of solute is specified in terms of moles
        m_MolesSolute = m_amountSolute -> value();
        m_amountSoluteUnit -> hide();
    }
    calculate();
}
// occurs when the amount of solvent is changed
void concentrationCalculator::amountSolventChanged()
{
    int type = m_amountSolventType -> nativeWidget()->currentIndex();
    switch (type) {     // amount of solvent specified in terms of volume
    	case 0:
    	case 1:
	        m_AmtSolvent = Value(m_amountSolvent -> value(), m_amountSolventUnit -> nativeWidget()->currentText());
	    	break;
	    case 2:
	    	m_MolesSolvent = m_amountSolvent -> value();
	    	break;
	}
	calculate();
}

void concentrationCalculator::amountSolventTypeChanged()
{
    int type = m_amountSolventType -> nativeWidget()->currentIndex();
    if (type == 0) {     // amount of solvent specified in terms of volume
        m_amountSolventUnit->nativeWidget()->clear();
	    m_amountSolventUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("liter")
         << i18n("cubic meters")
         << i18n("cubic feet")
         << i18n("cubic inch")
         << i18n("cubic mile")
         << i18n("fluid ounce")
         << i18n("cups")
         << i18n("gallons")
         << i18n("pints")
        );
        m_amountSolventUnit -> show();
        m_AmtSolvent = Value(m_amountSolvent -> value(), m_amountSolventUnit -> nativeWidget()->currentText());
    } else if (type == 1) { // amount of solvent is specified in terms of mass
        m_amountSolventUnit->nativeWidget()->clear();
   	    m_amountSolventUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("grams")
         << i18n("tons")
         << i18n("carats")
         << i18n("pounds")
         << i18n("ounces")
         << i18n("troy ounces")
        );
        m_amountSolventUnit -> show();
        m_AmtSolvent = Value(m_amountSolvent -> value(), m_amountSolventUnit -> nativeWidget()->currentText());
    } else { // amount is specified in terms of moles
        m_amountSolventUnit -> hide();
        m_MolesSolvent = m_amountSolvent -> value();
    }
    calculate();
}

//FIXME replace 'int value' with 'double value'
// occurs when the molar mass of solute is changed
void concentrationCalculator::molarMassChanged(int value)
{
    m_MolarMass = value;
    calculate();
}

// occurs when the equivalent mass of solute is changed
void concentrationCalculator::eqtMassChanged(int value)
{
    m_EqtMass = value;
    calculate();
}

// occurs when the molar mass of solvent is changed
void concentrationCalculator::molarMassSolventChanged(int value)
{
    m_MolarMassSolvent = value;
    calculate();
}

// occurs when the number of moles is changed
void concentrationCalculator::densitySoluteChanged()
{
    m_DensitySolute = Value(m_densitySolute -> value(), m_densitySoluteUnit -> nativeWidget()->currentText());
    calculate();
}

// occurs when the density of solvent is changed
void concentrationCalculator::densitySolventChanged()
{
    m_DensitySolvent = Value(m_densitySolvent -> value(), m_densitySolventUnit -> nativeWidget()->currentText());
    calculate();
}

// occurs when the concentration is changed
void concentrationCalculator::concentrationChanged(int value)
{
    m_Concentration = value;
    calculate();
}

// This function is called when the mode of calculation is changed
void concentrationCalculator::setMode (int mode)
{
	// If there is no change, return.
	if (m_mode == mode)
		return;

	// set all to writeable
	m_amountSolute->nativeWidget()->setReadOnly(false);
	m_molarMass->nativeWidget()->setReadOnly(false);
    m_eqtMass->nativeWidget()->setReadOnly(false);
    m_amountSolvent->nativeWidget()->setReadOnly(false);
 	m_molarMassSolvent->nativeWidget()->setReadOnly(false);
	m_concentration->nativeWidget()->setReadOnly(false);

	// set the value that should be calculated to readOnly
	switch (mode)
	{
		case 0:		// Calculate the amount of solute
			m_amountSolute->nativeWidget()->setReadOnly(true);
			break;
        case 1:		// Calculate the molar mass of solute
        	m_molarMass->nativeWidget()->setReadOnly(true);
        	break;
        case 2:		// Calculate the equivalent mass of solute
    	    m_eqtMass->nativeWidget()->setReadOnly(true);
    	    break;
		case 3:     // Calculate the amount of solvent
 			m_amountSolvent->nativeWidget()->setReadOnly(true);
 			break;
    	case 4:		// Calculate the molar mass of solvent
	        m_molarMassSolvent->nativeWidget()->setReadOnly(true);
	        break;
    	case 5:		// Calculate the concentration of the solution
	        m_concentration->nativeWidget()->setReadOnly(true);
	        break;
    }

	m_mode = mode;
	calculate();
}
// occurs when any quantity is changed
void concentrationCalculator::calculate()
{
	error(RESET_CONC_MESG);
	switch(m_calculationMode->nativeWidget()->currentIndex())
   {
    	case 0: // Calculate the amount of solute
    	    if (m_concentrationUnit -> nativeWidget()->currentIndex() > 2 && m_concentration -> value() > 100)
    	    {
    	    	error (PERCENTAGE_ZERO);
    	    	return;
    	    }
    	    calculateAmountSolute();
    		break;
    	case 1: // Calculate the molar mass of solute
    	    calculateMolarMass();
    	    break;
		case 2:	// Calculate the equivalent mass of solute
		    calculateEqtMass();
		    break;
    	case 3: // Calculate the amount of solvent
	        calculateAmountSolvent();
	        break;
		case 4: // Calculate the molar mass of solvent
	        calculateMolarMassSolvent();
	        break;
		case 5: // Calculate the concentration of the solution
	        calculateConcentration();
	        break;
	}
    return;
}

void concentrationCalculator::error(int mode)
{

    switch (mode) {
    case RESET_CONC_MESG:
    	m_error->setText("");
    	break;
    case PERCENTAGE_ZERO:
        m_error->setText(i18n("Percentage should be less than 100.0, please enter a valid value."));
        break;
    case DENSITY_ZERO:
        m_error->setText(i18n("Density cannot be zero, please enter a valid value."));
        break;
    case MASS_ZERO:
        m_error->setText(i18n("Mass cannot be zero, please enter a valid value."));
        break;
    case VOLUME_ZERO:
        m_error->setText(i18n("Volume cannot be zero, please enter a valid value."));
        break;
    case MOLES_ZERO:
        m_error->setText(i18n("Number of moles cannot be zero, please enter a valid value."));
        break;
    case MOLAR_SOLVENT_ZERO:
        m_error->setText(i18n("Molar mass of solvent is zero, please enter a valid value."));
        break;
    case EQTS_ZERO:
        m_error->setText(i18n("Number of equivalents is zero - cannot calculate equivalent mass."));
        break;
    case CONC_ZERO:
        m_error->setText(i18n("Concentration is zero, please enter a valid value."));
        break;
    case INSUFFICIENT_DATA_EQT:
        m_error->setText(i18n("Insufficient data to calculate the required value, please specify normality."));
        break;
    case INSUFFICIENT_DATA_MOLE:
        m_error->setText(i18n("Insufficient data, please specify molarity / mole fraction / molality to calculate."));
        break;
    case INSUFFICIENT_DATA_MOLES:
        m_error->setText(i18n("Amount is specified in moles, cannot calculate molar/equivalent masses.  Please specify mass/volume."));
        break;
    case INSUFFICIENT_DATA_SOLVENT:
        m_error->setText(i18n("You can only calculate the molar mass of a solvent if the mole fraction is specified."));
        break;
    case MOLAR_MASS_ZERO:
        m_error->setText(i18n("Molar mass cannot be zero, please enter a valid value."));
        break;
    case EQT_MASS_ZERO:
        m_error->setText(i18n("Equivalent mass cannot be zero, please enter a valid value."));
        break;
    default:
        break;
    }
}

void concentrationCalculator::createConfigurationInterface(KConfigDialog *parent)
{
    QWidget *widget = new QWidget();
    ui.setupUi(widget);
    parent->addPage(widget, i18n("General"), icon());

    ui.soluteMass->setChecked(m_soluteMass);
    ui.solventVolume->setChecked(m_solventVolume);

    connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()));
    connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted()));
    
    connect (ui.soluteMass, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
    connect (ui.solventVolume, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
}

void concentrationCalculator::configAccepted()
{
    KConfigGroup cg = config();
    QGraphicsItem::update();

    m_soluteMass = ui.soluteMass->isChecked();
    cg.writeEntry("soluteMass", m_soluteMass);

    m_solventVolume = ui.solventVolume->isChecked();
    cg.writeEntry("solventVolume", m_solventVolume);

    m_configUpdated = true;
    updateConstraints();

    emit configNeedsSaving();
}
#include "concentrationCalculator.moc"
