#!/usr/bin/env python
# (C) 2017 OpenEye Scientific Software Inc. All rights reserved.
#
# TERMS FOR USE OF SAMPLE CODE The software below ("Sample Code") is
# provided to current licensees or subscribers of OpenEye products or
# SaaS offerings (each a "Customer").
# Customer is hereby permitted to use, copy, and modify the Sample Code,
# subject to these terms. OpenEye claims no rights to Customer's
# modifications. Modification of Sample Code is at Customer's sole and
# exclusive risk. Sample Code may require Customer to have a then
# current license or subscription to the applicable OpenEye offering.
# THE SAMPLE CODE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED.  OPENEYE DISCLAIMS ALL WARRANTIES, INCLUDING, BUT
# NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. In no event shall OpenEye be
# liable for any damages or liability in connection with the Sample Code
# or its use.

# @ <SNIPPET-SETUP>
import sys
from openeye import oechem
from openeye import oeszmap
# @ </SNIPPET-SETUP>

InterfaceData = """
!BRIEF SzmapCalcs.py [-prob #.#] [-p] <molfile> [-l] <molfile> [-o] <molfile>
!PARAMETER -p
  !TYPE string
  !BRIEF Input protein (or other) context mol
  !REQUIRED true
  !KEYLESS 1
!END
!PARAMETER -l
  !TYPE string
  !BRIEF Input ligand coordinates for calculations
  !REQUIRED true
  !KEYLESS 2
!END
!PARAMETER -o
  !TYPE string
  !BRIEF Output file for points and probes molecules
  !REQUIRED true
  !KEYLESS 3
!END
!PARAMETER -prob
  !TYPE double
  !DEFAULT 0.5
  !BRIEF Cutoff for cumulative probability of probes
  !REQUIRED false
!END
"""


def RunSzmap(lig, prot):
    """
    run szmap at ligand coordinates in the protein context
    @rtype : None
    @param lig: mol defining coordinates for szmap calcs
    @param prot: context mol for szmap calcs (must have charges and radii)
    """

    # @ <SNIPPET-CREATE-SZMAPENGINEOPTIONS-OBJECT>
    opt = oeszmap.OESzmapEngineOptions()
    # @ </SNIPPET-CREATE-SZMAPENGINEOPTIONS-OBJECT>

    # @ <SNIPPET-GETPROBEMOL>
    mol = oechem.OEGraphMol()
    opt.GetProbeMol(mol)
    # @ </SNIPPET-GETPROBEMOL>
    print("probe mol smiles: %s" % oechem.OEMolToSmiles(mol))

    # @ <SNIPPET-SETPROBE>
    opt.SetProbe(360)
    # @ </SNIPPET-SETPROBE>
    print("norient = %d" % opt.NumOrientations())

    # @ <SNIPPET-COMBINE-OPTIONS>
    opt.SetProbe(24).SetMaskCutoff(999.99)
    # @ </SNIPPET-COMBINE-OPTIONS>
    print("norient = %d" % opt.NumOrientations())
    print("cutoff  = %.3f" % opt.GetMaskCutoff())

    opt.SetMaskCutoff(0.0)  # reset cutoff
    print("cutoff  = %.3f" % opt.GetMaskCutoff())

    # @ <SNIPPET-GET-PROBE>
    p = opt.GetProbe()
    print("probe nconf = %d" % p.NumConfs())
    # @ </SNIPPET-GET-PROBE>

    # @ <SNIPPET-CREATE-SZMAPENGINE-OBJECT>
    sz = oeszmap.OESzmapEngine(prot, opt)
    # @ </SNIPPET-CREATE-SZMAPENGINE-OBJECT>

    # @ <SNIPPET-GET-OPTIONS>
    opt = sz.GetOptions()
    print("norient= %d" % opt.NumOrientations())
    print("name   = %s" % opt.GetProbeName())
    print("cutoff = %.3f" % opt.GetMaskCutoff())
    # @ </SNIPPET-GET-OPTIONS>

    # @ <SNIPPET-GET-CONTEXT>
    mol = sz.GetContext()
    print("context mol: %s" % mol.GetTitle())
    # @ </SNIPPET-GET-CONTEXT>

    print("probe smiles: %s" % oechem.OEMolToSmiles(opt.GetProbe()))

    # @ <SNIPPET-CREATE-COORDSARRAY-OBJECT>
    coord = oechem.OEFloatArray(3)
    # @ </SNIPPET-CREATE-COORDSARRAY-OBJECT>

    # @ <SNIPPET-CREATE-SZMAPRESULTS-OBJECT>
    rslt = oeszmap.OESzmapResults()
    # @ </SNIPPET-CREATE-SZMAPRESULTS-OBJECT>

    for atom in lig.GetAtoms():
        lig.GetCoords(atom, coord)

        # @ <SNIPPET-GET-ENSEMBLE-NAME>
        name = oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_NeutralDiffDeltaG)
        # @ </SNIPPET-GET-ENSEMBLE-NAME>
        print("ensemble name: %s" % name)

        # @ <SNIPPET-GET-LONG-ENSEMBLE-NAME>
        longName = True
        name = oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_NeutralDiffDeltaG, longName)
        # @ </SNIPPET-GET-LONG-ENSEMBLE-NAME>
        # @ <SNIPPET-CALC-SZMAP-VALUE>
        nddg = oeszmap.OECalcSzmapValue(sz, coord)
        # @ </SNIPPET-CALC-SZMAP-VALUE>
        print("ensemble name: %s, value: %.3f" % (name, nddg))

        # @ <SNIPPET-GET-COMPONENT-NAME>
        name = oeszmap.OEGetComponentName(oeszmap.OEComponent_Interaction)
        # @ </SNIPPET-GET-COMPONENT-NAME>
        print("component name: %s" % name)

        # @ <SNIPPET-CLASH-RSLTS-EVAL>
        if not oeszmap.OEIsClashing(sz, coord):
            oeszmap.OECalcSzmapResults(rslt, sz, coord)
            nddg = rslt.GetEnsembleValue(oeszmap.OEEnsemble_NeutralDiffDeltaG)
            # @ </SNIPPET-CLASH-RSLTS-EVAL>

        # @ <SNIPPET-CALC-SZMAP-RESULTS>
        if not oeszmap.OEIsClashing(sz, coord):
            oeszmap.OECalcSzmapResults(rslt, sz, coord)
            # ...
            # @ </SNIPPET-CALC-SZMAP-RESULTS>

            print("result norient = %d" % rslt.NumOrientations())

            # @ <SNIPPET-GET-ENSEMBLE-VALUE>
            nddg = rslt.GetEnsembleValue(oeszmap.OEEnsemble_NeutralDiffDeltaG)
            # @ </SNIPPET-GET-ENSEMBLE-VALUE>
            print("nddG = %.3f" % nddg)
            print("vdw  = %.3f" % rslt.GetEnsembleValue(oeszmap.OEEnsemble_VDW))

            # @ <SNIPPET-GET-COMPONENT>
            coulomb = rslt.GetComponent(oeszmap.OEComponent_Interaction)
            print("interaction:")
            print(" ".join("%.3f" % c for c in coulomb))
            # @ </SNIPPET-GET-COMPONENT>

            # @ <SNIPPET-GET-ORDER>
            order = rslt.GetProbabilityOrder()
            print("conf with greatest prob = %d" % order[0])
            # @ </SNIPPET-GET-ORDER>

            # @ <SNIPPET-GET-PROBABILITIES>
            prob = rslt.GetProbabilities()
            print("greatest prob = %.3f" % prob[order[0]])
            # @ </SNIPPET-GET-PROBABILITIES>

            # @ <SNIPPET-GET-ORIENTATION-COMPONENT>
            vdw = oechem.OEDoubleArray(rslt.NumOrientations())
            rslt.GetComponent(vdw, oeszmap.OEComponent_VDW)
            print("vdw greatest prob = %.3f" % vdw[order[0]])
            # @ </SNIPPET-GET-ORIENTATION-COMPONENT>

            # @ <SNIPPET-GET-COORDS>
            point = oechem.OEFloatArray(3)
            rslt.GetCoords(point)
            # @ </SNIPPET-GET-COORDS>
            print("calc point: (%.3f, %.3f, %.3f)" % (point[0], point[1], point[2]))

            # @ <SNIPPET-PLACE-PROBE-SET-ALL>
            mcmol = oechem.OEMol()
            rslt.PlaceProbeSet(mcmol)
            # @ </SNIPPET-PLACE-PROBE-SET-ALL>

            # @ <SNIPPET-PLACE-PROBE-SET-PROB>
            probCutoff = 0.5
            rslt.PlaceProbeSet(mcmol, probCutoff)
            print("nconf to yield 50pct = %d" % mcmol.NumConfs())
            # @ </SNIPPET-PLACE-PROBE-SET-PROB>

            # @ <SNIPPET-PLACE-PROBE-SET-COUNT>
            clear = False
            cumulativeProb = rslt.PlaceProbeSet(mcmol, 10, clear)
            print("best 10 cumulative prob = %.3f" % cumulativeProb)
            # @ </SNIPPET-PLACE-PROBE-SET-COUNT>

            # @ <SNIPPET-PLACE-NEW-ATOM>
            amol = oechem.OEGraphMol()
            atom = rslt.PlaceNewAtom(amol)

            print("vdw = %s" % atom.GetData("vdw"))
            # @ </SNIPPET-PLACE-NEW-ATOM>

            # @ <SNIPPET-PLACE-PROBE-MOL>
            pmol = oechem.OEGraphMol()
            rslt.PlaceProbeMol(pmol, order[0])
            # @ </SNIPPET-PLACE-PROBE-MOL>


# @ <SNIPPET-GET-SZMAP-ENERGIES>
def GetSzmapEnergies(lig, prot):
    """
    run szmap at ligand coordinates in the protein context
    @rtype : None
    @param lig: mol defining coordinates for szmap calcs
    @param prot: context mol for szmap calcs (must have charges and radii)
    """

    print("num\tatom\t%s\t%s\t%s\t%s\t%s"
          % (oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_NeutralDiffDeltaG),
             oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_PSolv),
             oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_WSolv),
             oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_VDW),
             oeszmap.OEGetEnsembleName(oeszmap.OEEnsemble_OrderParam)))

    coord = oechem.OEFloatArray(3)

    sz = oeszmap.OESzmapEngine(prot)
    rslt = oeszmap.OESzmapResults()

    for i, atom in enumerate(lig.GetAtoms()):
        lig.GetCoords(atom, coord)

        if not oeszmap.OEIsClashing(sz, coord):
            oeszmap.OECalcSzmapResults(rslt, sz, coord)

            print("%2d\t%s\t%.3f\t%.3f\t%.3f\t%.3f\t%.3f"
                  % (i, atom.GetName(),
                     rslt.GetEnsembleValue(oeszmap.OEEnsemble_NeutralDiffDeltaG),
                     rslt.GetEnsembleValue(oeszmap.OEEnsemble_PSolv),
                     rslt.GetEnsembleValue(oeszmap.OEEnsemble_WSolv),
                     rslt.GetEnsembleValue(oeszmap.OEEnsemble_VDW),
                     rslt.GetEnsembleValue(oeszmap.OEEnsemble_OrderParam)))
        else:
            print("%2d\t%s CLASH" % (i, atom.GetName()))
# @ </SNIPPET-GET-SZMAP-ENERGIES>


# @ <SNIPPET-GENERATE-SZMAP-PROBES>
def GenerateSzmapProbes(oms, cumulativeProb, lig, prot):
    """
    generate multiconf probes and data-rich points at ligand coords
    @rtype : None
    @param oms: output mol stream for points and probes
    @param cumulativeProb: cumulative probability for cutoff of point set
    @param lig: mol defining coordinates for szmap calcs
    @param prot: context mol for szmap calcs (must have charges and radii)
    """

    coord = oechem.OEFloatArray(3)

    sz = oeszmap.OESzmapEngine(prot)
    rslt = oeszmap.OESzmapResults()

    points = oechem.OEGraphMol()
    points.SetTitle("points %s" % lig.GetTitle())

    probes = oechem.OEMol()

    for i, atom in enumerate(lig.GetAtoms()):
        lig.GetCoords(atom, coord)

        if not oeszmap.OEIsClashing(sz, coord):
            oeszmap.OECalcSzmapResults(rslt, sz, coord)

            rslt.PlaceNewAtom(points)

            clear = False
            rslt.PlaceProbeSet(probes, cumulativeProb, clear)

    oechem.OEWriteMolecule(oms, points)
    oechem.OEWriteMolecule(oms, probes)
# @ </SNIPPET-GENERATE-SZMAP-PROBES>


def main(argv=(__name__)):
    """
    the protein should have charges and radii but the ligand need not
    """
    itf = oechem.OEInterface()

    if not oechem.OEConfigure(itf, InterfaceData):
        oechem.OEThrow.Fatal("Problem configuring OEInterface!")

    if not oechem.OEParseCommandLine(itf, argv):
        oechem.OEThrow.Fatal("Unable to parse command line")

    ligFile = itf.GetString("-l")
    ims = oechem.oemolistream()
    if not ims.open(ligFile):
        oechem.OEThrow.Fatal("Unable to open %s for reading" % ligFile)
    lig = oechem.OEGraphMol()
    oechem.OEReadMolecule(ims, lig)

    contextFile = itf.GetString("-p")
    ims = oechem.oemolistream()
    if not ims.open(contextFile):
        oechem.OEThrow.Fatal("Unable to open %s for reading" % contextFile)
    prot = oechem.OEGraphMol()
    oechem.OEReadMolecule(ims, prot)

    RunSzmap(lig, prot)

    GetSzmapEnergies(lig, prot)

    outputFile = itf.GetString("-o")
    oms = oechem.oemolostream()
    if not oms.open(outputFile):
        oechem.OEThrow.Fatal("Unable to open %s for writing" % outputFile)

    GenerateSzmapProbes(oms, itf.GetDouble("-prob"), lig, prot)


if __name__ == "__main__":
    sys.exit(main(sys.argv))
