/***************************************************************************
    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 "gasCalculator.h"
#include <QPainter>
#include <QFontMetrics>
#include <QApplication>
#include <QGridLayout>
#include <QPushButton>
#include <QRadioButton>
#include <KTextEdit>
#include <QSlider>
#include <QSpinBox>
#include <KIntSpinBox>
#include <KComboBox>
#include <QSizeF>
#include <QLabel>
#include <Plasma/ComboBox>
#include <Plasma/LineEdit>
#include <Plasma/Label>
#include <Plasma/Frame>
#include <Plasma/RadioButton>
#include <Plasma/SpinBox>
#include <Plasma/PushButton>
#include <Plasma/Slider>
#include <QGraphicsGridLayout>

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

#include <KConfigDialog>

using namespace KUnitConversion;

gasCalculator::gasCalculator(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);
    resize(600, 250);
}

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

void gasCalculator::init()
{
    KConfigGroup cg = config();

    cg.readEntry("ideal",true);
}

QGraphicsWidget *gasCalculator::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);

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

	    //setup the grid layout
		QGraphicsGridLayout *pGridLayout = new QGraphicsGridLayout(m_widget);
		pGridLayout->addItem(pHeader, 0, 0, 1, 4);

		// Set up the user interface
		// 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("Moles / Mass")
 		 << i18n("Pressure")
 		 << i18n("Temperature")
 		 << i18n("Volume")
 		);

		pGridLayout->addItem(calcModeLabel, 1, 0);
 		pGridLayout->addItem(m_calculationMode, 1, 1);

		// molar mass

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

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

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

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

	    // moles

	    Plasma::Label *molesLabel = new Plasma::Label(this);
	    molesLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    molesLabel->setText(i18n("Number of moles:"));

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

	    pGridLayout->addItem(molesLabel, 3, 0);
	    pGridLayout->addItem(m_moles, 3, 1);

	    // mass

	    Plasma::Label *massLabel = new Plasma::Label(this);
	    massLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    massLabel->setText(i18n("Mass of the gas:"));

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

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

		pGridLayout->addItem(massLabel, 4, 0);
	    pGridLayout->addItem(m_mass, 4, 1);
	    pGridLayout->addItem(m_massUnit, 4, 2);

		// pressure

		Plasma::Label *pressureLabel = new Plasma::Label(this);
	    pressureLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    pressureLabel->setText(i18n("Pressure of the Gas:"));

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

  	    m_pressureUnit = new Plasma::ComboBox(this);
	    m_pressureUnit->setZValue(2);
		m_pressureUnit->nativeWidget()->insertItems(0, QStringList()
		 << i18n("atmospheres")
         << i18n("pascal")
         << i18n("bars")
         << i18n("millibars")
         << i18n("decibars")
         << i18n("torrs")
         << i18n("inches of mercury")
        );
	    m_pressureUnit->setZValue(5);

	    pGridLayout->addItem(pressureLabel, 5, 0);
	    pGridLayout->addItem(m_pressure, 5, 1);
	    pGridLayout->addItem(m_pressureUnit, 5, 2);

		// temperature

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

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

	    m_temperatureUnit = new Plasma::ComboBox(this);
	    m_temperatureUnit->setZValue(2);
	    m_temperatureUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("kelvins")
         << i18n("celsius")
         << i18n("fahrenheit")
         << i18n("delisles")
         << i18n("r\303\251aumurs")
        );
        m_temperatureUnit->setZValue(4);

	    pGridLayout->addItem(temperatureLabel, 6, 0);
	    pGridLayout->addItem(m_temperature, 6, 1);
	    pGridLayout->addItem(m_temperatureUnit, 6, 2);

	    // volume

	    Plasma::Label *volumeLabel = new Plasma::Label(this);
	    volumeLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    volumeLabel->setText(i18n("Volume of the gas:"));

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

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

	    pGridLayout->addItem(volumeLabel, 7, 0);
	    pGridLayout->addItem(m_volume, 7, 1);
	    pGridLayout->addItem(m_volumeUnit, 7, 2);

	    // van der waals constant a

	    Plasma::Label *vand_aLabel = new Plasma::Label(this);
	    vand_aLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    vand_aLabel->setText(i18n("Van der Waals constant 'a':"));

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

  	    m_aUnit = new Plasma::ComboBox(this);
	    m_aUnit->setZValue(2);
	    m_aUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("liter^2 atmosphere/mol^2")
        );
        m_aUnit->setZValue(2);

   	    pGridLayout->addItem(vand_aLabel, 8, 0);
	    pGridLayout->addItem(m_Vand_a, 8, 1);
	    pGridLayout->addItem(m_aUnit, 8, 2);

	    // van der Waals constant b

	    Plasma::Label *vand_bLabel = new Plasma::Label(this);
	    vand_bLabel->nativeWidget()->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
	    vand_bLabel->setText(i18n("Van der Waals constant 'b':"));

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

  	    m_bUnit = new Plasma::ComboBox(this);
	    m_bUnit->setZValue(2);
	    m_bUnit->nativeWidget()->insertItems(0, QStringList()
         << i18n("liters")
         << i18n("cubic meters")
         << i18n("cubic feet")
         << i18n("cubic inches")
         << i18n("gallons")
         << i18n("cups")
        );
        m_bUnit->setZValue(1);

   	    pGridLayout->addItem(vand_bLabel, 9, 0);
	    pGridLayout->addItem(m_Vand_b, 9, 1);
	    pGridLayout->addItem(m_bUnit, 9, 2);

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

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

	    // Adding objects to the UI done, now initialise
	    reset();

	    // Connect signals with slots
	    //FIXME replace all int with double after using the doubleSpinBox
	    connect(m_temperature, SIGNAL(valueChanged(int)),
	            this, SLOT(tempChanged()));
	    connect(m_temperatureUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(tempChanged()));
	    connect(m_volume, SIGNAL(valueChanged(int)),
	            this, SLOT(volChanged()));
	    connect(m_volumeUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(volChanged()));
	    connect(m_pressure, SIGNAL(valueChanged(int)),
	            this, SLOT(pressureChanged()));
	    connect(m_pressureUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(pressureChanged()));
	    connect(m_mass, SIGNAL(valueChanged(int)),
	            this, SLOT(massChanged()));
	    connect(m_massUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(massChanged()));
	    connect(m_moles, SIGNAL(valueChanged(int)),
	            this, SLOT(molesChanged(int)));
	    connect(m_molarMass, SIGNAL(valueChanged(int)),
	            this, SLOT(molarMassChanged(int)));
	    connect(m_Vand_a, SIGNAL(valueChanged(int)),
	            this, SLOT(Vand_aChanged()));
	    connect(m_aUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(Vand_aChanged()));
	    connect(m_Vand_b,  SIGNAL(valueChanged(int)),
	            this, SLOT(Vand_bChanged()));
	    connect(m_bUnit->nativeWidget(), SIGNAL(activated(int)),
	            this, SLOT(Vand_bChanged()));
		connect(m_calculationMode->nativeWidget(), SIGNAL(activated(int)),
				this, SLOT(setMode(int)));
		connect(m_reset, SIGNAL(clicked()),
				this, SLOT(reset()));
	    /**************************************************************************/
	    // gas Calculator setup complete
	    /**************************************************************************/
	}
    return m_widget;
}

void gasCalculator::reset()
{
	error(RESET_GAS_MESG);

    m_molarMass    -> setValue(2.008);
    m_temperature  -> setValue(273.0);
    m_volume       -> setValue(22.4024);
    m_pressure     -> setValue(1.0);
    m_Vand_a       -> setValue(0.0);
    m_Vand_b       -> setValue(0.0);
    m_mass         -> setValue(2.016);
    m_moles        -> setValue(1.0);
    // Setup of the UI done

    m_massUnit	      ->nativeWidget()->setCurrentIndex(0);
    m_pressureUnit    ->nativeWidget()->setCurrentIndex(0);
    m_temperatureUnit ->nativeWidget()->setCurrentIndex(0);
    m_volumeUnit      ->nativeWidget()->setCurrentIndex(0);
    m_bUnit		      ->nativeWidget()->setCurrentIndex(0);
    m_aUnit		      ->nativeWidget()->setCurrentIndex(0);
    m_calculationMode ->nativeWidget()->setCurrentIndex(3);

    // Initialise values
    m_Temp = Value(273.0, "kelvins");
    m_MolarMass = 2.016;
    m_Moles = 1.0;
    m_Mass = Value( m_MolarMass * m_Moles, "grams");
    m_Pressure = Value(1.0, "atmosphere");
    m_Vand_A = 0.0;
    m_Vand_B = Value(0.0, "liters");
    m_Vol = Value(22.4024, "liters");
    // Initialisation of values done

    setMode(3);
    molarMassChanged(2);
}
/*
    Note:-

    Van der Val's gas equation
    ( P + n^2 a / V^2) ( V - nb ) = nRT

    where P - pressure
          V - Volume
          n - number of moles
          R - Universal gas constant
          T - temperature

          a,b - Van der Val's constants
*/

// Calculates the Pressure
void gasCalculator::calculatePressure()
{
    double pressure;
    double volume = ((m_converter->convert(m_Vol, "liters")).number());
    double temp = ((m_converter->convert(m_Temp, "kelvins")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    pressure = m_Moles * R * temp / (volume - m_Moles * b) - m_Moles * m_Moles * m_Vand_A / volume / volume;
    m_Pressure = Value(pressure, "atmospheres");
    m_Pressure = (m_converter->convert(m_Pressure, m_pressureUnit->nativeWidget()->currentText()));
    m_pressure->setValue(m_Pressure.number());

    //pressure =
}

// Calculates the molar mass of the gas
void gasCalculator::calculateMolarMass()
{
    double mass = ((m_converter->convert(m_Mass, "grams")).number());
    double volume = ((m_converter->convert(m_Vol, "liters")).number());
    double pressure = ((m_converter->convert(m_Pressure, "atmospheres")).number());
    double temp = ((m_converter->convert(m_Temp, "kelvins")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    m_MolarMass = mass * R * temp / (pressure + m_Moles * m_Moles * m_Vand_A / volume / volume)\
                  / (volume - m_Moles * b);
    m_molarMass->setValue(m_MolarMass);
}

// Calculates the Volume
void gasCalculator::calculateVol()
{
    double volume;
    double pressure = ((m_converter->convert(m_Pressure, "atmospheres")).number());
    double temp = ((m_converter->convert(m_Temp, "kelvins")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    volume = m_Moles * R * temp / pressure + (m_Moles * b);
    m_Vol = Value(volume, "liters");
    m_Vol = (m_converter->convert(m_Vol, m_volumeUnit->nativeWidget()->currentText()));
    m_volume->setValue(m_Vol.number());
}

// Calculates the Temperature
void gasCalculator::calculateTemp()
{
    double temp;
    double volume = ((m_converter->convert(m_Vol, "liters")).number());
    double pressure = ((m_converter->convert(m_Pressure, "atmospheres")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    temp = (pressure + (m_Moles * m_Moles * m_Vand_A / volume / volume))\
           * (volume - m_Moles * b) / m_Moles / R;
    m_Temp = Value(temp, "kelvins");
    m_Temp = (m_converter->convert(m_Temp, m_temperatureUnit->nativeWidget()->currentText()));
    m_temperature->setValue(m_Temp.number());
}

// Calculates the number of moles
void gasCalculator::calculateMoles()
{
    double volume = ((m_converter->convert(m_Vol, "liters")).number());
    double pressure = ((m_converter->convert(m_Pressure, "atmospheres")).number());
    double temp = ((m_converter->convert(m_Temp, "kelvins")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    m_Moles = (pressure + m_Moles * m_Moles * m_Vand_A / volume / volume)\
              * (volume - m_Moles * b) / R / temp;
    m_moles->setValue(m_Moles);
}

// Calculates the mass of substance
void gasCalculator::calculateMass()
{
    double mass;
    double volume = ((m_converter->convert(m_Vol, "liters")).number());
    double pressure = ((m_converter->convert(m_Pressure, "atmospheres")).number());
    double temp = ((m_converter->convert(m_Temp, "kelvins")).number());
    double b = ((m_converter->convert(m_Vand_B, "liters")).number());

    mass = (pressure + m_Moles * m_Moles * m_Vand_A / volume / volume)\
           * (volume - m_Moles * b) * m_MolarMass / R / temp;
    m_Mass = Value(mass, "grams");
    m_Mass = (m_converter->convert(m_Mass, m_massUnit->nativeWidget()->currentText()));
    m_mass->setValue(m_Mass.number());
}


// Functions ( slots ) that occur on changing a value
// occurs when the volume is changed
void gasCalculator::volChanged()
{
    m_Vol = Value(m_volume->value(), m_volumeUnit->nativeWidget()->currentText());
    calculate();
}

// occurs when the temperature is changed
void gasCalculator::tempChanged()
{
    m_Temp = Value(m_temperature->value(), m_temperatureUnit->nativeWidget()->currentText());
    calculate();
}

// occurs when the pressure is changed
void gasCalculator::pressureChanged()
{
    m_Pressure = Value(m_pressure->value(), m_pressureUnit->nativeWidget()->currentText());
    calculate();
}

// occurs when the mass is changed
void gasCalculator::massChanged()
{
    m_Mass = Value(m_mass->value(), m_massUnit->nativeWidget()->currentText());
    m_Moles = ((m_converter->convert(m_Mass, "grams")).number()) / m_MolarMass;
    m_moles->setValue(m_Moles);
    calculate();
}

//FIXME: change int value to double value in the next two functions after using the doubleSpinBox
// occurs when the number of moles is changed
void gasCalculator::molesChanged(int value)
{
    m_Moles = value;
    m_Mass = Value((m_Moles * m_MolarMass), "grams");
    m_Mass = (m_converter->convert(m_Mass, m_massUnit->nativeWidget()->currentText()));
    m_mass->setValue(m_Mass.number());
    QString temp;
    temp.setNum(value);
    calculate();
}

// occurs when the molar mass is changed
void gasCalculator::molarMassChanged(int value)
{
	if ( value == 0.0 ) {
		error(MOLAR_MASS_ZERO_);
		return;
	}
    m_MolarMass = value;
    m_Mass = Value(m_MolarMass * m_Moles, "grams");
    m_Mass = (m_converter->convert(m_Mass, m_massUnit->nativeWidget()->currentText()));
    m_mass->setValue(m_Mass.number());
    calculate();
}

// occurs when the number of moles is changed
void gasCalculator::Vand_aChanged()
{
    m_Vand_A = m_Vand_a->value();
    calculate();
}

// occurs when the number of moles is changed
void gasCalculator::Vand_bChanged()
{
    m_Vand_B = Value(m_Vand_b->value(), m_bUnit->nativeWidget()->currentText());
    calculate();
}

void gasCalculator::setMode(int mode)
{
	m_mode = mode;

	m_moles->nativeWidget()->setReadOnly(false);
	m_mass->nativeWidget()->setReadOnly(false);
	m_pressure->nativeWidget()->setReadOnly(false);
	m_temperature->nativeWidget()->setReadOnly(false);
	m_volume->nativeWidget()->setReadOnly(false);

	// set the quantity that should be calculated to readOnly
	switch (mode)
	{
		case 0:
			m_moles->nativeWidget()->setReadOnly(true);
			m_mass->nativeWidget()->setReadOnly(true);
			break;
		case 1:
			m_pressure->nativeWidget()->setReadOnly(true);
			break;
		case 2:
			m_temperature->nativeWidget()->setReadOnly(true);
			break;
		case 3:
			m_volume->nativeWidget()->setReadOnly(true);
			break;
	}

	calculate();
}


// occurs when any quantity is changed
void gasCalculator::calculate()
{
	error(RESET_GAS_MESG);

	switch(m_mode) {
	    case 0:
	        calculateMoles();
	        break;
		case 1:
	        calculatePressure();
	        break;
		case 2:
	        calculateTemp();
	        break;
	    case 3:
	        calculateVol();
	        break;
	}
}

void gasCalculator::error(int mode)
{
    switch (mode) {
    case RESET_GAS_MESG:
    	m_error->setText("");
    	break;
    case VOL_ZERO :
        m_error->setText(i18n("Volume cannot be zero, please enter a valid value."));
        break;
    case MOLAR_MASS_ZERO_:
    	m_error->setText(i18n("The molar mass cannot be zero, please enter a non-zero value."));
    default:
        break;
    }
}

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

    ui.ideal->setChecked(m_ideal);
}

void gasCalculator::gasConfigAccepted()
{
    KConfigGroup cg = config();
    QGraphicsItem::update();

    m_ideal = ui.ideal->isChecked();
    cg.writeEntry("ideal", m_ideal);

    m_configUpdated = true;
    updateConstraints();

    emit configNeedsSaving();
}

#include "gasCalculator.moc"
