#if defined WIN32
#pragma warning(disable : 4786)
#endif

#include "Module.h"

#include "Create.h"
#include "Command_template.h"
#include "Model.h"
#include "Project.h"
#include "Atom_kit.h"
#include "Model_kit.h"
//#include "Model_adapter.h"
#include "Lock.h"
#include "Conventional_interactions.h" //fix
#include "System_of_Units.h"
#include "Energy_unit.h"
#include "Panel_force_field.h"
#include "Panel_performance.h"

//fix remove (Qt)
#include "Main_window.h"

#include "Timer.h"
#include "User.h"
#include "Log.h"
#include "Profiler.h"

#include <math.h>

namespace MM
{
class Module_energy : public Module
{
    static Module_energy    singleton_;
                            Module_energy();
    void                    init ();

public:
    void                    stop ();
    void                    calc_energy ();
    void                    calc_intermolecular_energy ();
    void                    calc_gradient ();

    void                    strip ();
    void                    assign_force_field ();
    void                    force_field   ();
    void                    performance   ();
};
Module_energy  Module_energy::singleton_;

Module_energy::Module_energy() : Module ("Compute") { }

void Module_energy::
init ()
{
    struct Ad_hoc : public Command_1 <bool> 
    {
        void execute (bool on) 
        {
            Project & project = Project::singleton();
            
            if (project.model_count() != 0)
            {
                Model & model = project.current_model();
                if (&model != &Model::none())
                    model.kit().interaction().common().set_ad_hoc (on);
            }
        }
    };

    insert_menu_separator ("&Compute");

    adopt_command (create_command (*this, &Module_energy::stop))
        .set_menu        ("&Compute", "Stop");

    insert_menu_separator ("&Analyse");

    adopt_command (create_command (*this, &Module_energy::calc_intermolecular_energy))
        .set_icon        ("Analyse", "intermolecular.png")
        .set_menu        ("&Analyse", "Intermolecular energy")
        .set_tool_tip    ("Calculate intermolecular energy");

    adopt_command (create_command (*this, &Module_energy::calc_energy))
        .set_menu        ("&Analyse", "&Energy")
        .set_accelerator ("Ctrl+E")
        .set_icon        ("Analyse", "energy.png")
        .set_tool_tip    ("Calculate Energy");

    adopt_command (create_command (*this, &Module_energy::calc_gradient))
        .set_menu        ("&Analyse", "&Gradient")
        .set_accelerator ("Ctrl+G")
        //.set_icon        ("Compute", "gradient.png")
        .set_tool_tip    ("Calculate relative gradient");

    insert_menu_separator ("&Compute");

    adopt_command (new Ad_hoc)
        .set_menu        ("&Compute", "&Ad hoc", false);

    insert_menu_separator ("&Compute");


    adopt_command (create_command (*this, &Module_energy::strip))
        .set_menu        ("&Build", "&Strip");

    adopt_command (create_command (*this, &Module_energy::assign_force_field))
        .set_menu        ("Settings", "Assign &FF types");

    adopt_command (create_command (*this, &Module_energy::force_field))
        .set_menu        ("Settings", "&Force field");

    adopt_command (create_command (*this, &Module_energy::performance))
        .set_menu        ("Settings", "&Performance");
}

void Module_energy::
stop ()
{
    Main_window::singleton()->toolButton_Stop->animateClick();
}

void Module_energy::
calc_energy ()
{
    Model       & model       = Project::singleton().current_model();
    Interaction & interaction = model.kit().interaction();
    model.kit().strip (true);

    
    //Conventional_interactions & i = 
    //    dynamic_cast<Conventional_interactions &>(interaction);
    //Lock <Conventional_interactions> lock (i);

    Timer timer;
    double E = interaction.potential ();
    double time = timer.elapsed();

    Text report;
    interaction.report (&report);
    report += Text("   ") + Text(time, 2) + " sec   ";
    to_user(). info   (report);

    Energy_unit value;
    System_of_Units::singleton().set_energy (value, E);
    to_user(). info (Text("Energy ") + value.kcalImol() + " kcal/mol    " 
                                     + value.kJImol()   + " kJ/mol      "
                                     + value.Hartrees() + " Hartrees    "
                                     + value.K()        + " K           "
                                     );
}

void Module_energy::
calc_intermolecular_energy ()
{
    Model       & model       = Project::singleton().current_model();
    Common_interactions & interaction = model.kit().interaction().common();
    model.kit().strip (true);

    
    //Conventional_interactions & i = 
    //    dynamic_cast<Conventional_interactions &>(interaction);
    //Lock <Conventional_interactions> lock (i);

    Timer timer;
    interaction.potential ();
    double time = timer.elapsed();

    Text report;
    report += Text("   ") + Text(time, 2) + " sec   \n";
    report += Text("intermolecular Van der Waals ")   + interaction.last_intermolecular_Van_der_Waals_potential();
    report += Text("\nintermolecular electrostatic ") + interaction.last_intermolecular_electrostatic_potential();
    report += Text("\nintermolecular induction ")     + interaction.last_intermolecular_induction_potential();
    to_user(). info   (report);

    Energy_unit value;
    System_of_Units::singleton().set_energy (value, interaction.last_intermolecular_potential());
    to_user(). info (Text(" ") + value.kcalImol() + " kcal/mol    " 
                                     + value.kJImol()   + " kJ/mol      "
                                     + value.Hartrees() + " Hartrees    "
                                     + value.K()        + " K           "
                                     );
}

void Module_energy::
calc_gradient ()
{
    Model       & model       = Project::singleton().current_model();
    Interaction & interaction = model.kit().interaction();
    model.kit().strip (true);

    //Model_adapter adapter(model);
    
    //Conventional_interactions & i = 
    //    dynamic_cast<Conventional_interactions &>(interaction);
    //Lock <Conventional_interactions> lock (i);

    Timer timer;
    profile().restart();
    profile().interactions___add_force___start();
    
    interaction.force ();
    
    profile().interactions___add_force___finish();
    profile().report();
    double time = timer.elapsed();

    double  rel_grad = 0.;

    for (int i=0;  i<model.atom_count();  ++i)
    {
        rel_grad += model.atom(i).kit().potential_force().length_2();
    }
    rel_grad = sqrt (rel_grad) / (3. * model.atom_count());

    Text report ("gradient ");
    report += rel_grad;
    report += Text("   time ") + Text(time, 2) + " sec   ";
    to_user(). info   (report);
    to_user(). status (report);



    //rel_grad = 0.;
    //interaction.add_long_force  ();
    //interaction.add_short_force ();
    //for (int i=0;  i<model.atom_count();  ++i)
    //{
    //    rel_grad += model.atom(i).kit().potential_force().length_2();
    //}
    //rel_grad = sqrt (rel_grad) / (3. * model.atom_count());

    //report = "gradient 2 steps ";
    //report += rel_grad;
    //report += Text("   ") + Text(time, 2) + " sec   ";
    //to_user(). info   (report);
}


void Module_energy::
strip ()
{
    Model & model = Project::singleton().current_model();
    model.kit().strip ();

/*    Cleaner cleaner(model);
    const Text zero_type = "0";

    for (int i=0;  i<model.atom_count();  ++i)
    {
        Atom & atom = model.atom (i);

        if (atom.kit().has_mm_type() 
            && zero_type == atom.kit().mm_type_c_str())
            cleaner.add (atom);
    }

    if (cleaner.is_empty())
        to_user().info ("There is nothing to strip.");//*/
}

void Module_energy::
assign_force_field ()
{
    Text         ff;
    Array <Text> ff_list;

    Fine_type::force_field_list (ff_list);

    if (to_user().choose ("Assign Force Field types for atoms.", ff_list, ff))
        Project::singleton().current_model().kit().set_force_field (ff);
}

void Module_energy::
force_field ()
{
    Panel_force_field::singleton()->popup ();
}

void Module_energy::
performance ()
{
    Panel_performance::singleton()->popup ();
}

}//MM
