#include "Hyperchem_file.h"

#include "User.h"
#include "Atom.h"
#include "Atom_kit.h"
#include "Bond.h"
#include "Residue.h"
#include "Hyperchem_attributes.h"
#include "Hyperchem_atoms.h"
#include "Model.h"
#include "Model_kit.h"
//#include "Molecular_iterator_impl.h"
#include "Molecule.h"
#include "Text_output.h"
#include "File_text_output.h"
#include "Molecular_detector.h"
#include "Create.h"
#include "MOT.h"

#include "Checked_cast.h"

#include <string>
#include <fstream>
#include <sstream>
#include <algorithm>

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


Hyperchem_file::
Hyperchem_file (Prototype const &)
:
    Model_file (Prototype())
{
    add_extension ("hin");
}

Hyperchem_file::
Hyperchem_file (Model & model)
:
    Model_file (model)
{
    add_extension ("hin");
}

Model_file * Hyperchem_file::
clone (Model & model) const
{
    Hyperchem_file *result = new Hyperchem_file (model);
    
    //result->extension_ = extension_;   //fix Array += should work
    //for (int i=0;  i<extension_.size();  ++i)
    //    result->extension_.push_back (extension_[i]);
    
    return result;
}

void Hyperchem_file::
add (Text const & file_name)
{
    using namespace std;

    string      line;
    Text        message;
    int         broken_line = -1;
    int         line_counter = 0;
    Molecule    *current_molecule = 0;
    bool        first_comment_line = true;

    // Read without bonds
    {
        //setlocale( LC_ALL, ".ACP" );
        ifstream    file (file_name.c_str());

        //wchar_t fn[] = L"D:/USER/Niki/2009/Work/Русский/MeCN.hin";
        ////wchar_t fn[] = L"D:/USER/Niki/2009/Work/Русский.hin";
        //ifstream    file (fn);

        //setlocale( LC_ALL, ".ACP" );
        //char fn[] = "D:/USER/Niki/2009/Work/Русский.hin";
        //ifstream    file (fn);

        if (file.fail())
        {
            to_user(). error (Text("Cannot open file " + file_name));
            return;   //fit to throw ?
        }

        while (getline (file, line))
        {
            ++line_counter;

            istringstream   in (line);
            string          first_word;
        
            in >> first_word;

            if (in.fail())
            {
                // empty string

                //fix error ?
                //broken_line = line_counter;
                //break;
            }

            else if (first_word[0] == ';')
            {
                // comment
                //fix 

                if (first_comment_line)
                    to_user().status (string(line.begin()+1, line.end()).c_str());
            }

            else if (first_word == "atom")
            {
                // atom
                int     number;
                string  name, element, type, flags;
                double  charge, x, y, z;

                in >> number >> name >> element >> type >> flags >> charge 
                   >> x >> y >> z;

                if (number <= 0)
                {
                    message = "Atom number should be more then 0.";
                    broken_line = line_counter;     break;
                }
                else if (name.size() > 4)
                {
                    message = "Atom name should have up to four characters.";
                    broken_line = line_counter;     break;
                }
                else if (count(name.begin(), name.end(), '\'') != 0 ||
                         count(name.begin(), name.end(), '"') != 0)
                {
                    message = "Atom name cannot contain quotes.";
                    broken_line = line_counter;     break;
                }
                else if (!Element::is (element.c_str ()))
                {
                    message = "Don't chemical element symbol.";
                    broken_line = line_counter;     break;
                }
                else if (type.size() > 4)
                {
                    message = "Atom type should have up to four characters.";
                    broken_line = line_counter;     break;
                }
                else if ( (count(flags.begin(), flags.end(), 'h') +
                           count(flags.begin(), flags.end(), 'i') +
                           count(flags.begin(), flags.end(), 'x') +
                           count(flags.begin(), flags.end(), 's') +
                           count(flags.begin(), flags.end(), '-')) != (int)flags.size())
                {
                    message = "Atom flags cannot contain characters other then 'hixs-'.";
                    broken_line = line_counter;     break;
                }
                else if (current_molecule == 0)
                {
                    message = "Current molecule should exist.";
                    broken_line = line_counter;     break;
                }

                //Atom *current_atom = 
                //    create_atom (Element (element.c_str ()));
                Atom & current_atom = new_atom (Element (element.c_str ()));

                current_atom.set_charge (charge);
                current_atom.set_x (x);
                current_atom.set_y (y);
                current_atom.set_z (z);

                Hyperchem_attributes *hin_attributes 
                    = new Hyperchem_attributes (current_atom);
                hin_attributes->set_number (number);
                hin_attributes->set_name   (name. c_str ());
                hin_attributes->set_type   (type. c_str ());
                hin_attributes->set_flags  (flags.c_str ());
                current_atom.kit().add_hyperchem_attributes (hin_attributes);

                current_atom.kit().set_mm_type (type. c_str ());  //fix

                //model(). add_atom (current_atom);
                current_molecule->add_atom (current_atom);

                if (in.fail())
                {
                    broken_line = line_counter;
                    break;
                }
            }

            else if (first_word == "mol")
            {
                // begin molecule
                current_molecule = &new_molecule ();
                //model(). add_molecule (current_molecule);
            }

            else if (first_word == "endmol")
            {
                // end molecule
                current_molecule = 0;
            }

            else if (first_word == "res")
            {
                // 
            }

            else if (first_word == "endres")
            {
                // 
            }

            else if (first_word == "forcefield")
            {
                string ff;  in >> ff;

                if (ff == "amber94")
                    model().kit().set_force_field ("AMBER94");
                
                else if (ff == "amber96")
                    model().kit().set_force_field ("AMBER94");// 99->96->94 !!!
                
                else if (ff == "amber99")
                    model().kit().set_force_field ("AMBER99");
                
                else if (ff == "bio+")
                    model().kit().set_force_field ("CHARMM19");
                
                else if (ff == "charmm22")
                    model().kit().set_force_field ("CHARMM22");
                
                else if (ff == "charmm27")
                    model().kit().set_force_field ("CHARMM27");
                
                else if (ff == "opls")
                    model().kit().set_force_field ("OPLS");
            }

            else if (first_word == "sys")
            {
                // 
            }

            else if (first_word == "view")
            {
                // 
            }

            else if (first_word == "box")
            {
                // 
            }

            else if (first_word == "vel")
            {
                // 
            }

            else if (first_word == "mass")
            {
                // 
            }

            else if (first_word == "basisset")
            {
                // 
            }

            else if (first_word == "selection")
            {
                // 
            }

            else if (first_word == "selectrestraint")
            {
                // 
            }

            else if (first_word == "selectatom")
            {
                // 
            }

            else if (first_word == "endselection")
            {
                // 
            }

            else if (first_word == "dynamics")
            {
                // 
            }

            else 
            {
                // unexpected first word
                //message = "Unexpected first word.";
                //broken_line = line_counter;
                //break;
            }

        }

        if (broken_line != -1)
        {
            to_user().error (message + "\n" +
                "File " + file_name + " is broken at line " + broken_line + ".\n'"
                + line.c_str() + "'");
            return;
        }
    }

    // Read bonds
    {
        ifstream    file (file_name.c_str());

        if (file.fail())
        {
            to_user(). error 
                (Text("Cannot open file " + file_name + " second time."));
            return;
        }

        line_counter = 0;
        current_molecule = 0;
        int     molecular_number   = 0, 
                global_atom_number = 0, 
                local_atom_number  = 0;
        bool    success = true;

        while (getline (file, line))
        {
            ++line_counter;

            istringstream   in (line);
            string          first_word;
        
            in >> first_word;

            if (first_word == "atom")
            {
                // atom
                int     number, cn;
                string  name, element, type, flags;
                double  charge, x, y, z;

                Atom *current_atom = &model().atom (global_atom_number);

                in >> number >> name >> element >> type >> flags >> charge 
                   >> x >> y >> z >> cn;

                if (cn < 0 || cn > 12)
                {
                    message = "The number of covalently bound atoms (cn)"
                              " should be from 0 to 12.";
                    broken_line = line_counter;     break;
                }
                else if ((local_atom_number+1) != 
                          current_atom->kit().hyperchem().number())
                {
                    message = "Internal error: atom numbers inconsistency.";
                    broken_line = line_counter;     break;
                }

                for (int i=0;  i<cn;  ++i)
                {
                    int  bound_atom_number;
                    char bond_type;
                    in >> bound_atom_number >> bond_type;

                    if (!(bond_type == 's' || bond_type == 'd' || 
                          bond_type == 't' || bond_type == 'a'))
                    {
                        message = "Bond type cannot be other then s, d, t or a.";
                        broken_line = line_counter;     success = false;    break;
                    }
                    else if (bound_atom_number <= 0 || 
                             bound_atom_number > current_molecule->atom_count())
                    {
                        message  = "Bound atom number "; 
                        message += bound_atom_number;
                        message += " is out of range 1-"; 
                        message += current_molecule->atom_count();
                        message += ".";
                        broken_line = line_counter;     success = false;    break;
                    }

                    if (local_atom_number+1 < bound_atom_number)
                    {
                        //Bond &current_bond = 
                            new_bond (*current_atom, 
                                         current_molecule->atom (bound_atom_number-1), 
                                         Order(bond_type));

                        //model(). add_bond (current_bond);
                    }

                    if (in.fail())
                    {
                        broken_line = line_counter;     success = false;    break;
                    }
                }
                if (!success) break;

                ++global_atom_number;
                ++local_atom_number;
            }

            else if (first_word == "mol")
            {
                // begin molecule
                current_molecule = &model().molecule (molecular_number);
            }

            else if (first_word == "endmol")
            {
                // end molecule
                current_molecule = 0;
                ++molecular_number;
                local_atom_number = 0;
            }
        }

        if (broken_line != -1)
        {
            to_user().error (message + "\n" +
                "File " + file_name + " is broken at line " + broken_line + ".\n'"
                + line.c_str() + "'");
            return;
        }
    }
//*/
    //to_user (). status (Text("bonds ") + model().bond_count());
    //fix check_integrity();
}

void Hyperchem_file::
check_file_name()
{
    std::string filename = name().c_str();
    if( filename.find( ".hin" ) == std::string::npos )
        throw "void Hyperchem_file::\n"
              "check_file_name();\n"
              "\n"
              "Wrong file name.\n"
              "Check file name extension.";
}

void Hyperchem_file::
save (bool to_comment)
{
    check_file_name();
    File_text_output out( name().c_str() );
    //fix Hyperchem_atoms set_hyperchem_attributes (model());

    Model_kit const & kit = model().kit();

    if (kit.has_comment_line())
    {
        out.text ("; ");
        out.line (kit.comment_line());
        out.line (";");
    }


    if (kit.has_force_field())
    {
        Text ff = kit.force_field().name();

        if (ff == "AMBER94" || ff == "AMBER96" || ff == "AMBER99")
            out.line ("forcefield amber94");

        else if (ff == "OPLS")
            out.line ("forcefield opls");

        else if (ff == "CHARMM19")
            out.line ("forcefield bio+");

        else if (ff == "CHARMM22" || ff == "CHARMM27")
            out.line ("forcefield charmm22");
    }
    else
        //out.line ("forcefield mm+");
        out.line ("forcefield amber94");

    out.line ("sys 0");
    //out.line (";view 40 0.1 100 100 1 0 0 0 1 0 0 0 1 0.08 -0.7 -40");

    if (to_comment)
        out.line ("; atom <at#> <name> <element> <type> <flags> "
                  "<charge> <x> <y> <z> <bonds> <nbor# nbor-bond>");

    Molecular_detector detector (model());
    detector.execute ();

    for (int i=0;  i<model().molecule_count();  ++i)
    {
        write_molecule (model().molecule(i), i+1, out);
    }

    /*int n=1;
    for( Molecular_iterator mol=create_molecular_iterator();
         mol.is_valid();
         mol.next(), ++n )
    {
        write_molecule( mol.current(), n, &out ); 
    }//*/

//    Janitor< Hyperchem_atoms > atoms( factory().make_hyperchem_atoms( model().atoms() ));
    /*Hyperchem_atoms atoms( model().atoms() );

    std::ofstream out( name().c_str() );

    out << "mol 1 -" << std::endl;

    for( int i=0;  i<atoms.size();  ++i )
    {
        MM_atom & current_atom = atoms[i];

        out << "atom "
            << current_atom.hyperchem().number()            << " "
            << current_atom.hyperchem().name().c_str()      << " "
            << current_atom.element().c_str()   << " "
            << current_atom.hyperchem().type().c_str()      << " "
            << current_atom.hyperchem().flags().c_str()     << " " 
//fix            << current_atom.charge()             << " "
            << 0              << " "
            << current_atom.x()                 << " "
            << current_atom.y()                 << " "
            << current_atom.z()                 << "   "
            << current_atom.bound_atoms().size()<< " ";  //number_of_bond

        const Array_of< MM_atom > & bound = current_atom.bound_atoms();

        //fix j
        
        for( int j=0;  j<bound.size();  ++j )
        {
            CHECK("Bound atom is a MM_atom", 
                dynamic_cast< const MM_atom * >( &bound[j] ) != 0);

            const MM_atom & current_bound = 
                static_cast<const MM_atom &>( bound[j] );

            out << current_bound.hyperchem().number() << " "
                << current_atom.hyperchem().bond_order(j).c_char() << " ";
        }
        out << std::endl;
    }

    out << "endmol 1" << std::endl;//*/

/*fix    check_file_name();

    int   atom_number;
    const Atoms & model_atoms = model().atoms();
    const Bonds & model_bonds = model().bonds();

    // create list of Hyperchem atoms
    Default_container_of_atoms atoms;

    for( model_atoms.first(),     atom_number=1;  
         model_atoms.is_valid();  
         model_atoms.next(),      ++atom_number  )
    {
        Hyperchem_atom * current_atom = 
            new Hyperchem_atom( model_atoms.current().element() );

        current_atom->set_id( model_atoms.current().id() );
        current_atom->set_number( atom_number );
        current_atom->set_name ( "-"  );
        current_atom->set_type ( "**" );
        current_atom->set_flags( "-"  );
        current_atom->set_charge( model_atoms.current().charge() );
        current_atom->set_x( model_atoms.current().x() );
        current_atom->set_y( model_atoms.current().y() );
        current_atom->set_z( model_atoms.current().z() );

        atoms.add_atom( current_atom );
    }

    for( model_bonds.first();  
         model_bonds.is_valid();  
         model_bonds.next() )
    {
        const Bond  & current_bond = model_bonds.current();
        const Atoms & atom_in_bond = current_bond.atoms();

        atom_in_bond.first();
        Hyperchem_atom & atom1 = static_cast< Hyperchem_atom & >( 
            atoms.atom( atom_in_bond.current().id() ));
        
        atom_in_bond.next();
        Hyperchem_atom & atom2 = static_cast< Hyperchem_atom & >( 
            atoms.atom( atom_in_bond.current().id() ));

        atom1.add_neighbor( atom2.number(), current_bond.order().c_char() );
        atom2.add_neighbor( atom1.number(), current_bond.order().c_char() ); 
    }

    // write file
    std::ofstream out( name().c_str() );

    out << "mol 1 -" << std::endl;

    for( atoms.first();  atoms.is_valid();  atoms.next() )
    {
        Hyperchem_atom & current_atom = 
            static_cast< Hyperchem_atom & >( atoms.current() );

        out << "atom "
            << current_atom.number()             << " "
            << current_atom.name()               << " "
            << current_atom.element().c_str()    << " "
            << current_atom.type()               << " "
            << current_atom.flags()              << " " 
            << current_atom.charge()             << " "
            << current_atom.x()                  << " "
            << current_atom.y()                  << " "
            << current_atom.z()                  << "   "
            << current_atom.neighbors_size()     << " ";  //number_of_bond

        for( int j=0;  j<current_atom.neighbors_size();  ++j )
            out << current_atom.neighbor(j).number() << " "
                << current_atom.neighbor(j).order()  << " ";
             
        out << std::endl;
    }

    out << "endmol 1" << std::endl;//*/
}

void Hyperchem_file::
write_molecule (const Molecule & molecule, 
                int              molecule_number, 
                Text_output &    out)
{
    int i, atom_n, res_n;
	int atom_count    = molecule.atom_count();
	int residue_count = molecule.residue_count();

    out.text ("mol ");
    out.line (molecule_number);

    //for(i=0;  i<molecule.atom_count();  ++i)
    //    molecule.atom(i).kit().hyperchem().set_number (i+1);

    //for(i=0;  i<molecule.atom_count();  ++i)
    //    write_atom (molecule.atom(i), i+1, out);

    // number atoms
    atom_n = 1;

    for (res_n=0;  res_n<molecule.residue_count();  ++res_n)
    {
        Residue const & current_res = molecule.residue (res_n);

        for (i=0;  i<current_res.atom_count();  ++i)
        {
            Atom const& current_atom = current_res.atom(i);

            if (&current_atom.kit().in_molecule() != &molecule)
                continue;

            current_atom.kit().hyperchem().set_number (atom_n);
            ++atom_n;
        }
    }

    for(i=0;  i<molecule.atom_count();  ++i)
    {
        Atom const& current_atom = molecule.atom(i);

        if (&current_atom.kit().in_residue() == &Residue::none())
        {
            current_atom.kit().hyperchem().set_number (atom_n);
            ++atom_n;
        }
    }

    // write atoms
    atom_n = 1;

//	int residue_count = molecule.residue_count();
    for (res_n=0;  res_n<residue_count;  ++res_n)
    {
        Residue const & current_res = molecule.residue (res_n);

        out.text ("res ");
        out.text (res_n + 1);
        if (current_res.has_alias() && current_res.alias().length() <= 4)
        {
            out.text (" ");
            out.text (current_res.alias());
        }
        else
            out.text (" -");
        out.text (" 0 - -");

        out.line ("");

        for (i=0;  i<current_res.atom_count();  ++i)
        {
            Atom const& current_atom = current_res.atom(i);

            if (&current_atom.kit().in_molecule() != &molecule)
                continue;

            write_atom (current_atom, atom_n, out);
            ++atom_n;
        }

        out.text ("endres ");
        out.line (res_n + 1);
    }

	//int atom_count = molecule.atom_count();
    for(i=0;  i<atom_count;  ++i)
    {
        Atom const& current_atom = molecule.atom(i);

        if (&current_atom.kit().in_residue() == &Residue::none())
        {
            write_atom (current_atom, atom_n, out);
            ++atom_n;
        }
    }

    out.text ("endmol ");
    out.line (molecule_number);

    ENSURE ("Atoms in molecule are atoms in residues plus free atoms.", 
        atom_n == molecule.atom_count() + 1);
}

void Hyperchem_file::
write_atom(const Atom & atom, int atom_number, Text_output & out) //fix to ofstream
{
    out.text ("atom ");

    // at#
    out.text (atom_number);                                 out.text (" ");
    //fix CHECK ("The same numbers.", );
    
    // atom-name
    if (atom.kit().has_pdb_attributes() && atom.kit().pdb().name() != "")
    {
        out.text (atom.kit().pdb().name().c_str ());        out.text (" ");
    }
    else
    {
        char const* pdb_name;

        if (atom.kit().has_fine_type() && (pdb_name = 
            Fine_type::to (atom.kit().fine_type_name(), "PDB", 0)) != 0)
        {
            out.text (pdb_name);                            
        }
        else
            out.text (atom.kit().hyperchem().name().c_str ()); //fix remove hyperchem().name_?

        out.text (" ");
    }

    out.text (atom.element().c_str());                      out.text (" ");

    //int     mm_type = atom.kit().mm_type();
    Text    mm_type_name (atom.kit().mm_type_c_str());

    if (atom.kit().has_mm_type())
    {
        out.text (atom.kit(). mm_type_c_str ());            out.text (" ");
    }
    else
    {
        out.text (atom.kit().hyperchem().type().c_str ());  out.text (" ");
    }

    /*if (mm_type != Micro_object_type::undefined_element &&
        mm_type != Micro_object_type::undefined_type &&
        mm_type != Micro_object_type::any_type)
    {
        out.text (atom.kit(). mm_type_c_str ());        out.text (" ");
    }
    else
        out.text (atom.kit().hyperchem().type().c_str ());  out.text (" ");//*/
    
    out.text (atom.kit().hyperchem().flags().c_str());  out.text (" ");
    out.text (atom.charge());                           out.text (" ");
    out.text (atom.x());                                out.text (" ");
    out.text (atom.y());                                out.text (" ");
    out.text (atom.z());                                out.text (" ");
    //out.text (atom.bound_atoms().size());               out.text (" ");

    //const Array_of <Atom> & bound = atom.bound_atoms ();

    int i, bound_count = 0;
    for (i=0;  i<atom.bound_count();  ++i)
    {
        Order order = atom.bond(i).order(); //fix
        if (order != Order::Dummy)
            ++bound_count;
    }
    out.text (bound_count);

    //for (int i=0;  i<bound.size();  ++i)
    for (i=0;  i<atom.bound_count();  ++i)
    {
        Order order = atom.bond(i).order(); //fix
        if (order == Order::Dummy)
            continue;

        /*CHECK("Bound atom is a MM_atom", 
            dynamic_cast< const MM_atom * >( &bound[i] ) != 0);

        const MM_atom & current_bound = 
            static_cast< const MM_atom & >( bound[i] );//*/
        //const Atom & current_bound = 
        //    ref_cast <const Atom> (bound[i]); //fix

        //out.text (bound[i].kit().hyperchem().number()); 
        out.text (" ");
        out.text (atom.bound(i).kit().hyperchem().number()); //fix
        out.text (" ");

        //Order order = atom.kit().hyperchem().bond_order(i); //fix
        char type = 's';
        if (order == Order::Single || 
            order == Order::Double || 
            order == Order::Triple || 
            order == Order::Aromatic)
        {
            type = order.c_char();
        }
        else if (order == Order::No)
            FLAW ("No order of bond.");
        else if (order == Order::Dummy)
            continue;

        //out.text (atom.kit().hyperchem().bond_order(i).c_char()); //fix
        out.text (type); //fix
    }

    out.text ("\n");
}

/*
Molecular_iterator Hyperchem_file::
create_molecular_iterator()
{
    FIX;
    return Molecular_iterator_impl (Atom());
}//*/

}//MM


