// -*- C++ -*-

/* 
 * GChemPaint library
 * chain.h 
 *
 * Copyright (C) 2001-2004 Jean Bréfort <jean.brefort@normalesup.org>
 *
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 */

#include "gchempaint-config.h"
#include "chain.h"
#include "molecule.h"
#include "document.h"

gcpChain::gcpChain(gcpBond* pBond, gcpAtom* pAtom, TypeId Type): Object(Type)
{
	gcpAtom *pAtom0;
	if (pAtom)
		pAtom0 = (gcpAtom*)pBond->GetAtom(pAtom);
	else
	{
		pAtom0 = (gcpAtom*)pBond->GetAtom(1);
		pAtom = (gcpAtom*)pBond->GetAtom(0);
	}
	m_Bonds[pAtom].fwd = pBond;
	m_Bonds[pAtom0].rev = pBond;
}

gcpChain::gcpChain(gcpMolecule* Molecule, gcpAtom* pAtom, TypeId Type): Object(Type)
{
	m_Molecule = Molecule;
	if (pAtom)
	{
		FindCycles(pAtom);
	}
}

/*
* Add a bond in an existing molecule and update cycles
* Implementation might have to be changed
*/
gcpChain::gcpChain(gcpMolecule* Molecule, gcpBond* pBond, TypeId Type): Object(Type)
{
	m_Molecule = Molecule;
	if (pBond)
	{
		gcpAtom *pAtom0, *pAtom;
		pAtom0 = (gcpAtom*)pBond->GetAtom(0);
		m_Bonds[pAtom0].fwd = pBond;
		pAtom = (gcpAtom*)pBond->GetAtom(1);
		m_Bonds[pAtom].rev = pBond;
		map<Atom*, Bond*>::iterator i;
		gcpBond* pBond0 = (gcpBond*)pAtom->GetFirstBond(i);
		while (pBond0)
		{
			if ((pBond0 != pBond) && FindCycle(pAtom, pBond0)) break;
			pBond0 = (gcpBond*)pAtom->GetNextBond(i);
		}
	}
	gcpDocument* pDoc = (gcpDocument*)m_Molecule->GetDocument();
	if (pDoc) pDoc->Update();
}

gcpChain::~gcpChain()
{
	m_Bonds.clear();
}

bool gcpChain::FindCycle(gcpAtom* pAtom, gcpBond* pBond)
{
	gcpAtom* pAtom1 = (gcpAtom*)pBond->GetAtom(pAtom);
	if (m_Bonds[pAtom1].fwd != NULL)
	{
		if (m_Bonds[pAtom1].rev != NULL) return false;
		gcpCycle* pCycle = new gcpCycle(m_Molecule);
		pCycle->m_Bonds[pAtom1].rev = pBond;
		pCycle->m_Bonds[pAtom1].fwd = m_Bonds[pAtom1].fwd;
		pCycle->m_Bonds[pAtom].fwd = pBond;
		pCycle->m_Bonds[pAtom].rev= m_Bonds[pAtom].rev;
		m_Bonds[pAtom].rev->AddCycle(pCycle);
		pBond->AddCycle(pCycle);
		while (pBond = pCycle->m_Bonds[pAtom1].fwd, pAtom1 = (gcpAtom*)pBond->GetAtom(pAtom1), pAtom != pAtom1)
		{
			pCycle->m_Bonds[pAtom1].rev = pBond;
			pCycle->m_Bonds[pAtom1].fwd = m_Bonds[pAtom1].fwd;
			pBond->AddCycle(pCycle);
		}
		pCycle->Simplify();//to reduce size of fused cycles
		return true;
	}
	m_Bonds[pAtom].fwd = pBond;
	m_Bonds[pAtom1].rev = pBond;
	map<Atom*, Bond*>::iterator i;
	gcpBond* pBond1 = (gcpBond*)pAtom1->GetFirstBond(i);
	while (pBond1)
	{
		if ((pBond1 != pBond) && FindCycle(pAtom1, pBond1)) return true;
		pBond1 = (gcpBond*)pAtom1->GetNextBond(i);
	}
	m_Bonds[pAtom].fwd = NULL;
	m_Bonds.erase(pAtom1);
	return false;
}

void gcpChain::FindCycles(gcpAtom* pAtom)
{
	map<Atom*, Bond*>::iterator i;
	gcpBond* pBond = (gcpBond*)pAtom->GetFirstBond(i);
	gcpAtom* pAtom0;
	while (pBond != NULL)
	{
		m_Bonds[pAtom].fwd = pBond;
		pAtom0 = (gcpAtom*)pBond->GetAtom(pAtom);
		if (pBond->GetMolecule() != m_Molecule)
		{
			m_Molecule->AddBond(pBond);
		}
		if ((pAtom0)->GetMolecule() != m_Molecule)
		{
			if (pAtom0->GetMolecule() != m_Molecule) pAtom0->AddToMolecule(m_Molecule);
			m_Bonds[pAtom0].rev = pBond;
			FindCycles(pAtom0);
		}
		else
		{
			if (m_Bonds[pAtom0].fwd != NULL)
			{
				gcpBond* pBond0 = m_Bonds[pAtom0].fwd;
				if (pAtom != pBond0->GetAtom(pAtom0))	//Cycle found.
				{
					gcpCycle* pCycle = new gcpCycle(m_Molecule);
					pCycle->m_Bonds[pAtom0].rev = pBond;
					pCycle->m_Bonds[pAtom0].fwd = pBond0;
					pBond0->AddCycle(pCycle);
					while (pAtom != pAtom0)
					{
						pAtom0 = (gcpAtom*)pBond0->GetAtom(pAtom0);
						pCycle->m_Bonds[pAtom0].rev = pBond0;
						pBond0 = m_Bonds[pAtom0].fwd;
						pCycle->m_Bonds[pAtom0].fwd = pBond0;
						pBond0->AddCycle(pCycle);
					}
					pCycle->Simplify();//to reduce size of fused cycles
				}
			}
		}
		pBond = (gcpBond*)pAtom->GetNextBond(i);
	}
	m_Bonds.erase(pAtom);
}

void gcpChain::Reverse()
{
	map<gcpAtom*, gcpChainElt>::iterator i;
	gcpBond* pBond;
	for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
	{
		pBond = (*i).second.fwd;
		(*i).second.fwd = (*i).second.rev;
		(*i).second.rev = pBond;
	}
}

void gcpChain::Erase(gcpAtom* pAtom1, gcpAtom* pAtom2)
{//This function is not safe
	gcpAtom *pAtom = (gcpAtom*)m_Bonds[pAtom1].fwd->GetAtom(pAtom1), *pAtom0;
	m_Bonds[pAtom1].fwd = NULL;
	while (pAtom != pAtom2)
	{
		pAtom = (gcpAtom*)m_Bonds[pAtom].fwd->GetAtom(pAtom0 = pAtom);
		m_Bonds.erase(pAtom0);
	}
	m_Bonds[pAtom2].rev = NULL;
}

void gcpChain::Insert(gcpAtom* pAtom1, gcpAtom* pAtom2, gcpChain& Chain)
{//This function is not safe
	m_Bonds[pAtom1].fwd = Chain.m_Bonds[pAtom1].fwd;
	gcpAtom *pAtom = (gcpAtom*)m_Bonds[pAtom1].fwd->GetAtom(pAtom1);
	while (pAtom != pAtom2)
	{
		m_Bonds[pAtom] = Chain.m_Bonds[pAtom];
		pAtom = (gcpAtom*)m_Bonds[pAtom].fwd->GetAtom(pAtom);
	}
	m_Bonds[pAtom2].rev = Chain.m_Bonds[pAtom2].rev;
}

void gcpChain::Extract(gcpAtom* pAtom1, gcpAtom* pAtom2, gcpChain& Chain)
{
	Chain.m_Bonds.clear();
	if (m_Bonds[pAtom1].fwd == NULL)
	{
		if (m_Bonds[pAtom1].rev == NULL) m_Bonds.erase(pAtom1);//pAtom1 is not in the chain
		return;
	}
	Chain.m_Bonds[pAtom1].fwd = m_Bonds[pAtom1].fwd;
	Chain.m_Bonds[pAtom1].rev = NULL;
	gcpAtom *pAtom = (gcpAtom*)Chain.m_Bonds[pAtom1].fwd->GetAtom(pAtom1);
	while (pAtom != pAtom2)
	{
		Chain.m_Bonds[pAtom] = m_Bonds[pAtom];
		if (m_Bonds[pAtom].fwd == NULL) return; //Chain never reach pAtom2
		pAtom = (gcpAtom*)m_Bonds[pAtom].fwd->GetAtom(pAtom);
	}
	Chain.m_Bonds[pAtom2].rev = m_Bonds[pAtom2].rev;
	Chain.m_Bonds[pAtom2].fwd = NULL;
}

unsigned gcpChain::GetUnsaturations()
{
	unsigned n = 0;
	std::map<gcpAtom*, gcpChainElt>::iterator i;
	for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
		if ((*i).second.fwd && ((*i).second.fwd->GetOrder() > 1)) n += 1;
	return n;
}
	
unsigned gcpChain::GetHeteroatoms()
{
	unsigned n = 0;
	std::map<gcpAtom*, gcpChainElt>::iterator i;
	for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
		if ((*i).first->GetZ() != 6) n += 1;
	return n;
}

void gcpChain::AddBond(gcpAtom* start, gcpAtom* end)
{
	gcpBond* pBond = (gcpBond*)start->GetBond(end);
	m_Bonds[start].fwd = pBond;
	m_Bonds[end].rev = pBond;
}

bool gcpChain::Contains(gcpAtom* pAtom)
{
	if ((m_Bonds[pAtom].fwd == NULL) && (m_Bonds[pAtom].rev == NULL))
	{
		m_Bonds.erase(pAtom);
		return false;
	}
	else return true;
}

bool gcpChain::Contains(gcpBond* pBond)
{
	gcpAtom *pAtom = (gcpAtom*)pBond->GetAtom(0);
	if ((m_Bonds[pAtom].fwd == NULL) && (m_Bonds[pAtom].rev == NULL))
	{
		m_Bonds.erase(pAtom);
		return false;
	}
	if ((m_Bonds[pAtom].fwd == pBond) && (m_Bonds[pAtom].rev == pBond)) return true;
	return false;
}

unsigned gcpChain::GetLength()
{
	unsigned n = 0;
	std::map<gcpAtom*, gcpChainElt>::iterator i;
	for (i = m_Bonds.begin(); i != m_Bonds.end(); i++) if ((*i).second.fwd) n++;
	return n;
}

double gcpChain::GetMeanBondLength()
{
	unsigned n = 0;
	double l = 0;
	std::map<gcpAtom*, gcpChainElt>::iterator i;
	for (i = m_Bonds.begin(); i != m_Bonds.end(); i++)
		if ((*i).second.fwd)
		{
			n++;
			l += (*i).second.fwd->Get2DLength();
		}
	return l / n;
}

gcpAtom* gcpChain::GetNextAtom(gcpAtom* pAtom)
{
	return (gcpAtom*)m_Bonds[pAtom].fwd->GetAtom(pAtom);
}


syntax highlighted by Code2HTML, v. 0.9.1