/***************************************************************************
    copyright            : (C) 2004, 2005, 2006  by Carsten Niehaus
    email                : cniehaus@kde.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "elementdataviewer.h"

#include <element.h>
#include <kplotaxis.h>
#include <kplotobject.h>

#include <klocale.h>
#include <kdebug.h>
#include <kactioncollection.h>
#include <kstandardaction.h>
#include <ktoolinvocation.h>
#include <kunitconversion/converter.h>
#include "prefs.h"

//QT-Includes
#include <QKeyEvent>
#include <QPen>
#include <QTimer>

AxisData::AxisData( AXISTYPE type) : currentDataType(-1)
{
    m_type = type;
}

double AxisData::value( int element ) const
{
    if ( ( element < 1 ) || ( element > dataList.count() ) )
        return 0.0;

    return dataList[ element-1 ];
}


ElementDataViewer::ElementDataViewer( QWidget *parent )
        : KDialog( parent ),
        m_yData( new AxisData( AxisData::Y ) ) ,
        m_xData( new AxisData( AxisData::X ) )
{
    setCaption( i18n( "Plot Data" ) );
    setButtons( Help | Close );
    setDefaultButton( Close );

    KalziumDataObject *kdo  = KalziumDataObject::instance();

    ui.setupUi( mainWidget() );

    m_timer = new QTimer( this );
    m_timer->setSingleShot( true );

    // setup the list of names
    foreach (Element * e, kdo->ElementList) {
        names << e->dataAsString( ChemicalDataObject::name );
        symbols << e->dataAsString( ChemicalDataObject::symbol );
        elecConfig<< e->dataAsString(ChemicalDataObject::electronicConfiguration);
        block << e->dataAsString( ChemicalDataObject::periodTableBlock );
    }

    m_actionCollection = new KActionCollection (this );
    KStandardAction::quit( this, SLOT(close()), m_actionCollection );

    connect( m_timer, SIGNAL(timeout()),
             this, SLOT(drawPlot()) );
    connect( ui.KCB_y, SIGNAL(activated(int)),
             this, SLOT(rangeChanged()) );
    connect( ui.KCB_x, SIGNAL(activated(int)),
             this, SLOT(rangeChanged()) );
    connect( ui.comboElementLabels, SIGNAL(activated(int)),
             this, SLOT(rangeChanged()) );
    connect( ui.comboElementType,   SIGNAL(activated(int)),
             this, SLOT(rangeChanged()) );
    connect( ui.from, SIGNAL(valueChanged(int)),
             this, SLOT(rangeChanged()) );
    connect( ui.to, SIGNAL(valueChanged(int)),
             this, SLOT(rangeChanged()) );
    connect( this, SIGNAL(helpClicked()), this, SLOT(slotHelp()) );
    connect( ui.full, SIGNAL (clicked()) ,
             this, SLOT(fullRange()) );
    drawPlot();

    resize( 650, 500 );
}

ElementDataViewer::~ElementDataViewer()
{
    delete m_yData;
    delete m_xData;
}

void ElementDataViewer::slotHelp()
{
    KToolInvocation::invokeHelp( "plot_data", "kalzium" );
}

void ElementDataViewer::rangeChanged()
{
    if ( ui.from->value() > ui.to->value() )
        ui.to->setValue( ui.from->value() );

    m_timer->stop();
    m_timer->start( 500 );
}

void ElementDataViewer::fullRange()
{
    ui.from -> setValue(1);
    ui.to   -> setValue(116);
}


void ElementDataViewer::setLimits()
{
    kDebug() << "ElementDataViewer::setLimits()";

    double x1 = 0.0, x2 = 0.0, y1 = 0.0, y2 = 0.0;

    getMinMax(x1, x2, m_xData);
    getMinMax(y1, y2, m_yData);

    kDebug() << x1 << " :: " << x2 << " ----- "  << y1 << " :: " << y2;

    //JH: add some padding to show all points
    double dx = 0.05*(x2-x1);
    double dy = 0.05*(y2-y1);
    x1 -= dx;
    x2 += dx;
    y1 -= dy;
    y2 += dy;

//X     // try to put a small padding to make the points on the axis visible
//X     double dx = ( to - from + 1 ) / 25 + 1.0;
//X     double dy = ( maxY - minY ) / 10.0;
//X     // in case that dy is quite small (for example, when plotting a single
//X     // point)
//X     if ( dy < 1e-7 )
//X         dy = maxY / 10.0;
//X     ui.plotwidget->setLimits( from - dx, to + dx, minY - dy, maxY + dy );

    ui.plotwidget->setLimits( x1, x2, y1, y2 );
}

void ElementDataViewer::getMinMax(double& min, double& max, AxisData * data)
{
    int firstElement = ui.from->value();
    int lastElement  = ui.to->value();

    double minValue = data->value(firstElement);
    double maxValue = data->value(firstElement);

    kDebug() << "Taking elements from " << firstElement << " to " << lastElement;

    for ( int _currentVal = firstElement; _currentVal <= lastElement; _currentVal++ )
    {//go over all selected elements
        double v = data->value( _currentVal );

        if ( minValue > v )
            minValue = v;
        if ( maxValue < v)
            maxValue = v;
    }

    kDebug() << "The value are ]"<< minValue << " , " << maxValue << "[.";

    min = minValue;
    max = maxValue;
}

void ElementDataViewer::keyPressEvent(QKeyEvent *e)
{
    switch ( e->key() )
    {
    case Qt::Key_Plus:
    case Qt::Key_Equal:
        slotZoomIn();
        break;
    case Qt::Key_Minus:
    case Qt::Key_Underscore:
        slotZoomOut();
        break;
    case Qt::Key_Escape:
        close();
        break;
    }
}

void ElementDataViewer::slotZoomIn() {}
void ElementDataViewer::slotZoomOut() {}

void ElementDataViewer::setupAxisData( AxisData * data )
{
    int selectedData = 0;
    if ( data->type() == AxisData::X )
        selectedData = ui.KCB_x->currentIndex();
    else
        selectedData = ui.KCB_y->currentIndex();

    data->currentDataType = selectedData;

    // init to _something_
    ChemicalDataObject::BlueObelisk kind = ChemicalDataObject::mass;
    QString caption;
    int unit = KUnitConversion::NoUnit;

    switch (selectedData)
    {
    case AxisData::NUMBER:
    {
        kind = ChemicalDataObject::atomicNumber;
        caption = i18n( "Atomic Number" );
        break;
    }
    case AxisData::MASS:
    {
        kind = ChemicalDataObject::mass;
        caption = i18n( "Atomic Mass" );
        break;
    }
    case AxisData::EN:
    {
        kind = ChemicalDataObject::electronegativityPauling;
        caption = i18n( "Electronegativity" );
        break;
    }
    case AxisData::MELTINGPOINT:
    {
        kind = ChemicalDataObject::meltingpoint;
        caption = i18n( "Melting Point" );
        unit = Prefs::temperatureUnit();
        break;
    }
    case AxisData::BOILINGPOINT:
    {
        kind = ChemicalDataObject::boilingpoint;
        caption = i18n( "Boiling Point" );
        unit = Prefs::temperatureUnit();
        break;
    }
    case AxisData::ATOMICRADIUS:
    {
        kind = ChemicalDataObject::radiusVDW;
        caption = i18n( "Atomic Radius" );
        unit = Prefs::lengthUnit();
        break;
    }
    case AxisData::COVALENTRADIUS:
    {
        kind = ChemicalDataObject::radiusCovalent;
        caption = i18n( "Covalent Radius" );
        unit = Prefs::lengthUnit();
        break;
    }
    }
    KalziumDataObject *kdo = KalziumDataObject::instance();

    DoubleList dblDataList;
    foreach (Element * element, kdo->ElementList) {
        dblDataList << element->dataAsVariant( kind, unit ).toDouble();
    }

    data->dataList.clear();
    data->dataList << dblDataList;
    data->kind = kind;

    if ( unit != KUnitConversion::NoUnit ) {
        QString stringUnit =  KalziumDataObject::instance()->unitAsString( unit );
        data->unit = QString(" " + stringUnit);

        caption.append(" [");
        caption.append( stringUnit );
        caption.append("]");
    }

    if ( data->type() == AxisData::X ) {
        ui.plotwidget->axis(KPlotWidget::BottomAxis)->setLabel( caption );
    } else {
        ui.plotwidget->axis(KPlotWidget::LeftAxis)->setLabel( caption );
        ui.plotwidget->axis(KPlotWidget::RightAxis)->setLabel( caption );
    }
}

void ElementDataViewer::drawPlot()
{
    /*
     * to be 100% safe delete the old list
     */
    ui.plotwidget->removeAllPlotObjects();

    /*
     * spare the next step in case everything is already set and done
     */
    if ( m_yData->currentDataType != ui.KCB_y->currentIndex() )
        initData();

    if ( m_xData->currentDataType != ui.KCB_x->currentIndex() )
        initData();

    /*
     * if the user selected the elements 20 to 30 the list-values are 19 to 29!!!
     */
    const int tmpfrom = ui.from->value();
    const int tmpto = ui.to->value();
    const int from = qMin( tmpfrom, tmpto );
    const int to = qMax( tmpfrom, tmpto );

    /*
     * The number of elements. #20 to 30 are 30-20+1=11 Elements
     */
    int num = to-from+1;

    setLimits();

    QSet<int> metals, metalloids, nonMetals;

    metals << 3 << 4 << 11 << 12 << 13;
    for ( int i = 19; i <= 31 ; i++ )
        metals << i;
    for ( int i = 37; i <= 50 ; i++ )
        metals << i;
    for ( int i = 55; i <= 83 ; i++ )
        metals << i;
    for ( int i = 87; i <= 116; i++ )
        metals << i;

    metalloids << 5 << 14 << 32 << 33 << 51 << 52 << 84 << 85;

    nonMetals << 1 << 2 << 6 << 7 << 8 << 9 << 10 << 15 << 16;
    nonMetals << 17 << 18 << 34 << 35 << 36 << 53 << 54 << 86;

    /*
     * check if the users wants to see the elementnames or not
     */
    int whatShow = ui.comboElementLabels->currentIndex();

    /*
     * Checks what type of element, the user wants to plot.
     * example, metal, non-metal.
     */
    int whichType = ui.comboElementType->currentIndex();

    KPlotObject* dataPointGreen = 0;
    KPlotObject* dataPointRed = 0;

    double av_x = 0.0;
    double max_x = m_xData->value(from);
    double min_x = m_xData->value(from);
    double av_y = 0.0;
    double max_y = m_yData->value(from);
    double min_y = m_yData->value(from);

    /*
     * iterate for example from element 20 to 30 and contruct
     * the KPlotObjects
     */
    dataPointGreen = new KPlotObject(
        Qt::green,
        KPlotObject::Points,
        4,
        KPlotObject::Star );
    dataPointGreen->setLabelPen( QPen( Qt::blue ) );

    dataPointRed = new KPlotObject(
        Qt::red,
        KPlotObject::Points,
        4,
        KPlotObject::Star );	//Star can be replaced with a cross
    dataPointRed->setLabelPen( QPen( Qt::blue ) );

    for ( int i = from; i < to+1 ; i++ )
    {
        double value_y = m_yData->value( i );
        double value_x = m_xData->value( i );

        bool known = ( ( value_y ) > 0.0 ) ? 1 : 0;
        //The element is know if its value is not zero
        bool belongs = 1;
        //The value of belongs is one if it belongs to the particular group

        //See if the particular element belongs to the selected set or not.
        //If a particular group of elements is selected,
        if ( whichType > 0)
        {
            belongs = 0;
            switch (whichType)
            {
            case 1: //Plot only metals
                belongs = metals . contains(i);
                break;
            case 2: //plot only nonmetals and metalloids
                belongs = ( nonMetals.contains(i) || metalloids.contains(i) );
                break;
            case 3: //Plot s block elements
                belongs = ( block [ i - 1 ] == "s");
                break;
            case 4: //Plot p block elements
                belongs = ( block [ i - 1 ] == "p");
                break;
            case 5: //Plot d block elements
                belongs = ( block [ i - 1 ] == "d");
                break;
            case 6: //plot f block elements
                belongs = ( block [ i - 1 ] == "f");
                break;
            case 7: // Noble gases
                belongs = ( ( elecConfig [ i - 1 ] ) . endsWith("p6") );
                belongs |= ( i == 2 );		//Include Helium
                break;
            case 8: // Alkalie metals
                belongs = ( ( elecConfig [ i - 1 ] ) . endsWith("s1") );
                belongs &= ( block [ i - 1 ] == "s" ); //exclude chromium
                belongs &= ( i != 1 );		//exclude Hydrogen
                break;
            case 9: // Alkaline earth metals
                belongs = ( ( elecConfig [ i - 1 ] ) . endsWith("s2") );
                belongs &= ( block [ i - 1 ] == "s" ); //exclude chromium
                belongs &= ( i != 2 );		//exclude Helium
                break;
            case 10: // Lanthanides
                // If element i is an f block element, with
                // electronic configuration containing "f4" in it
                // or the element is Lanthanum
                belongs = ( ( block [ i - 1 ] == "f") && \
                            ( ( elecConfig [ i - 1 ] ) . contains ("4f") ) ) || \
                          ( i == 57 );	//Lanthanum 57
                break;
            case 11: //Actinides
                //If element i is an f block element, with
                // electronic configuration containing "f5" in it
                // or the element is Actinium
                belongs = ( ( ( block [ i - 1 ] == "f") ) && \
                            ( ( elecConfig [ i - 1 ] ) . contains ("5f") ) ) || \
                          ( i == 89 );	//Actinium 89
                break;
            case 12: //Radio active
                belongs = ( ( i == 43 ) || ( i == 61 ) || ( i > 84 ) );
                // Technitium prothomium and then polonium onwards.
                break;
            default:
                whichType = 0;
                belongs = 1;
            }
        }
        if ( belongs )
        {

            if ( known )
            {
                av_x += value_x;
                av_y += value_y;

                if (value_x > max_x) {
                    max_x = value_x;
                }
                if (value_y > max_y) {
                    max_y = value_y;
                }
                if (value_x < min_x) {
                    min_x = value_x;
                }
                if (value_y < min_y) {
                    min_y = value_y;
                }

                QString lbl;
                if ( whatShow > 0 )//The users wants to see the labels
                {
                    lbl = whatShow == 1 ? names[i-1] : symbols[i-1];
                }

                dataPointGreen->addPoint( value_x, value_y, lbl );
            }
            else//unknown value
            {
                //num is required while finding the average, if an element is not
                //known it should not contribute to the average.
                num--;

                QString lbl;
                if ( whatShow > 0 )//The users wants to see the labels
                {
                    lbl = whatShow == 1 ? names[i-1] : symbols[i-1];
                }

                dataPointRed->addPoint( value_x, value_y, lbl );
                //For an Unknown value, use a red point to mark the data-point.
            }
        }

        else//The element does not belong to the set
        {
            //num is required while finding average, if an element is
            //not in the selected set, it should not contribute to the avg.
            num--;
        }
    }

    ui.plotwidget->addPlotObject( dataPointGreen );
    ui.plotwidget->addPlotObject( dataPointRed );


    if ( num > 0 )
    {
        //now set the values for the min, max and avarage value
        ui.av_x->setText( QString::number( av_x / num ).append(m_xData->unit) );
        ui.minimum_x->setText( QString::number( min_x ).append(m_xData->unit) );
        ui.maximum_x->setText( QString::number( max_x ).append(m_xData->unit) );

        ui.av_y->setText( QString::number( av_y / num ).append(m_yData->unit) );
        ui.minimum_y->setText( QString::number( min_y ).append(m_yData->unit) );
        ui.maximum_y->setText( QString::number( max_y ).append(m_yData->unit) );
    }
    else
    {
        ui.av_x->setText( QString::number( 0.0 ) );
        ui.minimum_x->setText( QString::number( 0.0 ) );
        ui.maximum_x->setText( QString::number( 0.0 ) );
        ui.av_y->setText( QString::number( 0.0 ) );
        ui.minimum_y->setText( QString::number( 0.0 ) );
        ui.maximum_y->setText( QString::number( 0.0 ) );
    }
}

void ElementDataViewer::initData()
{
    setupAxisData(m_xData);
    setupAxisData(m_yData);
}

#include "elementdataviewer.moc"

