#ifndef SimTK_MOLMODEL_NA_H_
#define SimTK_MOLMODEL_NA_H_

/* -------------------------------------------------------------------------- *
 *                      SimTK Core: SimTK Molmodel                            *
 * -------------------------------------------------------------------------- *
 * This is part of the SimTK Core biosimulation toolkit originating from      *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
 * Portions copyright (c) 2007 Stanford University and the Authors.           *
 * Adapted from RNA.h (original by Michael Sherman, Christopher Bruns)        *
 * by  Samuel Flores                                                          *
 *                                                                            *
 * Permission is hereby granted, free of charge, to any person obtaining a    *
 * copy of this software and associated documentation files (the "Software"), *
 * to deal in the Software without restriction, including without limitation  *
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,   *
 * and/or sell copies of the Software, and to permit persons to whom the      *
 * Software is furnished to do so, subject to the following conditions:       *
 *                                                                            *
 * The above copyright notice and this permission notice shall be included in *
 * all copies or substantial portions of the Software.                        *
 *                                                                            *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   *
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    *
 * THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,    *
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR      *
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE  *
 * USE OR OTHER DEALINGS IN THE SOFTWARE.                                     *
 * -------------------------------------------------------------------------- */


#include "molmodel/internal/common.h"
#include "molmodel/internal/Compound.h"

namespace SimTK {

// RiboseCore leaves 4 bond centers open:
// 1) inboard center at O5' atom, for binding 5' phosphate
// 2) 
class SimTK_MOLMODEL_EXPORT RiboseCore : public BiopolymerResidue {
public:
    RiboseCore(const String& name, const String& tlc, char olc) 
        : BiopolymerResidue(name, tlc, olc)
    {
        setBaseAtom(BivalentAtom("O5'", Element::getBySymbol("O"), 120.90*Deg2Rad));

        bondAtom(AliphaticCarbon("C5'"), "O5'/bond2", 0.1423, 180*Deg2Rad);
        bondAtom(AliphaticHydrogen("H5'1"), "C5'/bond4");
        bondAtom(AliphaticHydrogen("H5'2"), "C5'/bond3");

        bondAtom(AliphaticCarbon("C4'"), "C5'/bond2", 0.1510); // length from (Gelbin et al 1996)
        bondAtom(AliphaticHydrogen("H4'"), "C4'/bond4");

        bondAtom(BivalentAtom("O4'", Element::getBySymbol("O"), 112.0*Deg2Rad), "C4'/bond2", 0.1453);

        bondAtom(AliphaticCarbon("C3'"), "C4'/bond3", 0.1524);
        bondAtom(AliphaticHydrogen("H3'"), "C3'/bond4");

        bondAtom(BivalentAtom("O3'", Element::getBySymbol("O"), 119.7*Deg2Rad), "C3'/bond2", 0.1423);
 
        bondAtom(AliphaticCarbon("C2'"), "C3'/bond3", 0.1525);
        //bondAtom(AliphaticHydrogen("H2'"), "C2'/bond3");
        //bondAtom(AliphaticHydrogen("H2'2"), "C2'/bond4");

        bondAtom(AliphaticCarbon("C1'"), "C2'/bond2", 0.1528);
        bondAtom(AliphaticHydrogen("H1'"), "C1'/bond4");

        addRingClosingBond("C1'/bond2", "O4'/bond2", 0.1414);

        // Ring strain reduces ring bond angles
        setDefaultBondAngle(109.6*Deg2Rad, "C1'", "O4'", "C4'"); 
        setDefaultBondAngle(105.5*Deg2Rad, "O4'", "C4'", "C3'"); 
        setDefaultBondAngle(102.7*Deg2Rad, "C4'", "C3'", "C2'"); 
        setDefaultBondAngle(101.5*Deg2Rad, "C3'", "C2'", "C1'"); 
        setDefaultBondAngle(106.4*Deg2Rad, "C2'", "C1'", "O4'");

        nameBondCenter("bondO5", "O5'/bond1");
        nameBondCenter("bondO3", "O3'/bond2");
        nameBondCenter("bondC1", "C1'/bond3");

        // NA backbone dihedral angles
        // defineDihedralAngle("alpha", "bondO5", ""); // TODO requires phosphate
        //defineDihedralAngle("beta", "bondO5", "C5'/bond2");
        //defineDihedralAngle("gamma", "O5'", "C5'", "C4'", "C3'");
        //defineDihedralAngle("delta", "C5'", "C4'", "C3'", "C2'");
        //defineDihedralAngle("epsilon", "bondO3", "C3'/bond1");
        // TODO zeta requires next phosphate

        // ribose ring dihedral angles
        defineDihedralAngle( "nu0", "C4'", "O4'", "C1'", "C2'" );
        defineDihedralAngle( "nu1", "O4'", "C1'", "C2'", "C3'" );
        defineDihedralAngle( "nu2", "C1'", "C2'", "C3'", "C4'" );
        defineDihedralAngle( "nu3", "C2'", "C3'", "C4'", "O4'" );
        defineDihedralAngle( "nu4", "C3'", "C4'", "O4'", "C1'" );

        // Guestimated from Figure 2 of 
        // Schneider, B; Moravek, Z; and Berman, H.M. (2004)
        // "NA conformational classes"
        // Nucleic Acids Research 32(5): 1666-1677
        // setDefaultDihedralAngle("alpha", -60*Deg2Rad);
        //setDefaultDihedralAngle("beta", 180*Deg2Rad);
        //setDefaultDihedralAngle("gamma", 60*Deg2Rad);
        //setDefaultDihedralAngle("delta", 80*Deg2Rad);
        //setDefaultDihedralAngle("epsilon", 190*Deg2Rad);
        // setDefaultDihedralAngle("zeta", -70*Deg2Rad);

        // nu angles selected from resdue U52 of 1EHZ
        setDefaultDihedralAngle("nu0", 2.2*Deg2Rad);
        setDefaultDihedralAngle("nu1", -24.5*Deg2Rad);
        setDefaultDihedralAngle("nu2", 35.8*Deg2Rad);
        setDefaultDihedralAngle("nu3", -35.9*Deg2Rad); // same bond as delta, different reference atoms
        setDefaultDihedralAngle("nu4", 21.6*Deg2Rad);

        nameBondCenter("bondNext", "bondO3");
        nameBondCenter("bondPrevious", "bondO5");

        setDefaultInboardBondLength(0.16100);
    }

};


/// Phosphate group at 5' end (beginning) of NA
class SimTK_MOLMODEL_EXPORT FivePrimeNaHydroxylGroup : public BiopolymerResidue
{
public:
	explicit FivePrimeNaHydroxylGroup(String name, String threeLetterCode = "Unk", char oneLetterCode = '?') 
    : BiopolymerResidue(name, threeLetterCode, oneLetterCode)
    {
		instantiateBiotypes();
		
		setBaseAtom( UnivalentAtom("H5T", Element::getBySymbol("H")) );
		
        nameBondCenter("bondNext", "H5T/bond");

        setBiotypeIndex( "H5T", Biotype::get("Hydroxyl, RNA", "H5T", SimTK::Ordinality::Initial).getIndex() );
    }
	
	static void instantiateBiotypes() 
	{
		if ( ! Biotype::exists("Hydroxyl, RNA", "H5T", SimTK::Ordinality::Initial) )
			Biotype::defineBiotype(Element::getBySymbol("H"), 1, "Hydroxyl, RNA", "H5T", SimTK::Ordinality::Initial);
		
		// This oxygen biotype should get applied to the 5' oxygen of the nucleotide this hydroxyl attaches to
		if ( ! Biotype::exists("Hydroxyl, RNA", "O5'", SimTK::Ordinality::Initial) )
			Biotype::defineBiotype(Element::getBySymbol("O"), 2, "Hydroxyl, RNA", "O5'", SimTK::Ordinality::Initial);
	}
	
};


/// Phosphate group at 5' end (beginning) of NA
class SimTK_MOLMODEL_EXPORT FivePrimeNaPhosphateGroup : public BiopolymerResidue
{
public:
	explicit FivePrimeNaPhosphateGroup(String name, String threeLetterCode = "Unk", char oneLetterCode = '?') 
    : BiopolymerResidue(name, threeLetterCode, oneLetterCode)
    {
		instantiateBiotypes();
		
		setBaseAtom( QuadrivalentAtom("P", Element::getBySymbol("P")) );
		
        bondAtom(UnivalentAtom("OP1", Element::getBySymbol("O")), "P/bond4", 0.14800);
        bondAtom(UnivalentAtom("OP2", Element::getBySymbol("O")), "P/bond3", 0.14800);
        bondAtom(UnivalentAtom("OP3", Element::getBySymbol("O")), "P/bond2", 0.14800);
		
        nameBondCenter("bondNext", "P/bond1");

        setBiotypeIndex( "P", Biotype::get("Phosphate, RNA", "P", SimTK::Ordinality::Initial).getIndex() );
        setBiotypeIndex( "OP1", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Initial).getIndex() );
        setBiotypeIndex( "OP2", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Initial).getIndex() );
        setBiotypeIndex( "OP3", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Initial).getIndex() );
    }
	
	static void instantiateBiotypes() 
	{
		if ( ! Biotype::exists("Phosphate, RNA", "P", SimTK::Ordinality::Initial) )
			Biotype::defineBiotype(Element::getBySymbol("P"), 4, "Phosphate, RNA", "P", SimTK::Ordinality::Initial);
		if ( ! Biotype::exists("Phosphate, RNA", "OP", SimTK::Ordinality::Initial) )
			Biotype::defineBiotype(Element::getBySymbol("O"), 1, "Phosphate, RNA", "OP", SimTK::Ordinality::Initial);
	}
	
};


// Phosphodiester linkage between two NA nucleotides
class SimTK_MOLMODEL_EXPORT NaPhosphodiesterLinkage : public BiopolymerResidue
{
public:
    explicit NaPhosphodiesterLinkage(String name, String threeLetterCode = "Unk", char oneLetterCode = '?') 
        : BiopolymerResidue(name, threeLetterCode, oneLetterCode)
    {
		instantiateBiotypes();

        // TODO - set bond angles: 102.6 between ether oxygens, 119.9 between lone oxygen, 108.23 between mismatched pairs
		setBaseAtom( QuadrivalentAtom("P", Element::getBySymbol("P") 
				   ,  104.0*Deg2Rad // 03'-P-O5' from Gelbin et al
				   ,  107.9*Deg2Rad // 03'-P-OP? from Gelbin et al
				   ,  107.9*Deg2Rad // 03'-P-OP? from Gelbin et al
				   , -108.15*Deg2Rad // to make OP1-P-OP2 angle 119.6
				   ,  108.15*Deg2Rad // to make OP1-P-OP2 angle 119.6
				   ) );

        bondAtom(UnivalentAtom("OP1", Element::getBySymbol("O")), "P/bond3", 0.14800);
        bondAtom(UnivalentAtom("OP2", Element::getBySymbol("O")), "P/bond4", 0.14800);

        nameBondCenter("bondPrevious", "P/bond1");
        nameBondCenter("bondNext", "P/bond2");

        setDefaultInboardBondLength(0.16100);

        addCompoundSynonym("Phosphodiester, RNA");

		setBiotypeIndex("P", Biotype::get("Phosphodiester, RNA", "P").getIndex() );
		setBiotypeIndex("OP1", Biotype::get("Phosphodiester, RNA", "OP").getIndex() );
		setBiotypeIndex("OP2", Biotype::get("Phosphodiester, RNA", "OP").getIndex() );
    }
    
    
	static void instantiateBiotypes() 
	{
		if ( ! Biotype::exists("Phosphodiester, RNA", "P") )
			Biotype::defineBiotype(Element::getBySymbol("P"), 4, "Phosphodiester, RNA", "P");
		if ( ! Biotype::exists("Phosphodiester, RNA", "OP") )
			Biotype::defineBiotype(Element::getBySymbol("O"), 1, "Phosphodiester, RNA", "OP");
	}
};


/// Phosphate group at 3' end (beginning) of RNA
class SimTK_MOLMODEL_EXPORT ThreePrimeNaHydroxylGroup : public BiopolymerResidue
{
public:
	explicit ThreePrimeNaHydroxylGroup(String name, String threeLetterCode = "Unk", char oneLetterCode = '?') 
    : BiopolymerResidue(name, threeLetterCode, oneLetterCode)
    {
		instantiateBiotypes();
		
		setBaseAtom( UnivalentAtom("H3T", Element::getBySymbol("H")) );
		
        nameBondCenter("bondNext", "H3T/bond");

        setBiotypeIndex( "H3T", Biotype::get("Hydroxyl, RNA", "H3T", SimTK::Ordinality::Final).getIndex() );
    }
	
	static void instantiateBiotypes() 
	{
		if ( ! Biotype::exists("Hydroxyl, RNA", "H3T", SimTK::Ordinality::Final) )
			Biotype::defineBiotype(Element::getBySymbol("H"), 1, "Hydroxyl, RNA", "H3T", SimTK::Ordinality::Final);
		
		// This oxygen biotype should get applied to the 3' oxygen of the nucleotide this hydroxyl attaches to
		if ( ! Biotype::exists("Hydroxyl, RNA", "O3'", SimTK::Ordinality::Final) )
			Biotype::defineBiotype(Element::getBySymbol("O"), 2, "Hydroxyl, RNA", "O3'", SimTK::Ordinality::Final);
	}
	
};

/// Phosphate group at 3' end (end) of NA
class SimTK_MOLMODEL_EXPORT ThreePrimeNaPhosphateGroup : public BiopolymerResidue
{
public:
	explicit ThreePrimeNaPhosphateGroup(String name, String threeLetterCode = "Unk", char oneLetterCode = '?') 
    : BiopolymerResidue(name, threeLetterCode, oneLetterCode)
    {
		instantiateBiotypes();
		
		setBaseAtom( QuadrivalentAtom("P", Element::getBySymbol("P")) );
		
        bondAtom(UnivalentAtom("OP1", Element::getBySymbol("O")), "P/bond4", 0.14800);
        bondAtom(UnivalentAtom("OP2", Element::getBySymbol("O")), "P/bond3", 0.14800);
        bondAtom(UnivalentAtom("OP3", Element::getBySymbol("O")), "P/bond2", 0.14800);
		
        nameBondCenter("bondNext", "P/bond1");

        setBiotypeIndex( "P", Biotype::get("Phosphate, RNA", "P", SimTK::Ordinality::Final).getIndex() );
        setBiotypeIndex( "OP1", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Final).getIndex() );
        setBiotypeIndex( "OP2", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Final).getIndex() );
        setBiotypeIndex( "OP3", Biotype::get("Phosphate, RNA", "OP", SimTK::Ordinality::Final).getIndex() );
    }
	
	static void instantiateBiotypes() 
	{
		if ( ! Biotype::exists("Phosphate, RNA", "P", SimTK::Ordinality::Final) )
			Biotype::defineBiotype(Element::getBySymbol("P"), 4, "Phosphate, RNA", "P", SimTK::Ordinality::Final);
		if ( ! Biotype::exists("Phosphate, RNA", "OP", SimTK::Ordinality::Final) )
			Biotype::defineBiotype(Element::getBySymbol("O"), 1, "Phosphate, RNA", "OP", SimTK::Ordinality::Final);
	}
	
};





} // namespace SimTK

#endif // SimTK_MOLMODEL_NA_H_
