#include "Atom_impl.h"

#include "Pdb_attributes.h"
#include "Hyperchem_attributes.h"
#include "Bond.h"
#include "Bond_kit.h"
#include "ID.h"
#include "Create.h"
#include "View_atom_type_manager.h"
#include "Vector_3D_impl.h"
#include "Point_3D_impl.h"
#include "Mach_eps.h"

#include <math.h>

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

namespace MM
{

Atom_impl::~Atom_impl(){}

void Atom_impl::
set_element( Element e )
{
    element_ = e;

    if( kit().has_pdb_attributes() )
        kit().remove_pdb_attributes();

    if( kit().has_hyperchem_attributes() )
        kit().remove_hyperchem_attributes();

    kit().visual().renew();

    on_element_change ();
}

double Atom_impl::
ff_mass  () const
{
    double value = kit_.has_ff_mass() ? kit_.ff_mass() : element_.mass();
    return value;
}

double Atom_impl::
mass  () const
{
    double value = kit_.has_ff_mass() ? kit_.ff_mass() : element_.mass();
    if (Mass::has_minimal () && value < Mass:: minimal())
        value = Mass:: minimal();
    return value;
}

bool Atom_impl::
bound_with (Atom const &second, int * bond) const
{
    for (int i=0;  i<bound_count();  ++i)
        if (&bound(i) == &second)
        {
            if (bond != 0)
                *bond = i;
            return true;
        }

    return false;
}

Atom * Atom_impl::
clone () const          //fix remove ?
{
    Atom_impl * new_atom = new Atom_impl (element());
    handle_clone (new_atom);
    return new_atom;
}

Atom * Atom_impl::
clone (Element const & element) const
{
    Atom_impl * new_atom = new Atom_impl (element);
    handle_clone (new_atom);
    return new_atom;
}

void Atom_impl::
handle_clone (Atom_impl * new_atom) const
{
    new_atom->set_charge (this->charge ());

    new_atom->set_x( this->x() );
    new_atom->set_y( this->y() );
    new_atom->set_z( this->z() );

    new_atom->kit().set_number  (this->kit().number());
    new_atom->kit().set_mm_type (this->kit().mm_type());
    new_atom->kit().flag() = this->kit().flag();
    new_atom->kit().set_fine_type (this->kit().fine_type_id());
    new_atom->kit().set_ff_mass (this->kit().ff_mass());
    new_atom->kit().set_induction_charge (this->kit().induction_charge());

    if( this->kit().has_pdb_attributes() )
        new_atom->kit().add_pdb_attributes
            ( new Pdb_attributes( this->kit().pdb())); //fix clone ?

    if( this->kit().has_hyperchem_attributes() )
        new_atom->kit().add_hyperchem_attributes
            ( new Hyperchem_attributes( this->kit().hyperchem())); //fix clone ?

    //if (this->kit().has_visual())
    //{
    //    new_atom->kit().add_visual
    //        (this->kit().visual().clone (new_atom->kit()));
    //    new_atom->update_view_type(); //fix to view?
    //}//*/

    //fix to kit
    for (int i=0;  i<this->kit().visual().count();  ++i)
    {
        Visual_atom const & view = this->kit().visual().view(i);
        new_atom->kit().add_visual (view.clone (new_atom->kit()));
    }

//    if (this->kit().has_openRM())
//        new_atom->kit().add_openRM
//            (this->kit().openRM().clone());
}

Atom_impl::
Atom_impl ()
:
    kit_    (*this),
    charge_ (.0),
    id_     (prototype <ID> ().clone()),
    position_(0,0,0),
    element_(Element())
{
}

Atom_impl::
Atom_impl (Element const & element)
:
    kit_    (*this),
    charge_ (.0),
    id_     (prototype <ID> ().clone()),
    //x_      (.0),
    //y_      (.0),
    //z_      (.0),
    position_(0,0,0),
    element_(element)//,
    //mm_type_(0)
{
}

//fix
bool Atom_impl::
is_backbone () const
{
    //bool result = false;

    if (kit_.has_fine_type())
    {
        char const * fine_type = kit_.fine_type_name ();
        char const * c_name    = Fine_type::to (fine_type, "Name", 0);

        if (c_name != 0)
        {
            Text name = c_name;

            if (name == "C_alpha" || name == "C_amide"|| name == "N_amide")
                return true;
        }
    }

    if (kit_.has_pdb_attributes())
    {
        Pdb_attributes const & pdb = kit_.pdb ();
        if (pdb.name() == "CA" || pdb.name() == "C"|| pdb.name() == "N")
            return true;
    }

    if (kit_.has_hyperchem_attributes())
    {
        Hyperchem_attributes const & hin = kit_.hyperchem ();
        if (hin.name() == "CA" || hin.name() == "C"|| hin.name() == "N")
            return true;
    }

    //return result;
    return false;
}

/*Array_of <Atom> & Atom_impl::
bound_atoms()
{
    return bound_atoms_;
}

const Array_of <Atom> & Atom_impl::
bound_atoms() const
{
    return bound_atoms_;
}//*/

/*bool Atom_impl::
operator == (const Associated & item ) const
{ 
    return (Associated *)this == &item;
}//*/

void Atom_impl::
update_coordinates ()
{
    //if (kit().has_visual())
    //    kit().visual().update_coordinates (x(), y(), z());

    kit().visual().renew_coordinates();

    for (int i=0;  i<bound_count ();  ++i)
        bond(i).kit().visual().renew();


//    if (kit().has_openRM())
//        kit().openRM().update_coordinates (x(), y(), z());
}

void Atom_impl::
update_view_type ()
{
    //View_atom_type & type = View_atom_type_manager::singleton().
    //    type( element().c_str() );

    //if (kit().has_visual())
    //    kit().visual().update_view_type (type);

    kit().visual().renew_view ();


//    if (kit().has_openRM())
//        kit().openRM().update_view_type (type);
}

void Atom_impl::
add_bond (Bond & bond)
{
    bonds_.push_back (bond);
}

void Atom_impl::
add_bound (Atom & atom)
{
    bound_atoms_.push_back (atom);
}

void Atom_impl::
remove_bond (Bond & bond)
{
    bonds_.remove (bond);
}

void Atom_impl::
remove_bound (Atom & atom)
{
    bound_atoms_.remove (atom);
}

void Atom_impl::
release_all_bonds ()
{
//    Array_of_ref <Bond> bond;
//    for (int i=0;  i<bound_count();  ++i)
//        bond.push_back (bonds_[i]);

    bound_atoms_.clear();
    bonds_.clear();
}

bool Atom_impl::
invariant () const
{
    return bound_atoms_.size() == bonds_.size();
}

void Atom_impl::
set_position (Point_3D const & new_position, bool silent)
{
    position_.set_x (new_position.x());
    position_.set_y (new_position.y());
    position_.set_z (new_position.z());

    if (!silent)
        on_move();
}

void Atom_impl::
move (Vector_3D const & movement, bool silent)
{
    position_ += movement;

    if (!silent)
        on_move();
}


void Atom_impl::
update ()
{
    on_move();
}

void Atom_impl::
rotate (Vector_3D const & around, double teta)
{
    REQUIRE ("'around' is a unit vector.", 
        around.length_2() < 1 + d_mach_eps_2 && 
        around.length_2() > 1 - d_mach_eps_2);

    //fix
    if (!(around.length_2() < 1 + d_mach_eps_2 && 
        around.length_2() > 1 - d_mach_eps_2))
        return;

    Vector_3D const &   n = around;

    Vector_3D_impl      r (0,0,0);
    position_.difference (Point_3D_impl(0,0,0), &r);

    Vector_3D_impl      nrn (n);
    nrn *= r.dot (n);

    Vector_3D_impl      cos_(r);
    cos_ -= nrn;
    cos_ *= cos(teta);

    Vector_3D_impl      sin_(n);
    sin_.cross(r);
    sin_ *= sin(teta);

    Vector_3D_impl      result (nrn);
    result += cos_;
    result += sin_;

    position_.set_zero();
    position_ += result;
}

void Atom_impl::
rotate (Vector_3D const& around, Point_3D const& point, double teta)
{
    Vector_3D_impl n (around);
    n.norm();

    Vector_3D_impl      p (0,0,0);
    point.difference (Point_3D_impl(0,0,0), &p);

    position_ -= p;
    rotate (n, teta);
    position_ += p;

    on_move();
}

}//MM















