/***************************************************************************
 *   Copyright (C) 2005, 2006      by Pino Toscano, toscano.pino@tiscali.it      *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 *   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, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
 ***************************************************************************/

#include "kalziumgradienttype.h"

#include "element.h"
#include "prefs.h"
#include "kalziumdataobject.h"
#include <kunitconversion/converter.h>

#include <QVariant>

#include <klocale.h>
#include <kdebug.h>

#include <math.h>

KalziumGradientTypeFactory::KalziumGradientTypeFactory()
{
    m_gradients << KalziumSOMGradientType::instance();
    m_gradients << KalziumCovalentRadiusGradientType::instance();
    m_gradients << KalziumVanDerWaalsRadiusGradientType::instance();
    m_gradients << KalziumMassGradientType::instance();
    m_gradients << KalziumBoilingPointGradientType::instance();
    m_gradients << KalziumMeltingPointGradientType::instance();
    m_gradients << KalziumElectronegativityGradientType::instance();
    m_gradients << KalziumElectronaffinityGradientType::instance();
    m_gradients << KalziumDiscoverydateGradientType::instance();
    m_gradients << KalziumIonizationGradientType::instance();
}

KalziumGradientTypeFactory* KalziumGradientTypeFactory::instance()
{
    static KalziumGradientTypeFactory kttf;
    return &kttf;
}

KalziumGradientType* KalziumGradientTypeFactory::build( int id ) const
{
    if ( ( id < 0 ) || ( id >= m_gradients.count() ) )
        return 0;

    return m_gradients.at( id );
}

KalziumGradientType* KalziumGradientTypeFactory::build( const QByteArray& id ) const
{
    for ( int i = 0; i < m_gradients.count(); i++ )
    {
        if ( m_gradients.at( i )->name() == id )
            return m_gradients.at( i );
    }

    return 0;
}

QStringList KalziumGradientTypeFactory::gradients() const
{
    QStringList l;
    for ( int i = 0; i < m_gradients.count(); i++ )
    {
        l << m_gradients.at( i )->description();
    }
    return l;
}


KalziumGradientType::KalziumGradientType()
{
}

KalziumGradientType::~KalziumGradientType()
{
}

KalziumGradientType* KalziumGradientType::instance()
{
    return 0;
}

double KalziumGradientType::elementCoeff( int el ) const
{
    double val = value( el );
    if ( val == -1 ) return val;

    if ( logarithmicGradient() ) {
        double minVal = minValue();
        double maxVal = maxValue();

        // Fixing negative values for log calculation (no negative values allowed -> NaN)
        if ( minVal < 0 ) {
            minVal = abs(minVal) + 1; // make sure it's bigger than 0
            maxVal += minVal;
            val += minVal;
            minVal = 0.01;
        };

        double result = ( log(val) - log(minVal) ) / ( log(maxVal) - log(minVal));

        // now we perform a "gamma-correction" on the result. Indeed, logarithmic gradients
        // often have the problem that all high values have roughly the same color. Note that
        // as firstColor() is not necessarily black and secondColor() is not necessarily white,
        // this is not exactly a "gamma-correction" in the usual sense.
        const double gamma = 1.4;
        result = exp(gamma * log(result));
        return result;
    } else {
        return ( val - minValue() ) / ( maxValue() - minValue() );
    }
}

QColor KalziumGradientType::firstColor() const
{
    return Prefs::minColor();
}

QColor KalziumGradientType::secondColor() const
{
    return Prefs::maxColor();
}

QColor KalziumGradientType::notAvailableColor() const
{
    return Qt::lightGray;
}

QColor KalziumGradientType::calculateColor( const double coeff ) const
{
    if ( ( coeff < 0.0 ) || ( coeff > 1.0 ) ) return notAvailableColor();

    QColor color2 = secondColor();
    QColor color1 = firstColor();

    int red = static_cast<int>( (color2.red() - color1.red()) * coeff + color1.red() );
    int green = static_cast<int>( (color2.green() - color1.green()) * coeff + color1.green() );
    int blue = static_cast<int>( (color2.blue() - color1.blue()) * coeff + color1.blue() );

    return QColor( red, green, blue );
}

KalziumCovalentRadiusGradientType* KalziumCovalentRadiusGradientType::instance()
{
    static KalziumCovalentRadiusGradientType kcrgt;
    return &kcrgt;
}

KalziumCovalentRadiusGradientType::KalziumCovalentRadiusGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumCovalentRadiusGradientType::name() const
{
    return "CovalentRadius";
}

QString KalziumCovalentRadiusGradientType::description() const
{
    return i18n( "Covalent Radius" );
}

double KalziumCovalentRadiusGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )
                 ->dataAsVariant( ChemicalDataObject::radiusCovalent, Prefs::lengthUnit() );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumCovalentRadiusGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::lengthUnit() );
}

int KalziumCovalentRadiusGradientType::decimals() const
{
    return 2;
}

double KalziumCovalentRadiusGradientType::minValue() const
{
    KUnitConversion::Value minValue( 0.32, KUnitConversion::Angstrom );
    return minValue.convertTo( Prefs::lengthUnit() ).number();
}

double KalziumCovalentRadiusGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 2.25, KUnitConversion::Angstrom );
    return minValue.convertTo( Prefs::lengthUnit() ).number();
}

bool KalziumCovalentRadiusGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicCovalentRadiusGradient();
}


KalziumVanDerWaalsRadiusGradientType* KalziumVanDerWaalsRadiusGradientType::instance()
{
    static KalziumVanDerWaalsRadiusGradientType kvdwrgt;
    return &kvdwrgt;
}

KalziumVanDerWaalsRadiusGradientType::KalziumVanDerWaalsRadiusGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumVanDerWaalsRadiusGradientType::name() const
{
    return "KalziumVanDerWaalsRadiusGradientType";
}

QString KalziumVanDerWaalsRadiusGradientType::description() const
{
    return i18n( "van Der Waals" );
}

double KalziumVanDerWaalsRadiusGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )
                 ->dataAsVariant( ChemicalDataObject::radiusVDW, Prefs::lengthUnit() );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumVanDerWaalsRadiusGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::lengthUnit() );
}

int KalziumVanDerWaalsRadiusGradientType::decimals() const
{
    return 1;
}


double KalziumVanDerWaalsRadiusGradientType::minValue() const
{
    KUnitConversion::Value minValue( 1.2, KUnitConversion::Angstrom );
    return minValue.convertTo( Prefs::lengthUnit() ).number();
}

double KalziumVanDerWaalsRadiusGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 3.0, KUnitConversion::Angstrom );
    return minValue.convertTo( Prefs::lengthUnit() ).number();
}

bool KalziumVanDerWaalsRadiusGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicVanDerWaalsRadiusGradient();
}


KalziumMassGradientType* KalziumMassGradientType::instance()
{
    static KalziumMassGradientType kargt;
    return &kargt;
}

KalziumMassGradientType::KalziumMassGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumMassGradientType::name() const
{
    return "AtomicMass";
}

QString KalziumMassGradientType::description() const
{
    return i18n( "Atomic Mass" );
}

double KalziumMassGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::mass );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumMassGradientType::unit() const
{
    return i18n("u");
}

int KalziumMassGradientType::decimals() const
{
    return 5;
}

double KalziumMassGradientType::minValue() const
{
    return 1.00794;
}

double KalziumMassGradientType::maxValue() const
{
    return 294.0;
}

bool KalziumMassGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicMassGradient();
}

KalziumBoilingPointGradientType* KalziumBoilingPointGradientType::instance()
{
    static KalziumBoilingPointGradientType kbpgt;
    return &kbpgt;
}

KalziumBoilingPointGradientType::KalziumBoilingPointGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumBoilingPointGradientType::name() const
{
    return "BoilingPoint";
}

QString KalziumBoilingPointGradientType::description() const
{
    return i18n( "Boiling Point" );
}

double KalziumBoilingPointGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::boilingpoint, Prefs::temperatureUnit() );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumBoilingPointGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::temperatureUnit() );
}

int KalziumBoilingPointGradientType::decimals() const
{
    return 3;
}

double KalziumBoilingPointGradientType::minValue() const
{
    KUnitConversion::Value minValue( 4.216, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

double KalziumBoilingPointGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 5870.0, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

bool KalziumBoilingPointGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicBoilingPointGradient();
}


KalziumMeltingPointGradientType* KalziumMeltingPointGradientType::instance()
{
    static KalziumMeltingPointGradientType kmpgt;
    return &kmpgt;
}

KalziumMeltingPointGradientType::KalziumMeltingPointGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumMeltingPointGradientType::name() const
{
    return "MeltingPoint";
}

QString KalziumMeltingPointGradientType::description() const
{
    return i18n( "Melting Point" );
}

double KalziumMeltingPointGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::meltingpoint, Prefs::temperatureUnit() );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumMeltingPointGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::temperatureUnit() );
}

int KalziumMeltingPointGradientType::decimals() const
{
    return 2;
}

double KalziumMeltingPointGradientType::minValue() const
{
    KUnitConversion::Value minValue( 0.94, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

double KalziumMeltingPointGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 3825.0, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

bool KalziumMeltingPointGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicMeltingPointGradient();
}

KalziumSOMGradientType* KalziumSOMGradientType::instance()
{
    static KalziumSOMGradientType kargt;
    return &kargt;
}

KalziumSOMGradientType::KalziumSOMGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumSOMGradientType::name() const
{
    return "SOM";
}

QString KalziumSOMGradientType::description() const
{
    return i18n( "State of matter" );
}

double KalziumSOMGradientType::value( int el ) const
{
    Q_UNUSED(el);
    return -1000;
}

QString KalziumSOMGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::temperatureUnit() );;
}

int KalziumSOMGradientType::decimals() const
{
    return 2;
}

double KalziumSOMGradientType::minValue() const
{
    KUnitConversion::Value minValue( 0.94, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

double KalziumSOMGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 5870.0, KUnitConversion::Kelvin );
    return minValue.convertTo( Prefs::temperatureUnit() ).number();
}

bool KalziumSOMGradientType::logarithmicGradient() const
{
    return true;
//     return Prefs::logarithmicSOMGradient();
}


KalziumElectronegativityGradientType* KalziumElectronegativityGradientType::instance()
{
    static KalziumElectronegativityGradientType kegt;
    return &kegt;
}

KalziumElectronegativityGradientType::KalziumElectronegativityGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumElectronegativityGradientType::name() const
{
    return "Electronegativity";
}

QString KalziumElectronegativityGradientType::description() const
{
    return i18n( "Electronegativity (Pauling)" );
}

double KalziumElectronegativityGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::electronegativityPauling );
    if ( v.type() != QVariant::Double ) return -1;
    return v.toDouble();
}

QString KalziumElectronegativityGradientType::unit() const
{
    return QString();
}

int KalziumElectronaffinityGradientType::decimals() const
{
    return 2;
}

double KalziumElectronegativityGradientType::minValue() const
{
    return 0.7;
}

double KalziumElectronegativityGradientType::maxValue() const
{
    return 3.98;
}

bool KalziumElectronegativityGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicElectronegativityGradient();
}

///DISCOVERYDATE///

KalziumDiscoverydateGradientType* KalziumDiscoverydateGradientType::instance()
{
    static KalziumDiscoverydateGradientType kegt;
    return &kegt;
}

KalziumDiscoverydateGradientType::KalziumDiscoverydateGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumDiscoverydateGradientType::name() const
{
    return "Discoverydate";
}

QString KalziumDiscoverydateGradientType::description() const
{
    return i18n( "Discovery date" );
}

double KalziumDiscoverydateGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::date );
    if ( v.value<int>() == 0 ) return -1;
    return v.toDouble();
}

QString KalziumDiscoverydateGradientType::unit() const
{
    return QString();
}

int KalziumDiscoverydateGradientType::decimals() const
{
    return 0;
}


double KalziumDiscoverydateGradientType::minValue() const
{
    return 1669.0;
}

double KalziumDiscoverydateGradientType::maxValue() const
{
    return QDate::currentDate().year();
}

bool KalziumDiscoverydateGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicDiscoverydateGradient();
}


///ELECTRONAFFINITY///

KalziumElectronaffinityGradientType* KalziumElectronaffinityGradientType::instance()
{
    static KalziumElectronaffinityGradientType kegt;
    return &kegt;
}

KalziumElectronaffinityGradientType::KalziumElectronaffinityGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumElectronaffinityGradientType::name() const
{
    return "Electronaffinity";
}

QString KalziumElectronaffinityGradientType::description() const
{
    return i18n( "Electronaffinity" );
}

double KalziumElectronaffinityGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::electronAffinity, Prefs::energiesUnit() );
    return v.toDouble();
}

QString KalziumElectronaffinityGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::energiesUnit() );
}

int KalziumElectronegativityGradientType::decimals() const
{
    return 2;
}

double KalziumElectronaffinityGradientType::minValue() const
{
    KUnitConversion::Value minValue( -0.07, KUnitConversion::Electronvolt );
    return minValue.convertTo( Prefs::energiesUnit() ).number();
}

double KalziumElectronaffinityGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 3.7, KUnitConversion::Electronvolt );
    return minValue.convertTo( Prefs::energiesUnit() ).number();
}

bool KalziumElectronaffinityGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicElectronaffinityGradient();
}

///FIRST IONIZATINO///

KalziumIonizationGradientType* KalziumIonizationGradientType::instance()
{
    static KalziumIonizationGradientType kegt;
    return &kegt;
}

KalziumIonizationGradientType::KalziumIonizationGradientType()
        : KalziumGradientType()
{
}

QByteArray KalziumIonizationGradientType::name() const
{
    return "Ionization";
}

QString KalziumIonizationGradientType::description() const
{
    return i18n( "First Ionization" );
}

double KalziumIonizationGradientType::value( int el ) const
{
    QVariant v = KalziumDataObject::instance()->element( el )->dataAsVariant( ChemicalDataObject::ionization, Prefs::energiesUnit() );
    return v.toDouble();
}

QString KalziumIonizationGradientType::unit() const
{
    return KalziumDataObject::instance()->unitAsString( Prefs::energiesUnit() );
}

int KalziumIonizationGradientType::decimals() const
{
    return 2;
}

double KalziumIonizationGradientType::minValue() const
{
    KUnitConversion::Value minValue( 3.89, KUnitConversion::Electronvolt );
    return minValue.convertTo( Prefs::energiesUnit() ).number();
}

double KalziumIonizationGradientType::maxValue() const
{
    KUnitConversion::Value minValue( 25.0, KUnitConversion::Electronvolt );
    return minValue.convertTo( Prefs::energiesUnit() ).number();
}

bool KalziumIonizationGradientType::logarithmicGradient() const
{
    return Prefs::logarithmicIonizationGradient();
}
