import unittest, os, sys
import ost
from ost import io, mol, settings, conop, seq
# check if we can import: fails if numpy or scipy not available
try:
    from ost.mol.alg import stereochemistry
except ImportError:
    print("Failed to import stereochemistry. Happens when numpy missing. " \
          "Ignoring test_stereochemistry.py tests.")
    sys.exit(0)


def _LoadFile(file_name):
    """Helper to avoid repeating input path over and over."""
    return io.LoadPDB(os.path.join('testfiles', file_name))

class TestStereochemistry(unittest.TestCase):

    def test_GetResidueType(self):
        """ internal function in stereochemistry module, nevertheless crucial...
        """
        ent = _LoadFile("10b2.pdb")

        # get some residues
        ile_a_199 = ent.FindResidue("A", mol.ResNum(199))
        self.assertEqual(ile_a_199.GetName(), "ILE")
        pro_a_200 = ent.FindResidue("A", mol.ResNum(200))
        self.assertEqual(pro_a_200.GetName(), "PRO")
        glu_a_201 = ent.FindResidue("A", mol.ResNum(201))
        self.assertEqual(glu_a_201.GetName(), "GLU")
        pro_a_202 = ent.FindResidue("A", mol.ResNum(202))
        self.assertEqual(pro_a_202.GetName(), "PRO")
        ser_a_221 = ent.FindResidue("A", mol.ResNum(221))
        self.assertEqual(ser_a_221.GetName(), "SER")
        gly_a_222 = ent.FindResidue("A", mol.ResNum(222))
        self.assertEqual(gly_a_222.GetName(), "GLY")
        arg_a_223 = ent.FindResidue("A", mol.ResNum(223))
        self.assertEqual(arg_a_223.GetName(), "ARG")
        c_b_75 = ent.FindResidue("B", mol.ResNum(75))
        self.assertEqual(c_b_75.GetName(), "C")
        a_b_76 = ent.FindResidue("B", mol.ResNum(76))
        self.assertEqual(a_b_76.GetName(), "A")

        # check PRO_CIS
        res_type = stereochemistry._GetResidueType([ile_a_199.FindAtom("C"),
                                                    pro_a_200.FindAtom("N")])
        self.assertEqual(res_type, "PRO_CIS")
        res_type = stereochemistry._GetResidueType([pro_a_200.FindAtom("N"),
                                                    ile_a_199.FindAtom("C")])
        self.assertEqual(res_type, "PRO_CIS")
        res_type = stereochemistry._GetResidueType([ile_a_199.FindAtom("O"),
                                                    ile_a_199.FindAtom("C"),
                                                    pro_a_200.FindAtom("N")])
        self.assertEqual(res_type, "PRO_CIS")
        res_type = stereochemistry._GetResidueType([pro_a_200.FindAtom("N"),
                                                    ile_a_199.FindAtom("C"),
                                                    ile_a_199.FindAtom("O")])
        self.assertEqual(res_type, "PRO_CIS")

        # check PRO_TRANS
        res_type = stereochemistry._GetResidueType([glu_a_201.FindAtom("C"),
                                                    pro_a_202.FindAtom("N")])
        self.assertEqual(res_type, "PRO_TRANS")
        res_type = stereochemistry._GetResidueType([pro_a_202.FindAtom("N"),
                                                    glu_a_201.FindAtom("C")])
        self.assertEqual(res_type, "PRO_TRANS")
        res_type = stereochemistry._GetResidueType([glu_a_201.FindAtom("O"),
                                                    glu_a_201.FindAtom("C"),
                                                    pro_a_202.FindAtom("N")])
        self.assertEqual(res_type, "PRO_TRANS")
        res_type = stereochemistry._GetResidueType([pro_a_202.FindAtom("N"),
                                                    glu_a_201.FindAtom("C"),
                                                    glu_a_201.FindAtom("O")])
        self.assertEqual(res_type, "PRO_TRANS")

        # check GLY
        res_type = stereochemistry._GetResidueType([ser_a_221.FindAtom("C"),
                                                    gly_a_222.FindAtom("N")])
        self.assertEqual(res_type, "GLY")
        res_type = stereochemistry._GetResidueType([gly_a_222.FindAtom("N"),
                                                    ser_a_221.FindAtom("C")])
        self.assertEqual(res_type, "GLY")
        res_type = stereochemistry._GetResidueType([ser_a_221.FindAtom("O"),
                                                    ser_a_221.FindAtom("C"),
                                                    gly_a_222.FindAtom("N")])
        self.assertEqual(res_type, "GLY")
        res_type = stereochemistry._GetResidueType([gly_a_222.FindAtom("N"),
                                                    ser_a_221.FindAtom("C"),
                                                    ser_a_221.FindAtom("O")])
        self.assertEqual(res_type, "GLY")

        # check PEPTIDE
        res_type = stereochemistry._GetResidueType([arg_a_223.FindAtom("C"),
                                                    gly_a_222.FindAtom("N")])
        self.assertEqual(res_type, "PEPTIDE")
        res_type = stereochemistry._GetResidueType([gly_a_222.FindAtom("N"),
                                                    arg_a_223.FindAtom("C")])
        self.assertEqual(res_type, "PEPTIDE")
        res_type = stereochemistry._GetResidueType([arg_a_223.FindAtom("O"),
                                                    arg_a_223.FindAtom("C"),
                                                    gly_a_222.FindAtom("N")])
        self.assertEqual(res_type, "PEPTIDE")
        res_type = stereochemistry._GetResidueType([gly_a_222.FindAtom("N"),
                                                    arg_a_223.FindAtom("C"),
                                                    arg_a_223.FindAtom("O")])
        self.assertEqual(res_type, "PEPTIDE")

        # check NA
        res_type = stereochemistry._GetResidueType([a_b_76.FindAtom("P"),
                                                    c_b_75.FindAtom("O3'")])
        self.assertEqual(res_type, "NA")
        res_type = stereochemistry._GetResidueType([c_b_75.FindAtom("O3'"),
                                                    a_b_76.FindAtom("P")])
        self.assertEqual(res_type, "NA")
        res_type = stereochemistry._GetResidueType([a_b_76.FindAtom("O5'"),
                                                    a_b_76.FindAtom("P"),
                                                    c_b_75.FindAtom("O3'")])
        self.assertEqual(res_type, "NA")
        res_type = stereochemistry._GetResidueType([c_b_75.FindAtom("O3'"),
                                                    a_b_76.FindAtom("P"),
                                                    a_b_76.FindAtom("O5'")])
        self.assertEqual(res_type, "NA")

    def test_FullParameters(self):
        """ Tests whether we get parameters for each bond/angle
        """
        ent = _LoadFile("10b2.pdb")
        data = stereochemistry.GetDefaultStereoData()
        link_data = stereochemistry.GetDefaultStereoLinkData()
        # do bonds
        for b in ent.bonds:
            param = stereochemistry.GetBondParam(b.first, b.second,
                                                 stereo_data=data,
                                                 stereo_link_data = link_data)
            self.assertTrue(None not in param)
        # do angles
        angles = stereochemistry._GetAngles(ent.bonds)
        for a in angles:
            param = stereochemistry.GetAngleParam(a[0], a[1], a[2],
                                                  stereo_data=data,
                                                  stereo_link_data = link_data)
            self.assertTrue(None not in param)

    def test_StereoCheck(self):
        """ Test StereoCheck

        GetClashes, GetBadBonds and GetBadAngles get tested implicitely
        """
        ent = _LoadFile("10b2.pdb")
        sterochecked_ent, clashes, bad_bonds, bad_angles = \
        stereochemistry.StereoCheck(ent)

        self.assertEqual(len(clashes), 0)
        self.assertEqual(len(bad_bonds), 0)
        self.assertEqual(len(bad_angles), 0)

        phe_A_218 = ent.FindResidue("A", mol.ResNum(218))
        c_B_75 = ent.FindResidue("B", mol.ResNum(75))

        phe_at = phe_A_218.FindAtom("CD2")
        c_at = c_B_75.FindAtom("C4'")
        ed = ent.EditXCS()
        ed.SetAtomPos(phe_at, 0.5*(phe_at.GetPos() + c_at.GetPos()))

        stereochecked_ent, clashes, bad_bonds, bad_angles = \
        stereochemistry.StereoCheck(ent)

        self.assertEqual(len(clashes), 2)
        self.assertEqual(len(bad_bonds), 2)
        self.assertEqual(len(bad_angles), 2)

        # c_B_75 gets completely removed as two backbone atoms are involved
        # in clashes. phe_A_218 has the sidechain removed
        self.assertEqual(len(ent.residues)-1,
                         len(stereochecked_ent.residues))
        c_B_75 = stereochecked_ent.FindResidue("B", mol.ResNum(75))
        self.assertFalse(c_B_75.IsValid())
        phe_A_218 = stereochecked_ent.FindResidue("A", mol.ResNum(218))
        self.assertTrue(phe_A_218.IsValid())
        phe_atoms = [a.GetName() for a in phe_A_218.atoms]
        self.assertEqual(sorted(["N", "CA", "C", "O"]),
                         sorted(phe_atoms))

        # reload and trigger the case where the nucleotide backbone remains
        ent = _LoadFile("10b2.pdb")
        c_B_75 = ent.FindResidue("B", mol.ResNum(75))
        ser_A_219 = ent.FindResidue("A", mol.ResNum(219))
        c_at = c_B_75.FindAtom("O2")
        ser_at = ser_A_219.FindAtom("O")
        ed = ent.EditXCS()
        new_pos = ser_at.GetPos() + 0.6*(c_at.GetPos()-ser_at.GetPos())
        ed.SetAtomPos(ser_at, new_pos)

        stereochecked_ent, clashes, bad_bonds, bad_angles = \
        stereochemistry.StereoCheck(ent)

        self.assertEqual(len(clashes), 1)
        self.assertEqual(len(bad_bonds), 1)
        self.assertEqual(len(bad_angles), 1)

        c_B_75 = stereochecked_ent.FindResidue("B", mol.ResNum(75))
        self.assertTrue(c_B_75.IsValid())
        ser_A_219 = stereochecked_ent.FindResidue("A", mol.ResNum(219))
        self.assertFalse(ser_A_219.IsValid())
        c_atoms = [a.GetName() for a in c_B_75.atoms]
        exp_c_atoms = ["P", "OP1", "OP2", "O5'", "C5'", "C4'", "C3'",
                       "C2'", "C1'", "O4'", "O3'", "O2'"]
        self.assertEqual(sorted(c_atoms), sorted(exp_c_atoms))

if __name__ == "__main__":
    from ost import testutils
    if testutils.DefaultCompoundLibIsSet():
        testutils.RunTests()
    else:
        print('No compound library available. Ignoring test_stereochemistry.py tests.')
