#include "Sequence_editor.h"

#include "Command_template.h"
#include "Atom.h"
#include "Atom_kit.h"
#include "Model.h"
#include "Model_kit.h"
#include "Residue.h"
#include "Molecule.h"
#include "Joint.h"
#include "Point_3D.h"
#include "Cleaner.h"
#include "Defs.h"
#include "Create.h"
#include "Default_bond.h"
#include "Default_builder.h"
#include "User.h"

#include "MinMax.h"

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

namespace MM
{
Text const& Sequence_editor::
name () const
{
    static Text result = "Sequence editor";
    return result;
}

void Sequence_editor::handle_start_editing    () {}
void Sequence_editor::handle_finish_editing   () {}
void Sequence_editor::handle_cancel_editing   () {}
void Sequence_editor::handle_activate_editing () {}



Sequence_editor::
Sequence_editor (Model & model)
:
    model_              (model),
    max_sequence_length_(0),
    reset_              (*create_command_1 (*this, &Sequence_editor::reset))
{
    model.kit().event().when_model_changed (&reset_);
    reset ();
}

Sequence_editor::
~Sequence_editor ()
{
    model_.kit().event().when_model_changed (&reset_, false);
    max_sequence_length_ = 0;
}

void Sequence_editor::
reset (Model_event::Hint hint)
{
    if (hint != Model_event::Structure_changed)
        return;

    molecules_.clear();
    max_sequence_length_ = 0;

    for (int i=0;  i<model_.molecule_count();  ++i)
    {
        Molecule & mol = model_.molecule (i);

        if (mol.residue_count () != 0)
        {
            molecules_.add (mol);

            if (max_sequence_length_ < mol.residue_count ())
                max_sequence_length_ = mol.residue_count ();
        }
    }
}

Residue & Sequence_editor::
residue (int mol_n, int res_n)
//residue (int , int )
{
    if (mol_n >= 0 && res_n >=0 &&
        mol_n < molecules_.molecule_count() &&
        res_n < molecules_.molecule(mol_n).residue_count())

        return molecules_.molecule (mol_n).residue (res_n);
    else
        return Residue::none();
}

void Sequence_editor::
new_molecule (Text const& file_name)
{
    model_.add (file_name);
    reset ();
}

int Sequence_editor::
append (int mol_n, int res_n, Text const& file_name)
{
    start_editing ();

    if (mol_n >= model_.molecule_count())
        mol_n  = model_.molecule_count() - 1;//fix hack

    int added = -1;//fix
    int first_res_N = model_.residue_count ();
    model_.add (file_name);
    int res_count = model_.residue_count ();

    bool right_terminal_residue = res_n != 0 &&
        //!model_.molecule(mol_n).residue(res_n).has_right_joint();
        molecule(mol_n).residue(res_n).joint_count() == 1;

    if (right_terminal_residue)
        --res_n;

    // move
    Residue & left_residue  = molecule(mol_n).residue(res_n);
    Residue & right_residue = model_.residue(first_res_N + 1);

    if (left_residue.joint_count() == 0)
    {
        to_user().error ("No joints in left residue.");//fix look before this function
        return 0;
    }
    if (right_residue.joint_count() == 0)
    {
        to_user().error ("No joints in right residue.");//fix look before this function
        return 0;
    }

    Joint   & left_mol_right_joint = left_residue.right_joint();

    Joint   & right_mol_left_joint = right_residue.left_joint();

    Residue & left_residue_2 = right_residue;
    Joint   & left_mol_right_joint_2 = left_residue_2.right_joint();

    Residue & right_residue_2 = molecule(mol_n).residue(res_n+1);
    Joint   & right_mol_left_joint_2 = right_residue_2.left_joint();

    double distance = max 
        (
            left_mol_right_joint.stub_atom().position()
                .distance (
            left_mol_right_joint.terminal_atom().position())
        ,
            right_mol_left_joint.stub_atom().position()
                .distance (
            right_mol_left_joint.terminal_atom().position())
        );

    double distance_2 = max 
        (
            left_mol_right_joint_2.stub_atom().position()
                .distance (
            left_mol_right_joint_2.terminal_atom().position())
        ,
            right_mol_left_joint_2.stub_atom().position()
                .distance (
            right_mol_left_joint_2.terminal_atom().position())
        );

    if (right_terminal_residue)
    {
        Cleaner cleaner (model_);

        cleaner.add (left_mol_right_joint.stub_atom().kit().in_residue());
        cleaner.add (right_mol_left_joint.stub_atom().kit().in_residue());

        chain (left_residue, right_residue, distance);

        added = res_count - first_res_N - 2;
    }
    else
    {
        Cleaner cleaner (model_);

        cleaner.add (right_mol_left_joint.  stub_atom().kit().in_residue());
        cleaner.add (left_mol_right_joint_2.stub_atom().kit().in_residue());

        Cleaner bond_cleaner (model_);
        int bond_n;
        bool done =     left_mol_right_joint.stub_atom().
            bound_with (left_mol_right_joint.terminal_atom(), &bond_n);
        CHECK ("Stub - terminal bond 1 found.", done);
        bond_cleaner.add (left_mol_right_joint.stub_atom().bond(bond_n));

        done =     right_mol_left_joint.stub_atom().
            bound_with (right_mol_left_joint.terminal_atom(), &bond_n);
        CHECK ("Stub - terminal bond 2 found.", done);
        bond_cleaner.add (right_mol_left_joint.stub_atom().bond(bond_n));//*/

        chain (left_residue, right_residue, distance);

        bond_cleaner.execute();
        
        //Default_bond tmp_bond 
        //  (left_mol_right_joint.stub_atom(), right_mol_left_joint.stub_atom());
        Own <Bond> tmp_bond (prototype <Bond> ().clone
            (left_mol_right_joint.stub_atom(), 
             right_mol_left_joint.stub_atom(), Order()));

        chain (left_residue_2, right_residue_2, distance_2);
        added = res_count - first_res_N - 2;
    }

    model_.detect_molecules ();
    reset ();
    finish_editing ();

    return added;
}

void Sequence_editor::
chain (Residue &left_residue, Residue &right_residue, double distance)
{
    Joint & left_mol_right_joint = left_residue.right_joint();
    Joint & right_mol_left_joint = right_residue.left_joint();

    left_mol_right_joint. chain (right_mol_left_joint, distance);

    // reconnect 
    //Bond * bond = prototype <Bond>(). 
    //    clone (left_mol_right_joint.terminal_atom(), 
    //    right_mol_left_joint.terminal_atom(), Order());
    //model_.add_bond (bond);//*/
    Default_builder builder;
    builder.new_bond (model_, left_mol_right_joint.terminal_atom(), 
        right_mol_left_joint.terminal_atom(), Order());

    int i;
    Atom *stub_atom = &left_mol_right_joint.stub_atom ();
    left_mol_right_joint.set_stub_atom (right_mol_left_joint.terminal_atom());
    for (i=0;  i<left_residue.joint_count();  ++i)
    {
        Joint & current_joint = left_residue.joint(i);
        if (&current_joint.directional_atom() == stub_atom)
            current_joint.set_directional_atom (right_mol_left_joint.terminal_atom());
    }

    stub_atom = &right_mol_left_joint.stub_atom ();
    right_mol_left_joint.set_stub_atom (left_mol_right_joint.terminal_atom());
    for (i=0;  i<right_residue.joint_count();  ++i)
    {
        Joint & current_joint = right_residue.joint(i);
        if (&current_joint.directional_atom() == stub_atom)
            current_joint.set_directional_atom (left_mol_right_joint.terminal_atom());
    }
}

}//MM
