#include "Panel_geometry.h"

#include "Measure_geometry.h"
#include "Project.h"
#include "Project_kit.h"
#include "Model.h"
#include "Atom_kit.h"
#include "Unit.h"
#include "User.h"
#include "Point_3D_impl.h"
#include "Selector.h"

#include "Command_template.h"
#include "Qt_command.h"

#include <QRegExpValidator>
#include <QDoubleValidator>
#include <QMenu>
#include <QTextStream>

#include <sstream>
#include <string>

namespace MM
{

class Qt_unit : public Qt_command
{
    QDoubleSpinBox *        box_;
    Unit const &            unit_;
    Text                    unit_type_;

public:
    Qt_unit (QDoubleSpinBox * box,
             Unit const &     unit,
             Text const &     type, 
             QObject *        parent=0)
    : 
        Qt_command (parent, create_command(*this, &Qt_unit::handle)),  
        box_(box), unit_(unit), unit_type_(type) { }

    void    handle () 
    {
        unit_.set_current_type (unit_type_);
        //box_->blockSignals (true);
        box_->setValue (unit_.value());
        box_->setSuffix (Text(" ") + unit_type_);
        //box_->blockSignals (false);
    }

    static void map (QDoubleSpinBox * box, Unit const & unit)
    {
        box->setSuffix (Text(" ") + unit.current_type());

        for (int i=0;  i<unit.type_count();  ++i)
        {
            Qt_unit * change_unit = new Qt_unit (box, unit, unit.type(i), box);

            QAction * menu = new QAction (unit.type(i), box);
            connect(menu,           SIGNAL (triggered()),
                    change_unit,    SLOT   (execute()));
            box->addAction (menu);
        }
        box->setContextMenuPolicy (Qt::ActionsContextMenu);
    }
};

Panel_geometry::
~Panel_geometry ()
{
    Selector::numerate_atoms   (false);
    Selector::when_atom_picked (add_atom_, false);
    Selector::set_max_picked_atoms (1);
}

Panel_geometry::
Panel_geometry ()
:
    Panel ("Geometry", "right"),
    old_slider_(0), 
    add_atom_  (create_command_1 (*this, &Panel_geometry::add_atom))
{
    setupUi (this);

    Selector::numerate_atoms (true);
    Selector::when_atom_picked (add_atom_, true);

    pushButton_Make_monitor->hide();
    //=========================================================================

    int i;

    for (i=0;  i<Measure_geometry::type_counter(); ++i)
    {
        comboBox_1->addItem (Measure_geometry::type_name(i));
        comboBox_2->addItem (Measure_geometry::type_name(i));
        comboBox_3->addItem (Measure_geometry::type_name(i));
        comboBox_4->addItem (Measure_geometry::type_name(i));
    }

    connect (comboBox_1,    SIGNAL(currentIndexChanged (const QString &)), 
             this,          SLOT  (set_point_type      (const QString &)));
    connect (comboBox_2,    SIGNAL(currentIndexChanged (const QString &)), 
             this,          SLOT  (set_point_type      (const QString &)));
    connect (comboBox_3,    SIGNAL(currentIndexChanged (const QString &)), 
             this,          SLOT  (set_point_type      (const QString &)));
    connect (comboBox_4,    SIGNAL(currentIndexChanged (const QString &)), 
             this,          SLOT  (set_point_type      (const QString &)));

    connect (spinBox_1,             SIGNAL(valueChanged (const QString &)), 
             this,                  SLOT  (set_ID       (const QString &)));
    connect (spinBox_2,             SIGNAL(valueChanged (const QString &)), 
             this,                  SLOT  (set_ID       (const QString &)));
    connect (spinBox_3,             SIGNAL(valueChanged (const QString &)), 
             this,                  SLOT  (set_ID       (const QString &)));
    connect (spinBox_4,             SIGNAL(valueChanged (const QString &)), 
             this,                  SLOT  (set_ID       (const QString &)));

    connect (lineEdit_1,            SIGNAL(editingFinished()), 
             this,                  SLOT  (set_space_position_1()));
    connect (lineEdit_2,            SIGNAL(editingFinished()), 
             this,                  SLOT  (set_space_position_2()));
    connect (lineEdit_3,            SIGNAL(editingFinished()), 
             this,                  SLOT  (set_space_position_3()));
    connect (lineEdit_4,            SIGNAL(editingFinished()), 
             this,                  SLOT  (set_space_position_4()));

    connect (radioButton_Distance,       SIGNAL(toggled (bool)),
             this,                       SLOT (measure_distance (bool)));
    connect (radioButton_Angle,          SIGNAL(toggled (bool)),
             this,                       SLOT (measure_angle (bool)));
    connect (radioButton_Dihedral_angle, SIGNAL(toggled (bool)),
             this,                       SLOT (measure_dihedral_angle (bool)));


    connect(stackedWidget,          SIGNAL (currentChanged (int)),
            this,                   SLOT   (stackedWidgetChanged (int)));

    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    Unit const       & distance_unit = meter.last_length();

    Qt_unit::map (doubleSpinBox_Distance, distance_unit);

    connect(doubleSpinBox_Distance,     SIGNAL (editingFinished ()),
            this,                       SLOT   (set_distance    ()));
    connect(toolButton_Distance_plus,   SIGNAL (clicked ()),
            this,                       SLOT   (increment_distance ()));
    connect(toolButton_Distance_minus,  SIGNAL (clicked ()),
            this,                       SLOT   (decrement_distance ()));

    Unit const       & angle_unit = meter.last_angle();
    Qt_unit::map (doubleSpinBox_Angle, angle_unit);

    connect(doubleSpinBox_Angle,     SIGNAL (editingFinished ()),
            this,                    SLOT   (set_angle    ()));
    connect(toolButton_Angle_plus,   SIGNAL (clicked ()),
            this,                    SLOT   (increment_angle ()));
    connect(toolButton_Angle_minus,  SIGNAL (clicked ()),
            this,                    SLOT   (decrement_angle ()));



    connect(horizontalSlider,           SIGNAL (valueChanged (int)),
            this,                       SLOT   (set_value    (int)));


    connect(pushButton_Restrain,        SIGNAL (clicked  ()),
            this,                       SLOT   (restrain ()));

    connect(toolButto_Fix,              SIGNAL (clicked  ()),
            this,                       SLOT   (fix ()));

    

    radioButton_Distance->setChecked (true);
    if (Project::singleton().model_count() != 0)
    {
        measure_distance (true);
        renew ();
    }
    stackedWidgetChanged (0);
}

void Panel_geometry::
add_atom (Atom & atom)
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();

    int atom_n = atom.kit().number();

    //meter.set_active    (measured_count () - 1, false);
    //meter.prepend_point ();
    //meter.set_atom_type (0, atom.kit().number());

    //meter.set_active (0, false);
    //meter.append_point ();
    //meter.set_atom_type (measured_count () - 1, atom.kit().number());

    if (radioButton_Distance->isChecked()) 
        meter.set_active (2, false);
    else if (radioButton_Angle->isChecked()) 
        meter.set_active (1, false);
    else if (radioButton_Dihedral_angle->isChecked()) 
        meter.set_active (0, false);
    else 
        FLAW ("");
    meter.append_point ();
    meter.set_atom_type (3, atom_n);

    renew();
}

void Panel_geometry::
renew ()
{
    renew (Model_event::Structure_changed);
}

void Panel_geometry::
renew_current_model (Model &)
{
    renew (Model_event::Structure_changed);
}

void Panel_geometry::
renew (Model_event::Hint hint)
{
    int models                 = Project::singleton().model_count();
    Measure_geometry & meter   = Project::singleton().kit().measure_geometry();
    int  active_points_count   = meter.active_count();
    int  measured_points_count = measured_count ();

    //if (hint == Model_event::Structure_changed)

    QPalette active  = spinBox_1->palette(); 
    QPalette passive = spinBox_1->palette();
    active. setBrush (QPalette::Text, QColor(0, 0, 0));
    passive.setBrush (QPalette::Text, QColor(150, 150, 100));

    if (meter.is_active (0))
        spinBox_1->setPalette (active);
    else
        spinBox_1->setPalette (passive);

    if (meter.is_active (1))
        spinBox_2->setPalette (active);
    else
        spinBox_2->setPalette (passive);

    if (meter.is_active (2))
        spinBox_3->setPalette (active);
    else
        spinBox_3->setPalette (passive);

    if (meter.is_active (3))
        spinBox_4->setPalette (active);
    else
        spinBox_4->setPalette (passive);

    if (models == 0 || active_points_count != measured_points_count)
    {
        lineEdit_1->setText ("0 0 0");
        lineEdit_2->setText ("0 0 0");
        lineEdit_3->setText ("0 0 0");
        lineEdit_4->setText ("0 0 0");
        stackedWidget   ->setEnabled (false);
        horizontalSlider->setEnabled (false);

        Text points_info;
        meter.get_points_info (points_info);
        label_Info->setText (points_info);
    }
    else
    {
        if (hint == Model_event::Structure_changed || 
            hint == Model_event::Conformation_changed)
        {
            stackedWidget   ->setEnabled (true);
            horizontalSlider->setEnabled (true);

            //Model & model = Project::singleton().current_model();

            if (active_points_count >= 2)
            {
                int index;
                comboBox_1->blockSignals (true);
                comboBox_2->blockSignals (true);
                comboBox_3->blockSignals (true);
                comboBox_4->blockSignals (true);
                index = comboBox_1->findText (meter.type(0));
                comboBox_1->setCurrentIndex (index);
                index = comboBox_2->findText (meter.type(1));
                comboBox_2->setCurrentIndex (index);
                index = comboBox_3->findText (meter.type(2));
                comboBox_3->setCurrentIndex (index);
                index = comboBox_4->findText (meter.type(3));
                comboBox_4->setCurrentIndex (index);
                comboBox_1->blockSignals (false);
                comboBox_2->blockSignals (false);
                comboBox_3->blockSignals (false);
                comboBox_4->blockSignals (false);

                spinBox_1->blockSignals (true);
                spinBox_2->blockSignals (true);
                spinBox_3->blockSignals (true);
                spinBox_4->blockSignals (true);
                spinBox_1->setValue (meter.number(0));
                spinBox_2->setValue (meter.number(1));
                spinBox_3->setValue (meter.number(2));
                spinBox_4->setValue (meter.number(3));
                spinBox_1->blockSignals (false);
                spinBox_2->blockSignals (false);
                spinBox_3->blockSignals (false);
                spinBox_4->blockSignals (false);

                lineEdit_1->blockSignals (true);
                lineEdit_2->blockSignals (true);
                lineEdit_3->blockSignals (true);
                lineEdit_4->blockSignals (true);
                Point_3D_impl p(0,0,0);
                meter.get_space_position (0, p);
                lineEdit_1->setText (QString("%1 %2 %3").
                    arg(p.x()).arg(p.y()).arg(p.z()));
                meter.get_space_position (1, p);
                lineEdit_2->setText (QString("%1 %2 %3").
                    arg(p.x()).arg(p.y()).arg(p.z()));
                meter.get_space_position (2, p);
                lineEdit_3->setText (QString("%1 %2 %3").
                    arg(p.x()).arg(p.y()).arg(p.z()));
                meter.get_space_position (3, p);
                lineEdit_4->setText (QString("%1 %2 %3").
                    arg(p.x()).arg(p.y()).arg(p.z()));
                lineEdit_1->blockSignals (false);
                lineEdit_2->blockSignals (false);
                lineEdit_3->blockSignals (false);
                lineEdit_4->blockSignals (false);

                Text info;
                meter.get_info (info);
                double value = meter.parameter().value();

                if (active_points_count == 2)
                {
                    doubleSpinBox_Distance->blockSignals (true);
                    doubleSpinBox_Distance->setValue     (value);
                    doubleSpinBox_Distance->blockSignals (false);
                }
                else
                {
                    doubleSpinBox_Angle->blockSignals (true);
                    doubleSpinBox_Angle->setValue     (value);
                    doubleSpinBox_Angle->blockSignals (false);
                }

                horizontalSlider->blockSignals (true);
                horizontalSlider->setSliderPosition (value * 1000);
                old_slider_ = value * 1000;
                horizontalSlider->blockSignals (false);

                //to_user().status(info);
                Text points_info;
                meter.get_points_info (points_info);
                label_Info->setText (points_info);

                if (meter.support_restraint ())
                    pushButton_Restrain->setEnabled (true);
                else
                    pushButton_Restrain->setEnabled (false);

            }
            else
                doubleSpinBox_Distance->setSpecialValueText ("-");
        }
    }
}

void Panel_geometry::
set_point_type (const QString & point_type)
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    int         box;
    QSpinBox *  spinBox = 0;
    
    if      (comboBox_1->hasFocus())    {box = 0;    spinBox = spinBox_1;}
    else if (comboBox_2->hasFocus())    {box = 1;    spinBox = spinBox_2;}
    else if (comboBox_3->hasFocus())    {box = 2;    spinBox = spinBox_3;}
    else if (comboBox_4->hasFocus())    {box = 3;    spinBox = spinBox_4;}
    else FLAW ("Wrong QComboBox.");

    if (point_type == "Atom")
        meter.set_atom_type (box, spinBox->value());

    else if (point_type == "Mass center")
        meter.set_mass_center_type (box, spinBox->value());

    else if (point_type == "Space position")
    {
        Point_3D_impl position (0,0,0);
        get_space_position (box, position);
        meter.set_space_position_type (box, position);
    }
}

void Panel_geometry::
set_ID (const QString & id)
{
    bool is_int;
    int  n = id.toInt (&is_int);
    int  box = -1;

    if      (spinBox_1->hasFocus())        box = 0;
    else if (spinBox_2->hasFocus())        box = 1;
    else if (spinBox_3->hasFocus())        box = 2;
    else if (spinBox_4->hasFocus())        box = 3;

    if (box == -1)
        FLAW ("Wrong SpinBox.");

    if (is_int)
        Project::singleton().kit().measure_geometry().set_number (box, n);

    renew ();
}

Point_3D_impl position (0,0,0);

void Panel_geometry::
set_space_position_1 ()
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    if (get_space_position (0, position))
        meter.set_space_position (0, position);
}

void Panel_geometry::
set_space_position_2 ()
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    if (get_space_position (1, position))
        meter.set_space_position (1, position);
}

void Panel_geometry::
set_space_position_3 ()
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    if (get_space_position (2, position))
        meter.set_space_position (2, position);
}

void Panel_geometry::
set_space_position_4 ()
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    if (get_space_position (3, position))
        meter.set_space_position (3, position);
}

void Panel_geometry::
measure_distance (bool on)
{
    if (!on)
        return;

    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    meter.set_active (0, false);
    meter.set_active (1, false);
    meter.set_active (2, true);
    meter.set_active (3, true);

    comboBox_1->setEnabled (false);
    spinBox_1 ->setEnabled (false);
    lineEdit_1->setEnabled (false);

    comboBox_2->setEnabled (false);
    spinBox_2 ->setEnabled (false);
    lineEdit_2->setEnabled (false);

    Selector::set_max_picked_atoms (2);
    stackedWidget->setCurrentIndex (0);
    renew ();
}

void Panel_geometry::
measure_angle (bool on)
{
    if (!on)
        return;

    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    meter.set_active (0, false);
    meter.set_active (1, true);
    meter.set_active (2, true);
    meter.set_active (3, true);

    comboBox_1->setEnabled (false);
    spinBox_1 ->setEnabled (false);
    lineEdit_1->setEnabled (false);

    comboBox_2->setEnabled (true);
    spinBox_2 ->setEnabled (true);
    lineEdit_2->setEnabled (true);

    Selector::set_max_picked_atoms (3);
    stackedWidget->setCurrentIndex (1);
    renew ();
}

void Panel_geometry::
measure_dihedral_angle (bool on)
{
    if (!on)
        return;

    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    meter.set_active (0, true);
    meter.set_active (1, true);
    meter.set_active (2, true);
    meter.set_active (3, true);

    comboBox_1->setEnabled (true);
    spinBox_1 ->setEnabled (true);
    lineEdit_1->setEnabled (true);

    comboBox_2->setEnabled (true);
    spinBox_2 ->setEnabled (true);
    lineEdit_2->setEnabled (true);

    Selector::set_max_picked_atoms (4);
    stackedWidget->setCurrentIndex (1);
    renew ();
}


void Panel_geometry::
set_value (double value)
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    meter.set_parameter (value);
    renew ();
}

void Panel_geometry::
set_value (int i)
{
    add_value (double (i-old_slider_) / 1000.);
    old_slider_ = i;
//    set_value (double (i) / 1000.);
}

void Panel_geometry::
add_value (double value)
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();
    meter.add_parameter (value);
    renew ();
}

void Panel_geometry::set_distance ()
    {set_value (doubleSpinBox_Distance->value());}

void Panel_geometry::increment_distance ()
    {add_value (doubleSpinBox_Distance_increment->value());}

void Panel_geometry::decrement_distance ()
    {add_value (-doubleSpinBox_Distance_increment->value());}

void Panel_geometry::set_angle ()
    {set_value (doubleSpinBox_Angle->value());}

void Panel_geometry::increment_angle ()
    {add_value (doubleSpinBox_Angle_increment->value());}

void Panel_geometry::decrement_angle ()
    {add_value (-doubleSpinBox_Angle_increment->value());}

void Panel_geometry::
stackedWidgetChanged (int i)
{
    Text panel = stackedWidget->widget(i)->objectName();
    horizontalSlider->blockSignals (true);

    if (panel == "Distance")
    {
        //horizontalSlider->setTickPosition (QSlider::TicksBelow);
        //horizontalSlider->setTickInterval (1000);
        //horizontalSlider->setRange (1, 10001);
        horizontalSlider->setRange (1, 10000);
    }
    else if (panel == "Angle")
    {
        //horizontalSlider->setTickPosition (QSlider::TicksBelow);
        //horizontalSlider->setTickInterval (45000);
        //horizontalSlider->setRange (-180000, 180000);
        horizontalSlider->setRange (-180000, 180000);
    }
    else
        FLAW ("Wrong page name.");

    horizontalSlider->blockSignals (false);
}

void Panel_geometry::
showEvent (QShowEvent * event)
{
    QWidget::showEvent (event);

//    if (isVisible ())
        renew ();
}

int Panel_geometry::
measured_count ()
{
    if (radioButton_Distance->isChecked())
        return 2;

    if (radioButton_Angle->isChecked())
        return 3;

    if (radioButton_Dihedral_angle->isChecked())
        return 4;

    return 0;
}

bool Panel_geometry::
get_space_position (int point_N, Point_3D & result)
{
    switch (point_N)
    {
    case 0:     return get_space_position (lineEdit_1, result);
    case 1:     return get_space_position (lineEdit_2, result);
    case 2:     return get_space_position (lineEdit_3, result);
    case 3:     return get_space_position (lineEdit_4, result);
    default:    FLAW ("Out of range.");
    }
}

bool Panel_geometry::
get_space_position (QLineEdit * lineEdit, Point_3D & result)
{
    //std::string line = lineEdit->text().toStdString ();
    //std::istringstream in (line);
    //double x, y, z;
    //in >> x >> y >> z;
    //if (in.fail())
    //    return false;

    QString str = lineEdit->text();
    QTextStream in (&str);
    double x, y, z;
    in >> x >> y >> z;
    if (in.status() == QTextStream::ReadCorruptData)
        return false;

    result.set_x (x);
    result.set_y (y);
    result.set_z (z);
    return true;
}

void Panel_geometry::
restrain ()
{
    Measure_geometry & meter = Project::singleton().kit().measure_geometry();

    meter.new_distance_restraint 
        (doubleSpinBox_Restrain_R_eqv->value(),
         doubleSpinBox_Restrain_K_r  ->value());
}

void Panel_geometry::
fix ()
{
    doubleSpinBox_Restrain_R_eqv->setValue (doubleSpinBox_Distance->value());
}

}//MM
