// 
// This file is part of MolMeccano, a cross-platform C++ chemical library.
//
// Copyright (C) 2001-2009 Alexey Nikitin
//

#include "Model_impl.h"

#include "Project.h"
#include "Atom.h"
#include "Atom_kit.h"
#include "Residue.h"
#include "Residue_numerator.h"
#include "Molecular_numerator.h"
#include "Molecular_detector.h"
#include "Create.h"
#include "Bond.h"
#include "Bond_kit.h"
#include "ID.h"
#include "Atom_numerator.h"
#include "Command_template.h"
#include "Point_3D_impl.h"

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

namespace MM
{

bool Model_impl::
invariant() const
{
    if (clone_flag_)
        return true;

//fix    if ((Model*)this == &prototype <Model>())
//        return false;

    if ((Model*)this == &Model::none())
        return false;

    return true;
}

Model_impl::Model_impl()
: 
    kit_(*this), 
    clone_flag_(false)
{
    atom_prototype_.adopt (create <Atom> ());
    bond_prototype_.adopt (create <Bond> ());

//    Storable::set_model (*this);  //fix ???
}

Model_impl::
~Model_impl()
{
}

Model_impl::Model_impl (Prototype & p)
:
    kit_(p, *this),
    clone_flag_(false)
{
    atom_prototype_.adopt (create <Atom> ());
    bond_prototype_.adopt (create <Bond> ());
}

Model_impl::Model_impl (None &)
:
    kit_(it_is_a_prototype, *this), //fix it_is_a_prototype
    clone_flag_(false)
{
}

/*void MM_model::
destroy( Associated & item )
{
    MM_atom & atom = dynamic_cast < MM_atom & > ( item );
    atoms_.remove( atom );
}//*/

void Model_impl::
copy (int n, Expanded_atom_group * result)
{
    Model_impl *new_model = new Model_impl ();
    clone (new_model, n, result);
    merge_model (new_model);
}

Model * Model_impl::
clone () const                                 //fix
{
    Model_impl *new_model = new Model_impl ();
    clone (new_model, 1, 0);
    return  new_model;
}

void Model_impl::
clone (Model_impl *new_model, int n, Expanded_atom_group * result) const
{
    clone_flag_ = true;

    int old_atoms  = atoms_.size();
    int old_joints = joints_.size();

    Atom_numerator      atom_numerator      (*this);
    Residue_numerator   residue_numerator   (*this);
    Molecular_numerator molecular_numerator (*this);

    //new_model->kit_.add_view (kit_.clone());

    new_model->atom_prototype_.adopt ((Atom *)atom_prototype_().clone()); //fix Base::Atom
    new_model->atom_prototype_().kit().visual().show (false);
    new_model->bond_prototype_.adopt ((Bond *)bond_prototype_().clone()); 
    new_model->bond_prototype_().kit().visual().show (false);

    int i,j,k;

    for (k=0;  k<n;  ++k)
    {
        if (result == 0)
            for (i=0;  i<old_atoms;  ++i)
                new_model->add_atom ((Atom *)atoms_[i].clone()); //fix Base::Atom
        else
            for (i=0;  i<old_atoms;  ++i)
            {
                Atom * new_atom = (Atom *)atoms_[i].clone();
                new_model->add_atom (new_atom);
                result->add (*new_atom);
            }

        for (i=0;  i<bonds_.size();  ++i)
        {
            Bond const& current_bond = bonds_[i];
            int    n1    = current_bond.atom(0).kit().number();
            int    n2    = current_bond.atom(1).kit().number();
            Atom & atom1 = new_model->atoms_[k*old_atoms+n1];
            Atom & atom2 = new_model->atoms_[k*old_atoms+n2];
            Order  order = current_bond.order();

            //Bond * new_bond = prototype <Bond>(). clone (atom1, atom2, order);
            Bond * new_bond = new_model->bond_prototype_(). clone (atom1, atom2, order);
            new_model->add_bond (new_bond);

        new_bond->kit().visual().show (true);
        new_bond->kit().visual().set_style (Visual_style::ball_and_stick);
        new_bond->kit().visual().renew ();
        }

        //Array <Joint*>   new_joint;

        for (i=0;  i<old_joints;  ++i)
        {
            Joint const & current_joint = joints_[i];
            current_joint.set_local_id (i);

            int n1 = current_joint.stub_atom().        kit().number();
            int n2 = current_joint.terminal_atom().    kit().number();
            int n3 = current_joint.directional_atom(). kit().number();

            Atom & stub_atom        = new_model->atoms_[k*old_atoms+n1];
            Atom & terminal_atom    = new_model->atoms_[k*old_atoms+n2];
            Atom & directional_atom = new_model->atoms_[k*old_atoms+n3];

            Joint * new_joint =
                new Joint (stub_atom, terminal_atom, directional_atom);
            new_model->add_joint (new_joint);
            new_joint->set_local_id (k*old_joints+i);
        }

        Array <Residue*>   new_residue;
        int res_count = residues_.size();

        for (i=0;  i<res_count;  ++i)
        {
            Residue const & current_residue = residues_[i];
            //Residue *new_residue = prototype <Residue> ().clone (*this);
            Residue *new_residue = create <Residue> ();
            new_model->add_residue (new_residue);

            if (current_residue.has_name())
                new_residue->set_name (current_residue.name());

            if (current_residue.has_short_alias())
                new_residue->set_short_alias (current_residue.short_alias());

            if (current_residue.has_alias())
                new_residue->set_alias (current_residue.alias());

            if (current_residue.has_comment_line())
                new_residue->set_comment_line (current_residue.comment_line());

            for (j=0;  j<current_residue.atom_count();  ++j)
            {
                Atom const & current_atom = current_residue.atom (j);
                int          n = current_atom.kit().number();
                new_residue->add_atom (new_model->atom (k*old_atoms+n));
            }

            for (j=0;  j<current_residue.joint_count();  ++j)
            {
    //            Joint const & current_joint = joints_[j];
                Joint const & current_joint = current_residue.joint(j);

                new_residue->add_joint (new_model->
                    joint (k*old_joints+current_joint.local_id().to_int()));
            }
        }

        for (i=0;  i<molecules_.size();  ++i)
        {
            Molecule const& current_molecule = molecules_[i];
            Molecule *new_molecule = create <Molecule> ();
            new_model->add_molecule (new_molecule);

            for (j=0;  j<current_molecule.atom_count();  ++j)
            {
                Atom const & current_atom = current_molecule.atom (j);
                int         n = current_atom.kit().number();
                new_molecule->add_atom (new_model->atom (k*old_atoms+n));
            }

            new_molecule->reset_residues ();
            /*for (j=0;  j<current_molecule.residue_count();  ++j)
            {
                Residue const & current_residue = current_molecule.residue (j);
                new_molecule->add_residue (current_residue);
            }//*/
        }
    }
//    Array_of_own <Molecule>     molecules_;

//    reset_residues ();


    #ifndef NDEBUG //fix remove
    for (i=0;  i<new_model->atoms_.size();  ++i)
        new_model->atoms_[i].kit().set_number(-1);
    #endif

    clone_flag_ = false;

    //return  new_model;//fix content
}

void Model_impl::
reset_molecules ()
{
    detect_molecules ();//fix
}

void Model_impl::
reset_residues ()
{
    FIX;
}


void Model_impl::
erase ()
{
    //delete singleton <Model_manager> ().orphan (*this);
    delete Project::singleton().orphan (*this);
}

void Model_impl::
orphan_all ()
{
    INVARIANT;

    atoms_.     orphan_all ();
    bonds_.     orphan_all ();
    residues_.  orphan_all ();
    joints_.    orphan_all ();
    molecules_. orphan_all ();
}

void Model_impl::
merge_model (Model * adoptee)                  //fix develop
{
    INVARIANT;

    //fix Graphics

    //atoms_.adopt (((Model_impl*)adoptee)->atoms_); //fix cast
    //bonds_.adopt (((Model_impl*)adoptee)->bonds_);

    int i, atom_count = adoptee->atom_count();
    for (i=0;  i<atom_count;  ++i)
        add_atom (&adoptee->atom (i));

    int bond_count = adoptee->bond_count();
    for (i=0;  i<bond_count;  ++i)
        bonds_.adopt (&adoptee->bond (i));

    int residue_count = adoptee->residue_count();
    for (i=0;  i<residue_count;  ++i)
        residues_.adopt (&adoptee->residue (i));

    int molecule_count = adoptee->molecule_count();
    for (i=0;  i<molecule_count;  ++i)
        molecules_.adopt (&adoptee->molecule (i));

    int joint_count = adoptee->joint_count();
    for (i=0;  i<joint_count;  ++i)
        joints_.adopt (&adoptee->joint (i));

    for (std::map <Text, Text>::iterator 
        it  = adoptee->kit().MDynaMix_file_.begin(); 
        it != adoptee->kit().MDynaMix_file_.end(); ++it)
    {
        kit().MDynaMix_file_[it->first] = it->second;
    }

    adoptee->orphan_all ();

    kit_.event().structure_changed();

    ENSURE ("No atoms in adoptee",      adoptee->atom_count     () == 0);
    ENSURE ("No bonds in adoptee",      adoptee->bond_count     () == 0);
    ENSURE ("No molecules in adoptee",  adoptee->molecule_count () == 0);
}

void Model_impl::
move (Vector_3D const & movement)
{
    INVARIANT;

    for (int i=0;  i<atom_count();  ++i)
        atom(i).move (movement);

    kit_.event().conformation_changed();
}

void Model_impl::
rotate (Vector_3D const & around, double teta)
{
    INVARIANT;

    for (int i=0;  i<atom_count();  ++i)
        atom(i).rotate (around, Point_3D_impl(0,0,0), teta);

    kit_.event().conformation_changed();
}

void Model_impl::add_bond( Bond * bond )
{
    INVARIANT;

    //Default_container_of_bonds::add_bond( bond );
    bonds_.push_back( bond );

    //kit_.event().structure_changed();
}

void Model_impl::
detect_molecules ()
{
    INVARIANT;

    Molecular_detector detector (*this);
    detector.execute ();

    //molecules_.clear();

}

void Model_impl::
add_molecule (Molecule * mol)
{
    INVARIANT;

    molecules_.push_back (mol);
   // kit_.event().structure_changed();
}

Molecule * Model_impl::
orphan_molecule (Molecule & mol)
{
    INVARIANT;

    return molecules_.orphan (mol);
    //kit_.event().structure_changed();
}

void Model_impl::
add_residue (Residue *res)
{
    residues_.push_back (res);
}

void Model_impl::
add_joint (Joint *joint)
{
    joints_.push_back (joint);
}

void Model_impl::
add_atom (Atom * atom)// fix remove
{
    INVARIANT;

//fix!!!    atom->kit().event().when_moved
//fix!!!        (create_command (kit().event(), &Model_event::conformation_changed));

    atoms_.push_back (atom);
    set_ownership (*atom, *this);

//fix!!!    kit_.event().structure_changed();
}

void Model_impl::
erase (Atom & atom)
{
    INVARIANT;

    atoms_.remove (atom);//fix refs

    kit_.event().structure_changed();
}

void Model_impl::
clear()
{
    INVARIANT; 

    kit().selector().clear_picking ();

    //atoms_.    clear ();
    //bonds_.    clear ();
    //molecules_.clear ();
    joints_   .clear ();
    residues_ .clear ();
    molecules_.clear ();
    bonds_.    clear ();
    atoms_.    clear ();

    kit_.event().structure_changed();
}

void Model_impl::        //fix
remove_deads ()
{
    INVARIANT;

    int i,j;

    {// Remove dead associations========================================

    // Molecule
    Array_of_ref <Molecule> tmp_molecule;
    for (i=0;  i<molecule_count();  ++i)
    {
        Molecule & current_molecule = molecule(i);

        if (current_molecule.is_alive())
            tmp_molecule.push_back (current_molecule);
        else
        {
            for (j=0;  j<current_molecule.residue_count();  ++j)
                current_molecule.residue(j).set_dead();

            //current_molecule.remove_dead_residues ();

            for (j=0;  j<current_molecule.atom_count();  ++j)
                current_molecule.atom(j).set_dead();

            //current_molecule.remove_dead_atoms ();

            current_molecule.kill();
        }
    }

    molecules_.orphan_all ();

    for (i=0;  i<tmp_molecule.size();  ++i)
        molecules_.adopt (&tmp_molecule[i]);


    // Residue
    //Array_of_ref <Residue> tmp_residue;

    for (i=0;  i<residue_count ();  ++i)
    {
        Residue & current_residue = residue(i);

        if (current_residue.is_alive())
            ;//tmp_residue.push_back (current_residue);
        else
        {
            for (j=0;  j<current_residue.joint_count();  ++j)
                current_residue.joint(j).set_dead();

            for (j=0;  j<current_residue.atom_count();  ++j)
                current_residue.atom(j).set_dead();

            //current_residue.kill();
        }
    }
    
    //residues_.orphan_all ();

    //for (i=0;  i<tmp_residue.size();  ++i)
    //    residues_.adopt (&tmp_residue[i]);


    // Bond
    Array_of_ref <Bond> tmp_bond;

    for (i=0;  i<bond_count();  ++i)
    {
        Bond & current_bond = bond(i);
        bool atoms_alive = true;

        for (j=0;  j<current_bond.atom_count();  ++j)
        {
            if (current_bond.atom(j).is_dead())
            {
                atoms_alive = false;
                break;
            }
        }

        if (current_bond.is_alive() && atoms_alive)
            tmp_bond.push_back (current_bond);
        else
            current_bond.kill();
    }

    bonds_.orphan_all ();

    for (i=0;  i<tmp_bond.size();  ++i)
        bonds_.adopt (&tmp_bond[i]);


    // Joint
    //Array_of_ref <Joint> tmp_joint;

    for (i=0;  i<joint_count ();  ++i)
    {
        Joint & current_joint = joints_[i];

        if (current_joint.                   is_alive() &&
            current_joint.stub_atom().       is_alive() &&
            current_joint.terminal_atom().   is_alive() &&
            current_joint.directional_atom().is_alive() )

            ;//tmp_joint.push_back (current_joint);

        else
            //current_joint.kill();
            current_joint.set_dead();
    }

    //joints_.orphan_all ();

    //for (i=0;  i<tmp_joint.size();  ++i)
    //    joints_.adopt (&tmp_joint[i]);


    }// ================================================================
    

    /*// Atom
    //using namespace std;
    //list <Atom*> tmp;                       //fix (speed) to Array_of_ref?
    Array_of_ref <Atom> tmp_atom;
    for (i=0;  i<atom_count();  ++i)
        tmp_atom.push_back (atom(i));//fix speed (like Molecule)
    atoms_.orphan_all ();

    for (i=0;  i<tmp_atom.size();  ++i)
    {
        Atom & current_atom = tmp_atom[i];
        if (current_atom.is_alive())
            atoms_.adopt (&current_atom);
        else
        {
            //fix to atom_group
            //fix make dead
            // remove bonds
            for (j=0;  j<current_atom.bound_count();  ++j)
//                delete bonds_.orphan (atom(j));
            {
                Bond & current_bond = current_atom.bond (j);
                current_bond.set_alive (false);
                //Bond * bond = bonds_.orphan (current_bond);
                //delete bond;
            }

            current_atom.kit().in_molecule().remove_atom (current_atom); //fix ->
            delete &current_atom;
        }
    }//*/

    /*// Bond
    Array_of_ref <Bond> tmp_bond;
    for (i=0;  i<bond_count();  ++i)
        if (bond(i).is_alive())
            tmp_bond.push_back (bond(i));

    bonds_.orphan_all ();
    for (i=0;  i<tmp_bond.size();  ++i)
        bonds_.adopt (&tmp_bond[i]);//*/


    // Remove empty associations=======================================
    {

    // Residue
    //Array_of_ref <Residue> tmp_residue;

    for (i=0;  i<residue_count ();  ++i)
    {
        Residue & current_residue = residue(i);

        current_residue.remove_dead_joints();
        current_residue.remove_dead_atoms ();

        if (current_residue.atom_count() != 0)
            ;//tmp_residue.push_back (current_residue);
        else
            //current_residue.kill();
            current_residue.set_dead();
    }

    //residues_.orphan_all ();

    //for (i=0;  i<tmp_residue.size();  ++i)
    //    residues_.adopt (&tmp_residue[i]);


    // Molecule
    Array_of_ref <Molecule> tmp_molecule;

    for (i=0;  i<molecule_count();  ++i)
    {
        Molecule & current_molecule = molecule(i);

        current_molecule.remove_dead_residues ();
        current_molecule.remove_dead_atoms ();

        if (current_molecule.atom_count() != 0)
        {
            current_molecule.remove_dead_residues();
            tmp_molecule.push_back (current_molecule);
        }
        else
            current_molecule.kill();
    }

    molecules_.orphan_all ();

    for (i=0;  i<tmp_molecule.size();  ++i)
        molecules_.adopt (&tmp_molecule[i]);

    }// ================================================================
    residues_.stable_kill_deads();
    joints_.stable_kill_deads();
    atoms_.stable_kill_deads();

    kit_.sequence_editor().reset();

    kit_.event().structure_changed();
    INVARIANT;
}

Atom & Model_impl::
atom( const ID & id )
{
    INVARIANT;

    //    return Default_container_of_atoms::atom( id );
    for( int i=0;  i<atoms_.size();  ++i )
        if( atoms_[i].id().is_equal( id ) )
            return atoms_[i];

    FLAW( "No atom with such ID." );
}

const Atom & Model_impl::
atom( const ID & id ) const //fix speed
{
    INVARIANT;

    //    return Default_container_of_atoms::atom( id );
    for( int i=0;  i<atoms_.size();  ++i )
        if( atoms_[i].id().is_equal( id ) )
            return atoms_[i];

    kit_.event().conformation_changed ();

    FLAW( "No atom with such ID." );
}

Joint & Model_impl::
joint (Text const& local_id)
{
    for (int i=0;  i<joints_.size();  ++i)
        if (joints_[i].local_id() == local_id)
            return joints_[i];

    FLAW (Text("No joint with local ID '")+ local_id +"'.");
}

//*/
bool Model_impl::
save (bool to_comment)
{
    return kit_.file().save (to_comment);
}

bool Model_impl::
save_as (Text const & file_name, Text const& file_type,
         Text * name_saved, bool to_comment)
{
    return kit_.file().save_as (file_name, file_type, name_saved, to_comment);
}

void Model_impl::
load (Text const & file_name, Text const & file_type)
{
    kit_.file().load (file_name, file_type);
    kit_.event().structure_changed();
}

void Model_impl::
add (Text const & file_name, Text const & file_type)
{
    kit_.file().add (file_name, file_type);
    kit_.event().structure_changed();
}

void Model_impl::
pull_on (Text const & file_name, Text const & file_type)
{
    kit_.file().pull_on (file_name, file_type);
    kit_.event().structure_changed();
}

void Model_impl::
clear_local_ids ()
{
    int i;

    for (i=0;  i<atoms_.size();  ++i)
        atoms_[i].set_local_id ("");

    //for (i=0;  i<residues_.size();  ++i)
    //    residues_[i].clear_local_ids ("");

    for (i=0;  i<molecules_.size();  ++i)
        molecules_[i].clear_local_ids ();
}

}//MM
