#include "M_DynaMix_simulator.h"

#include "Arbitrary_bond_group.h"
#include "Arbitrary_molecular_group.h"
#include "Molecular_detector.h"
#include "M_DynaMix_mol_file.h"
#include "Model_kit.h"
#include "Model_impl.h"
//#include "M_DynaMix_task.h"// fix -
#include "File_text_output.h"
#include "Atom.h"
#include "Atom_kit.h"
#include "Bond.h"
#include "Molecule.h"
#include "Path.h"
#include "User.h"
#include "Project.h"
#include "Project_kit.h"
#include "Model_proxy.h"//fix remove
#include "Flaw.h"//fix

#include "Collect_potential_from_mDynaMix.h"
#include "Collect_temperature_from_mDynaMix.h"
#include "Collect_pressure_from_mDynaMix.h"
#include "Collect_density_from_mDynaMix.h"
#include "M_DynaMix_pipe_process.h"
//#include "M_DynaMix_QProcess.h"
#include "M_DynaMix_Q4Process.h"
//#include "Form_M_DynaMix_options.h"
#include "Simulator_factory.h"
#include "Temperature_unit.h"
#include "System_of_Units.h"
#include "Create.h"

#include "Panel_MDynaMix.h"//fix

#include <qapplication.h>//fix remove (to event)
#include <qeventloop.h>

//#include <process.h>
#include <stdio.h>
#include <map>
//#include <iostream>
//#include <strstream>
#include <sstream>
#include <fstream>

#ifdef WIN32
#pragma warning(disable : 4700)
#endif

namespace MM
{
M_DynaMix_simulator M_DynaMix_simulator::prototype_ (it_is_a_prototype);

M_DynaMix_simulator::M_DynaMix_simulator (Prototype &)
:
    m_dynamix_input_(Model::none())
{
    Simulator_factory::accept ("MDynaMix", this);
}

M_DynaMix_simulator::
M_DynaMix_simulator ()
:
    //Simulator_frame (model),
    m_dynamix_input_(Project::singleton().kit().protocol().model())//, //fix

    //run_M_DynaMix_  (model, m_dynamix_input_)//,
    //m_DynaMix_process_(new M_DynaMix_pipe_process (interface_for (&m_DynaMix_process_())))
    //m_DynaMix_process_(new M_DynaMix_QProcess (interface_for (&m_DynaMix_process_())))
{
    Model & model = Project::singleton().kit().protocol().model(); //fix to Project::singleton().curent_model()

    potential_monitor_ = new Collect_potential_from_mDynaMix (model);
    after_each_step (potential_monitor_);
    monitor_.push_back (*potential_monitor_);

    temperature_monitor_ = new Collect_temperature_from_mDynaMix (model);
    after_each_step (temperature_monitor_);
    monitor_.push_back (*temperature_monitor_);

    pressure_monitor_ = new Collect_pressure_from_mDynaMix (model);
    after_each_step (pressure_monitor_);
    monitor_.push_back (*pressure_monitor_);

    density_monitor_ = new Collect_density_from_mDynaMix (model);
    after_each_step (density_monitor_);
    monitor_.push_back (*density_monitor_);
}

void M_DynaMix_simulator::
set_model (Model & model)            //fix !!!!! model model model
{
    Simulator_frame::set_model (model);
    m_dynamix_input_.set_model (model);
    //run_M_DynaMix_  .set_model (model);
}

void M_DynaMix_simulator::
tune  ()
{
    model().kit().force_field().start_work (true);
    //Arbitrary_atom_group ordered_atoms;
    Arbitrary_atom_group & ordered_atoms = model().kit().ordered_atoms();

    int n = model().bond_count();
    clear                   (&ordered_atoms);
    detect_molecules        ();
    n =  model().bond_count ();
    write_type_of_molecules (&ordered_atoms);
    n =  model().bond_count ();
    write_coords            (ordered_atoms);
    n =  model().bond_count ();
    write_input             ();
    n = model().bond_count();

}

void M_DynaMix_simulator::
handle_start ()
{
    if  (model_.kit().force_field().was_problems ())
        throw Text ("Force Field problem");

    clear_monitors   ();
    remove_STOP_file ();

    Arbitrary_atom_group & ordered_atoms = model().kit().ordered_atoms();
    if (!m_DynaMix_process_.was_initialized())
    {
//#ifdef MM_WIN32 //fix to option
//        M_DynaMix_pipe_process *new_process =
//            new M_DynaMix_pipe_process (interface_for (new_process));
//
//        //M_DynaMix_QProcess *new_process =
//        //    new M_DynaMix_QProcess (interface_for (new_process));
//#endif
//#ifdef MM_UNIX
//        M_DynaMix_QProcess *new_process = 
//            new M_DynaMix_QProcess (interface_for (new_process));
//#endif
        M_DynaMix_Q4Process *new_process = 
            new M_DynaMix_Q4Process (interface_for (new_process));

        m_DynaMix_process_.adopt (new_process);
    }

    m_DynaMix_process_().start (&ordered_atoms);

    //start_mDynaMix          (&ordered_atoms);
    //run_M_DynaMix_.execute ();
}

void M_DynaMix_simulator::
stop ()
{
    using namespace std;
    ofstream stop_file ((Path::MDynaMix() + "MD_STOP").c_str());

    if (stop_file.fail())
        to_user(). error ("Can not open MD_STOP file.");

    to_user(). status ("Stop");
}

void M_DynaMix_simulator::
edit_options ()
{
    //Form_M_DynaMix_options::pop_up();
    //update ();
}

// protected /////////////////////////////////
/*
//fix remuve 
// extention of Simulator_frame::create_monitor 
MD_monitor * M_DynaMix_simulator::
create_monitor (Text const & monitor)
{
    if (monitor == "Collect_potential_from_mDynaMix")
        return new Collect_potential_from_mDynaMix (model());  

    else if (monitor == "Collect_temperature_from_mDynaMix")
        return new Collect_temperature_from_mDynaMix (model());
    
    else if (monitor == "Collect_pressure_from_mDynaMix")
        return new Collect_pressure_from_mDynaMix (model());
    
    else if (monitor == "Collect_density_from_mDynaMix")
        return new Collect_density_from_mDynaMix (model());
    
    return Simulator_frame::create_monitor (monitor);
}//*/

void M_DynaMix_simulator::
clear (Arbitrary_atom_group *ordered_atoms)
{
    ordered_atoms->erase ();
    m_dynamix_input_.erase_molecular_types ();

    #ifdef MM_WIN32
    Text command = Text("del /Q ") + Path::MDynaMix_database() + "~*";
    #endif
    #ifdef MM_UNIX
    Text command = Text("rm -f ") + Path::MDynaMix_database() + "~*";
    #endif

    int result = system (command.c_str());
    if (result != 0)
        to_user().error 
            (Text("Can not execute: ") + command + ". ErrNo = " + result);

    #ifdef MM_WIN32
    command = Text("del /Q " + Path::MDynaMix() + "MDynaMix.input");
    #endif
    #ifdef MM_UNIX
    command = Text("rm -f " + Path::MDynaMix() + "MDynaMix.input");
    #endif

    result = system (command.c_str());
    if (result != 0)
        to_user().error 
            (Text("Can not execute: ") + command + ". ErrNo = " + result);

    /*#ifdef MM_WIN32
    command = Text("del /Q md.output*");
    #endif
    #ifdef MM_UNIX
    command = Text("rm -f md.output*");
    #endif

    result = system (command.c_str());
    if (result != 0)
        to_user().error 
            (Text("Can not execute: ") + command + ". ErrNo = " + result);//*/
}

void M_DynaMix_simulator::
remove_STOP_file ()
{
    #ifdef MM_WIN32
    Text command = Text("del /Q " + Path::MDynaMix() + "MD_STOP");
    #endif
    #ifdef MM_UNIX
    Text command = Text("rm -f " + Path::MDynaMix() + "MD_STOP");
    #endif

    int result = system (command.c_str());
    if (result != 0)
        to_user().error 
            (Text("Can not execute: ") + command + ". ErrNo = " + result);
}

void M_DynaMix_simulator::
detect_molecules ()
{
    Molecular_detector detector (model());
    detector.execute ();
}

void M_DynaMix_simulator::
write_type_of_molecules (Arbitrary_atom_group * ordered_atoms)
{
    using namespace std;
    int i;
    map <Molecule*, Arbitrary_bond_group>   bonds;
    map <Text, Arbitrary_molecular_group>   molecular_type;

    // associate bonds with molecules
    for (i=0;  i<model().bond_count();  ++i)
    {
        Bond & current_bond = model().bond (i);
        Molecule *mol = &current_bond.atom(0).kit().in_molecule();
        bonds [mol]. add (current_bond);
    }

    // associate molecular formulas with groups of molecules
    for (i=0;  i<model().molecule_count();  ++i)
        molecular_type[model().molecule (i).molecular_formula()].
            add (model().molecule (i));

    // make a file per molecular type
    for (map <Text, Arbitrary_molecular_group>::iterator 
         it=molecular_type.begin();  it!=molecular_type.end();  ++it)
    {
        Text formula = it->first;
        Text type_name;
        if (formula == "H2O1" || formula == "H2O")  //fix take from moldb
            type_name = "H2O";
        else
        {
            type_name  = "~";
            type_name += formula;
        }

        Molecule   & first_molecule = it->second.molecule(0);
        Bond_group & first_bonds    = bonds [&first_molecule];

        setup_molecular_type 
            (type_name, formula, first_molecule, first_bonds, ordered_atoms);

        for (i=1;  i<it->second.molecule_count();  ++i)
        {
            Molecule   & current_molecule = it->second.molecule(i);
            Bond_group & current_bonds    = bonds [&current_molecule];
            
            if (has_the_same_type (first_molecule, current_molecule)) //fix for isomers
            {
                m_dynamix_input_.
                    increment_molecular_type (type_name);

                for (int j=0;  j<current_molecule.atom_count();  ++j)
                    ordered_atoms->add (current_molecule.atom(j));
            }
            else
                setup_molecular_type (type_name + "-" + i, 
                                      formula,
                                      current_molecule, 
                                      current_bonds,
                                      ordered_atoms);
        }
    }//*/
}

void M_DynaMix_simulator::
write_coords (Atom_group const & ordered_atoms)
{
    int i;

    //fix remove
/*
    std::ofstream out 
        ((m_dynamix_input_.main_file_name() + ".inp").c_str());

    out << ordered_atoms.atom_count() << std::endl;
    out << std::endl;

    for (i=0;  i<ordered_atoms.atom_count();  ++i)
        out 
            << ordered_atoms.atom(i).element().c_str() << "  \t"
            << ordered_atoms.atom(i).x() << "  \t"
            << ordered_atoms.atom(i).y() << "  \t"
            << ordered_atoms.atom(i).z() << std::endl;//*/

    //==========================================================
    Text filename;
    filename += Path::MDynaMix();
    filename += m_dynamix_input_.main_file_name();
    filename += ".start";
    std::ofstream out5 (filename.c_str());
    //std::ofstream out5 ("MDynaMix.start");

    out5 << ordered_atoms.atom_count() << std::endl;

    if (model().kit().boundary_conditions().type_name() == "Box")
    {
        out5 << "BOX: " 
             << model().kit().boundary_conditions().box().a_mod_rectangle() << " "
             << model().kit().boundary_conditions().box().b_mod_rectangle() << " "
             << model().kit().boundary_conditions().box().c_mod_rectangle() << " "
             << std::endl;
    }
    else
        out5 << std::endl;

    for (i=0;  i<ordered_atoms.atom_count();  ++i)
        out5
            << ordered_atoms.atom(i).element().c_str() << "  \t"
            << ordered_atoms.atom(i).x() << "  \t"
            << ordered_atoms.atom(i).y() << "  \t"
            << ordered_atoms.atom(i).z() << std::endl;
}

void M_DynaMix_simulator::
setup_molecular_type (Text const       & type_name,
                      Text const       & formula,
                      Atom_group       & atoms,
                      Bond_group const & bonds, 
                      Arbitrary_atom_group *ordered_atoms)
{
    Text mmol_file = model().kit().MDynaMix_file (formula);

    if (mmol_file.is_not_empty())
    {
        using namespace std;
        ifstream in  (mmol_file.c_str());
        ofstream out ((Path::MDynaMix_database() 
            + type_name + ".mmol").c_str());
        string line;
        while (getline (in, line))   //fix fcopy
            out << line << endl;

        M_DynaMix_molecular_type new_type (type_name);
        m_dynamix_input_.add_molecular_type (new_type);   
    }
    else if (type_name == "H2O")
    {
        M_DynaMix_molecular_type new_type (type_name);
        new_type.intramolecular_potential_type_ = 2;
        m_dynamix_input_.add_molecular_type (new_type);
    }
    else
    {
        Text file_name = Path::MDynaMix_database() + 
            type_name + ".mmol";

        //Model_impl model;
        //M_DynaMix_mol_file tmp (model);
        M_DynaMix_mol_file tmp (model());

        //model().m_dynamix_mol_file_
        Text old_name = tmp.name();
        tmp.set_name (file_name);//*/

        //model().m_dynamix_mol_file_
        tmp
            .save (atoms, bonds);
        tmp.set_name (old_name);

        M_DynaMix_molecular_type new_type (type_name);
        m_dynamix_input_.add_molecular_type (new_type);
    }

    for (int j=0;  j<atoms.atom_count();  ++j)
        ordered_atoms->add (atoms.atom(j));
}

bool M_DynaMix_simulator::
has_the_same_type (Molecule & first_mol, Molecule & second_mol)
{
    CONTRACT CHECK ("Have the same size", 
        first_mol.atom_count() == second_mol.atom_count());

    int i;

    for (i=0;  i<first_mol.atom_count();  ++i)
    {
        first_mol. atom (i).kit().set_number (i);
        second_mol.atom (i).kit().set_number (i);
    }

    for (i=0;  i<first_mol.atom_count();  ++i)
    {
        const Atom & atom_1 = first_mol. atom (i);
        const Atom & atom_2 = second_mol.atom (i);

        if (atom_1.element()     != atom_2.element() ||
            atom_1.bound_count() != atom_2.bound_count())
            return false;

        for (int j=0;  j<atom_1.bound_count();  ++j)
            if (atom_1.bound(j).kit().number() != 
                atom_2.bound(j).kit().number())
            return false;
    }

    #ifndef NDEBUG                                  
    for (i=0;  i<first_mol.atom_count();  ++i)
    {
        first_mol. atom (i).kit().set_number (-1);
        second_mol.atom (i).kit().set_number (-1);
    }
    #endif

    return true;
}

void M_DynaMix_simulator::
write_input ()
{
    ////File_text_output file  (Path::bin() + "md.input");
    ////m_dynamix_input_.prepare_input (file);
    //File_text_output file4  (Path::MDynaMix_database() + "md4.input");
    //m_dynamix_input_.prepare_input (file4);

    Text filename;
    filename += Path::MDynaMix();
    filename += m_dynamix_input_.main_file_name();
    filename += ".input";
    //File_text_output file5 (filename);
    //m_dynamix_input_.prepare_input_5 (file5);
    Panel_MDynaMix::singleton()->prepare_input (filename, m_dynamix_input_);
}

/*void M_DynaMix_simulator::
start_mDynaMix (Atom_group * ordered_atoms)
{
    to_user(). status ("Launch M.DynaMix.");

    ////////////////////////////////////////////////
    //to_user().info(fork());

    // pipe
    #ifdef MM_WIN32
    FILE * mDynaMix = _popen ("!md.exe < md.input", "rt");
    #endif
    #ifdef MM_UNIX
//    FILE * mDynaMix = popen ("./md < md.input", "rt");
    FILE * mDynaMix = popen ("md 2>&1", "rt");
    #endif
    if (mDynaMix == NULL)
    {
    	Text err;
    	switch (errno)
    	{
        	case EINVAL: err = "EINVAL";
        	case EAGAIN: err = "EAGAIN";
        	case ENOMEM: err = "ENOMEM";
    	}
        to_user().error (Text("Could not start m.DynaMix. ErrNo = ") + err);
        //to_user().error ("Could not start m.DynaMix.");
        return;
    }

    try
    {
        read_mDynaMix_output (mDynaMix, ordered_atoms);
    }
    catch(...)
    {
        to_user().error ("m.DynaMix problem.");
    }
    
    #ifdef MM_WIN32
    _pclose (mDynaMix);//fix to Pipe_ptr
    #endif
    #ifdef MM_UNIX
    pclose (mDynaMix);//fix to Pipe_ptr
    #endif
}

void M_DynaMix_simulator::
read_mDynaMix_output (FILE * output, Atom_group * ordered_atoms)
{
    static bool reinter_flag = false;//fix to lock
    if (reinter_flag)
    {
        //log() << "# reinter #";
        return;
    }
    reinter_flag = true;

    using namespace std;
    const int   bufsize = 1024;
    char        buffer [bufsize];
    bool        mDynaMix_success = false;
    bool        has_warnings     = false;
    bool        read_coords      = false;
    ofstream    out ("md.output");
    int         current_atom_number =0; //fix
    Text        report;

    while (!feof (output))
    {
        if (fgets (buffer, bufsize, output) != 0)
        {
            string          line (buffer);
            stringstream    in   (line);

            if (line.find("main MD loop finished") != string::npos)
                mDynaMix_success = true;

            string prefix;
            in >> prefix;

            if (prefix == "@mm")
            {
                if (line.find ("coord.start") != string::npos)
                {
                    read_coords = true;
                    current_atom_number = 0;
                    continue;
                }

                if (line.find ("coord.end") != string::npos)
                {
                    read_coords = false;
                    continue;
                }

                if (read_coords)
                {
                    double x, y, z;
                    in >> x >> y >> z;
            
                    if (in.fail())
                    {
                        stop();

                        to_user (). error (Text () + 
                            "Can not read coords from '" + buffer + "'.");
                        break;
                    }
        
                    Atom &current_atom = 
                        ordered_atoms->atom (current_atom_number);
                    current_atom.set_x (x);
                    current_atom.set_y (y);
                    current_atom.set_z (z);
                    //fix to model().set_coords;
        
                    ++current_atom_number;
                    continue;
                }

                string second_word;
                in >> second_word;
                if (second_word != "E")
                {
                        //qApp->processEvents (100);//fix
                        //qApp->processOneEvent ();
                        QApplication::eventLoop ()->
                            processEvents (QEventLoop::ExcludeSocketNotifiers);
                        continue;

                    //stop();
                    //to_user (). error 
                    //    (Text("Unexpected string: ") + buffer + "'.");
                    //break;
                }

                Text text;
                double value;
                int number;
                
                in >> number;   text += "step=";                text += number;

                in >> value;    text += "   E.pot=";        text += value;
                potential_monitor_->set_current_value (value);
                
                in >> value;    text += "   E.bonded=";   text += value;
                in >> value;    text += "   E.el.st=";      text += value;
                in >> value;    text += "   E.full=";       text += value;

                in >> value;    text += "   T=";            text += value;
                temperature_monitor_->set_current_value (value);
                
                in >> value;    text += "   P=";            text += value;
                pressure_monitor_->set_current_value (value);
                
                in >> value;    text += "   d=";            text += value;
                density_monitor_->set_current_value (value);

                if (in.fail())
                {
                    stop();
                    to_user (). error (Text () + 
                        "Can not read E from: '" + buffer + "'.");
                    break;
                }

                to_user().status (text);
                step_finished_.publish();
                //qApp->processEvents (1);
                report = text;

                //out << buffer;//fix remove
            }
            else
            {
                if (line.find ("!!!") != string::npos)
                {
                    to_user().status (buffer);
                    has_warnings = true;
                }

                out << buffer;
                printf (buffer);
            }

        }
        //else
        //{
        //    //to_user().error ("Could not read m.DynaMix output.");
        //    continue;
        //}
    }

    if (has_warnings)
        report += "   Warnings!!!";

    if (mDynaMix_success)
        report += "   Success.";
    else
        report += "   Fail!!!";  

    to_user().status (report);

    reinter_flag = false;
}
//*/
///////////////////////////////////////


/*double M_DynaMix_simulator::   // picoseconds
step_length () const
{
    return m_dynamix_input_.time_step_ * 1e12;
}

void M_DynaMix_simulator::
set_step_length (double new_length)
{
    m_dynamix_input_.time_step_ =  new_length / 1e12;
}//*/

Duration_unit M_DynaMix_simulator::
duration () const
{
    return m_dynamix_input_.time_step();
}

void M_DynaMix_simulator::
set_duration (Duration_unit l)
{
    m_dynamix_input_.set_time_step (l);
}

int M_DynaMix_simulator::
steps () const
{
//    return m_dynamix_input_.total_long_steps_;
    return m_dynamix_input_.time_step().steps ();
}

void M_DynaMix_simulator::
set_steps (int steps)
{
//    m_dynamix_input_.total_long_steps_ = steps;

    //m_dynamix_input_.time_step_.set_steps (steps);
    Duration_unit s = m_dynamix_input_.time_step();
    s.set_steps (steps);
    m_dynamix_input_.set_time_step (s);
}

Temperature_unit M_DynaMix_simulator::
desired_temperature () const
{
    Temperature_unit result;
    result.set_K (m_dynamix_input_.temperature_);
    return result;
}

void M_DynaMix_simulator::
set_desired_temperature (Temperature_unit const& v)
{
    m_dynamix_input_.temperature_ = v.K();
}

//void M_DynaMix_simulator::
//set_starting_pressure (double v)
//{
//    m_dynamix_input_.pressure_ = v;
//}
void M_DynaMix_simulator::
set_desired_pressure (double p)
{
    Pressure_unit unit;
    System_of_Units::singleton().set_pressure (unit, p);
    m_dynamix_input_.pressure_ = unit.atm();
}
double M_DynaMix_simulator::
desired_pressure () const 
{
    Pressure_unit result;
    result.set_atm (m_dynamix_input_.pressure_);
    return System_of_Units::singleton().pressure (result);
}

void M_DynaMix_simulator::
set_final_temperature (Temperature_unit const& v)
{
    if (m_dynamix_input_.temperature_ != v.K())
        FLAW("Not done yet.");
}

//void M_DynaMix_simulator::
//set_final_pressure (double v)
//{
//    if (m_dynamix_input_.pressure_ != v)
//        FLAW("Not done yet.");
//}

bool M_DynaMix_simulator::
has_thermostat () const
{
    return m_dynamix_input_.constant_temperature_;
}

bool M_DynaMix_simulator::
has_barostat () const
{
    return m_dynamix_input_.constant_pressure_;
}

void M_DynaMix_simulator::
set_thermostat (bool on)
{
    m_dynamix_input_.constant_temperature_ = on;
}

void M_DynaMix_simulator::
set_barostat (bool on)
{
    m_dynamix_input_.constant_pressure_ = on;
}

void M_DynaMix_simulator::
add_monitor (char const * name)
{
    Monitor * new_monitor = Monitor::create (model (), name, "Simulator");
    after_each_step (new_monitor);
    monitor_.push_back (*new_monitor);
}

void M_DynaMix_simulator::
after_each_step (Command * to_do)
{
    step_finished_.subscribe (to_do);
}

void M_DynaMix_simulator::
after_stop (Command * to_do)
{
    stop_.subscribe (to_do);
}

void M_DynaMix_simulator::
eliminate_after_each_step (Command & to_do)
{
    step_finished_.subscribe (&to_do, false);
}

void M_DynaMix_simulator::
eliminate_after_stop (Command & to_do)
{
    stop_.subscribe (&to_do, false);
}

void M_DynaMix_simulator::
update ()
{
    //if (Form_M_DynaMix_options::exist())
    //    Form_M_DynaMix_options::singleton()->update_for (m_dynamix_input_);
}

}//MM







