#include "Torsion_list.h"

#include "Dihedral_angle.h"
#include "Branch.h"
#include "Molecule.h"
#include "Residue.h"
#include "Atom.h"
#include "Atom_kit.h"
#include "Point_3D.h"
#include "Torsion_type.h"
#include "Fine_type.h"
#include "Joint.h"
#include "User.h"

namespace MM
{
void Torsion_list::
clear()
{
    torsion_type_.  clear();
    value_.         clear();
    residue_ID_.    clear();

    residue_.       clear();
}

void Torsion_list::
add (Text const & torsion_type, double value, Text const & residue_ID)
{
    torsion_type_.  push_back (torsion_type);
    value_.         push_back (value);
    residue_ID_.    push_back (residue_ID == "" ? "-" : residue_ID);

    residue_.insert (residue_ID);
}

void Torsion_list::
apply (Model & model)
{
    for (int i=0;  i<torsion_count();  ++i)
    {
        Text residue_ID = residue_ID_[i];

        if (residue_ID != "-")
        {
            Residue & current_res = residue (model, residue_ID);
            set_torsion (current_res, torsion_type_[i], value_[i]);
        }
        else
        {
            set_torsion (model, torsion_type_[i], value_[i]);
        }
    }
}

void Torsion_list::
apply (Molecule & mol, int res_n, int conformation_n)
{
    Residue &    current_res  = mol.residue (res_n);
    /*Text const & torsion_type = torsion_type_[conformation_n];
    double       value        = value_[conformation_n];

    if (torsion_type == "Joint")
        set_joint_torsion (mol, res_n, value);
    else
        set_torsion (current_res, torsion_type, value);//*/

    first_torsion (conformation_n);
    for (;  is_valid_torsion ();  next_torsion ())
    {
        Text   torsion_type;
        double value;
        current_torsion (torsion_type, value);

        if (torsion_type == "Joint")
            set_joint_torsion (mol, res_n, value);
        else
            set_torsion (current_res, torsion_type, value);
    }
}


// protected: -----------------------------------------------------------------

Residue & Torsion_list::
residue (Model & model, Text const& local_ID)
{
	int residue_count = model.residue_count();

    for (int i=0;  i<residue_count;  ++i)
	{
		Text const & id = model.residue(i).local_id();
        if (id == local_ID)
            return model.residue(i);
	}

    Text message ("Model does not have residue with local_ID '");
    message += local_ID;
    message += "'.";
    to_user().error (message);
    return Residue::none();
}

void Torsion_list::
set_torsion (Atom_group & group, Text const& torsion_type, double value)
{
    int i;

    // Find atoms =========================================
    Atom *atom_1 = 0, *atom_2 = 0, *atom_3 = 0, *atom_4 = 0; 
    Text  atom_1_ID,   atom_2_ID,   atom_3_ID,   atom_4_ID;
    Torsion_type::get (torsion_type, atom_1_ID, atom_2_ID, atom_3_ID, atom_4_ID);

    int atom_1_fine_type = Fine_type::none;
    int atom_2_fine_type = Fine_type::none;
    int atom_3_fine_type = Fine_type::none;
    int atom_4_fine_type = Fine_type::none;

    if (Fine_type::exist (atom_1_ID.c_str())) 
        atom_1_fine_type = Fine_type::id (atom_1_ID.c_str());
    if (Fine_type::exist (atom_2_ID.c_str())) 
        atom_2_fine_type = Fine_type::id (atom_2_ID.c_str());
    if (Fine_type::exist (atom_3_ID.c_str())) 
        atom_3_fine_type = Fine_type::id (atom_3_ID.c_str());
    if (Fine_type::exist (atom_4_ID.c_str())) 
        atom_4_fine_type = Fine_type::id (atom_4_ID.c_str());

    // try fine types
    for (i=0;  i<group.atom_count();  ++i)
    {
        Atom & current_atom = group.atom (i);
        Atom_kit & kit      = current_atom.kit();
        int fine_type       = kit.fine_type_id();

        if (fine_type == Fine_type::none)
            continue;

        /*if (fine_type == atom_1_fine_type)
        {
            if (atom_1 == 0)    atom_1 = &current_atom;
            else{the_same_fine_type(current_atom, *atom_1, fine_type); return;}
        }//*/
        if (fine_type == atom_2_fine_type)
        {
            if (atom_2 == 0)    
				atom_2 = &current_atom;
            else
			{
				the_same_type
					(current_atom, *atom_2, 
					 Fine_type::name(fine_type), "Fine type"); 
				return;
			}
        }
        if (fine_type == atom_3_fine_type)
        {
            if (atom_3 == 0)    
				atom_3 = &current_atom;
            else
			{
				the_same_type
					(current_atom, *atom_3, 
					 Fine_type::name(fine_type), "Fine type"); 
				return;
			}
        }
        /*if (fine_type == atom_4_fine_type)
        {
            if (atom_4 == 0)    atom_4 = &current_atom;
            else{the_same_fine_type(current_atom, *atom_4, fine_type); return;}
        }//*/
    }
    if (atom_2 != 0 && atom_3 != 0)
    {
        for (i=0;  i<atom_2->bound_count();  ++i)
        {
            Atom & current_atom = atom_2->bound (i);
            Atom_kit & kit      = current_atom.kit();
            int fine_type       = kit.fine_type_id();

            if (fine_type == Fine_type::none || &current_atom == atom_3)
                continue;

            if (fine_type == atom_1_fine_type)
            {
                if (atom_1 == 0)    atom_1 = &current_atom;
                else
                {
                    the_same_type 
						(current_atom, *atom_1, 
						 Fine_type::name(fine_type), "Fine type"); 
                    return;
                }
            }
        }

        for (i=0;  i<atom_3->bound_count();  ++i)
        {
            Atom & current_atom = atom_3->bound (i);
            Atom_kit & kit      = current_atom.kit();
            int fine_type       = kit.fine_type_id();

            if (fine_type == Fine_type::none || &current_atom == atom_2)
                continue;

            if (fine_type == atom_4_fine_type)
            {
                if (atom_4 == 0)    atom_4 = &current_atom;
                else
                {
                    the_same_type 
						(current_atom, *atom_4, 
						 Fine_type::name(fine_type), "Fine type"); 
                    return;
                }
            }
        }
    }

    // try names
    if (atom_1 == 0 || atom_2 == 0 || atom_3 == 0 || atom_4 == 0)
	{
		atom_1 = 0, atom_2 = 0, atom_3 = 0, atom_4 = 0; 

		for (i=0;  i<group.atom_count();  ++i)
		{
			Atom & current_atom = group.atom (i);
			Atom_kit & kit      = current_atom.kit();
			char const * name = Fine_type::to(kit.fine_type_name(), "Name", 0);

			if (name == 0)
				continue;

			if (name == atom_2_ID)
			{
				if (atom_2 == 0)    
					atom_2 = &current_atom;
				else
				{
					the_same_type (current_atom, *atom_2, Fine_type::none, "name"); 
					return;
				}
			}
			if (name == atom_3_ID)
			{
				if (atom_3 == 0)    
					atom_3 = &current_atom;
				else
				{
					the_same_type (current_atom, *atom_3, Fine_type::none, "name"); 
					return;
				}
			}
		}
		if (atom_2 != 0 && atom_3 != 0)
		{
			for (i=0;  i<atom_2->bound_count();  ++i)
			{
				Atom & current_atom = atom_2->bound (i);
				Atom_kit & kit      = current_atom.kit();
				char const * name   = Fine_type::to
					(kit.fine_type_name(), "Name", 0);

				if (name == 0 || &current_atom == atom_3)
					continue;

				if (name == atom_1_ID)
				{
					if (atom_1 == 0)    
						atom_1 = &current_atom;
					else
					{
						the_same_type (current_atom, *atom_1, Fine_type::none, "name"); 
						return;
					}
				}
			}

			for (i=0;  i<atom_3->bound_count();  ++i)
			{
				Atom & current_atom = atom_3->bound (i);
				Atom_kit & kit      = current_atom.kit();
				char const * name   = Fine_type::to
					(kit.fine_type_name(), "Name", 0);

				if (name == 0 || &current_atom == atom_2)
					continue;

				if (name == atom_4_ID)
				{
					if (atom_4 == 0)    
						atom_4 = &current_atom;
					else
					{
						the_same_type (current_atom, *atom_4, Fine_type::none, "name"); 
						return;
					}
				}
			}
		}
	}

    // try local IDs
    if (atom_1 == 0 || atom_2 == 0 || atom_3 == 0 || atom_4 == 0)
    {
		atom_1 = 0, atom_2 = 0, atom_3 = 0, atom_4 = 0; 

        for (i=0;  i<group.atom_count();  ++i)
        {
            Atom &       current_atom = group.atom (i);
            if (!current_atom.has_local_id())   continue;
            Text const & local_id     = current_atom.local_id();

            if (atom_1 == 0 && local_id == atom_1_ID)   atom_1 = &current_atom;  
            if (atom_2 == 0 && local_id == atom_2_ID)   atom_2 = &current_atom;  
            if (atom_3 == 0 && local_id == atom_3_ID)   atom_3 = &current_atom;  
            if (atom_4 == 0 && local_id == atom_4_ID)   atom_4 = &current_atom;  
        }
    }

    // not found
    if (atom_1 == 0 || atom_2 == 0 || atom_3 == 0 || atom_4 == 0)
    {
        Text id = atom_1 == 0 ? atom_1_ID 
               : (atom_2 == 0 ? atom_2_ID
               : (atom_3 == 0 ? atom_3_ID
               :                atom_4_ID));

        Text message;
        message += "Atom '";
        message += id;
        message += "' was not found.";
        to_user().error (message);
        return;
    }

    set_torsion (*atom_1, *atom_2, *atom_3, *atom_4, value);
}

void Torsion_list::
set_joint_torsion (Molecule & mol, int res_n, double value)
{
    if (res_n == 0)
        return;

    Joint & right_joint = mol.residue(res_n-1).right_joint();//fix find for branch
    Joint & left_joint  = mol.residue(res_n).  left_joint();

    CHECK ("", &right_joint.terminal_atom() == &left_joint.stub_atom());

    Atom & atom_1 = right_joint.directional_atom(); 
    Atom & atom_2 = left_joint.stub_atom(); 
    Atom & atom_3 = left_joint.terminal_atom(); 
    Atom & atom_4 = left_joint.directional_atom(); 

    CHECK ("", &atom_2 != &atom_3);

    set_torsion (atom_1, atom_2, atom_3, atom_4, value);
}

void Torsion_list::
set_torsion (Atom & atom_1, Atom & atom_2, 
             Atom & atom_3, Atom & atom_4, double value)
{
    // Define rotation angle ==============================
    CHECK ("", atom_2.bound_with (atom_3));

    Dihedral_angle  old_dihedral_angle 
        (atom_1.position (), atom_2.position (),
         atom_3.position (), atom_4.position ());

    double old_angle = old_dihedral_angle.value();
    double angle     = value - old_angle;

    // Rotate =============================================
     Vector_3D_impl axis (0,0,0);
     atom_3.position(). difference (atom_2.position(), &axis);

     Branch branch (atom_3, atom_2);

     if (branch.is_a_whole_molecule())
     {
        Text message;
        message += "Can not rotate around the cyclic bond '";
        message += atom_2.local_id ();
        message += "'-'";
        message += atom_3.local_id ();
        message += "'.";
        to_user().error (message);
        return;
     }

    for (int i=0;  i<branch.atom_count();  ++i)
        branch.atom(i). rotate (axis, atom_2.position(), angle);
}

void Torsion_list::
the_same_type (Atom const& atom1, Atom const& atom_2, 
			   Text const& type, Text const& word)
{
    Text message;
    message += "Atoms ";
    message += atom1.element().c_str();
    message += " ";
    if (atom1.has_local_id())
    {
        message += atom1.local_id();
        message += " ";
    }
    message += "and ";
    message += atom_2.element().c_str();
    message += " ";
    if (atom_2.has_local_id())
    {
        message += atom_2.local_id();
        message += " ";
    }
    message += "have the same ";
    message += word;
    message += " '";
    message += type;
    message += "'.";
    to_user().error (message);
}

int Torsion_list::
residue_count () const
{
    return residue_.size();
}

void Torsion_list::
first_torsion (int residue_n)
{
    std::set <Text>::iterator it = residue_.begin();
    for (int i=0;  i<residue_n;  ++i)
        ++it;
    current_residue_ = *it;
    torsion_n_ = -1;
    next_torsion ();
}

void Torsion_list::
next_torsion ()
{
    ++torsion_n_;

    while (is_valid_torsion ())
    {
        if (residue_ID_[torsion_n_] == current_residue_)
            return;
        ++torsion_n_;
    }
}

bool Torsion_list::
is_valid_torsion () const
{
    return torsion_n_ < torsion_count ();
}

void Torsion_list::
current_torsion (Text & torsion_type, double & value) const
{
    torsion_type = torsion_type_[torsion_n_];
    value        = value_       [torsion_n_];
}

}//MM

