##
# File:    FormatUtils.py
# Date:    10-April-2010
#
# Updates:
# 23-April-2010  jdw Add rcsb internal cif to pdbx
# 18-April-2013  jdw update to latest RcsbDbUtility() class -
# 29-May  -2013  jdw Add pdb2pdbxDepositOp()  and pdbx2pdbxDepositOp() methods
# 15-Aug  -2013  jdw Add mtz2pdbxOp()
# 31-Dec  -2013  jdw add timeout for mtz2pdbxOp()
# 16-Jan  -2014  jdw repoint dst3 in mtz2pdbxOp() to logfile
# 26-Sep  -2014  jdw add pdbx2nmrstarAnnotOp() from annotation package
##
"""
Module of format translation utility operations supporting the call protocol of the ProcessRunner() class.

"""
__docformat__ = "restructuredtext en"
__author__ = "John Westbrook"
__email__ = "jwest@rcsb.rutgers.edu"
__license__ = "Creative Commons Attribution 3.0 Unported"
__version__ = "V0.01"

import os
import sys
import traceback
import shutil
import datetime
import time
import difflib
import string
import random

from wwpdb.utils.wf.plugins.UtilsBase import UtilsBase
from wwpdb.utils.config.ConfigInfo import ConfigInfo
from wwpdb.utils.dp.RcsbDpUtility import RcsbDpUtility
from wwpdb.utils.wf.plugins.SFConvert import SFConvert


class FormatUtils(UtilsBase):

    """ Utility class to perform file format conversions.

        Current supported operations include:
        - annot-pdbx2pdb (cif2pdb)
        - annot-pdb2pdbx (pdb2cif)
        - annot-rcsb2pdbx (cif2pdbx)
        - annot-cif2cif-dep   (pdbx2pdbx)
        - annot-pdb2cif-dep   (pdb2cif)

        Each method in this class implements the method calling interface of the
        `ProcessRunner()` class.   This interface provides the keyword arguments:

        - inputObjectD   dictionary of input objects
        - outputObjectD  dictionary of output objects
        - userParameterD  dictionary of user adjustable parameters
        - internalParameterD dictionary of internal parameters

        Each method in the class handles its own exceptions and returns
        True on success or False otherwise.

    """

    def __init__(self, verbose=False, log=sys.stderr):
        super(FormatUtils, self).__init__(verbose, log)
        self.__cleanUp = True
        """Flag to remove any temporary directories created by this class.
        """
        #

    def pdbx2pdbOp(self, **kwArgs):
        """Performs PDBx(cif) to PDB format conversion operation.
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            pdbxPath = inpObjD["src"].getFilePathReference()
            pdbPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(pdbxPath)
            dp.op("annot-cif2pdb")
            dp.exp(pdbPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.pdbx2pdbOp() - PDB  file path: %s\n" % pdbPath)
                self._lfh.write("+FormatUtils.pdbx2pdbOp() - PDBx file path: %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def pdb2pdbxOp(self, **kwArgs):
        """Perform PDB to PDBx(cif) format conversion operation.
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            pdbPath = inpObjD["src"].getFilePathReference()
            pdbxPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(pdbPath)
            dp.op("annot-pdb2cif")
            dp.exp(pdbxPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.pdb2pdbxOp() - PDB  file path: %s\n" % pdbPath)
                self._lfh.write("+FormatUtils.pdb2pdbxOp() - PDBx file path: %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def pdb2pdbxDepositOp(self, **kwArgs):
        """Perform PDB to PDBx(cif) format conversion operation (special processing for deposition sessions)
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            pdbPath = inpObjD["src"].getFilePathReference()
            pdbxPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(pdbPath)
            dp.op("annot-pdb2cif-dep")
            dp.exp(pdbxPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.pdb2pdbxDepositOp() - PDB  file path: %s\n" % pdbPath)
                self._lfh.write("+FormatUtils.pdb2pdbxDepositOp() - PDBx file path: %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def rcsb2pdbxOp(self, **kwArgs):
        """Perform RCSB(cif) to PDBx(cif) format conversion operation.
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            rcsbPath = inpObjD["src"].getFilePathReference()
            pdbxPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(rcsbPath)
            dp.op("annot-cif2cif")
            dp.exp(pdbxPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.cif2cifOp() - RCSB cif file path: %s\n" % rcsbPath)
                self._lfh.write("+FormatUtils.cif2cifOp() - PDBx file path: %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def pdbx2pdbxDepositOp(self, **kwArgs):
        """Perform  PDBx(cif) to PDBx(cif) format conversion operation (special processing deposition sessions)
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            rcsbPath = inpObjD["src"].getFilePathReference()
            pdbxPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(rcsbPath)
            dp.op("annot-cif2cif-dep")
            dp.exp(pdbxPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.pdbx2pdbxDepositOp() - PDBx input  file path: %s\n" % rcsbPath)
                self._lfh.write("+FormatUtils.pdbx2pdbxDepositOp() - PDBx output file path: %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def mtz2pdbxOp(self, **kwArgs):
        """Perform  MTZ or other SF file  to  PDBx(cif) format conversion operation.
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            sfPath = inpObjD["src1"].getFilePathReference()
            sfFmt = inpObjD["src1"].getFileFormat()
            xyzPath = inpObjD["src2"].getFilePathReference()
            #
            #
            sfPdbxFilePath = outObjD["dst1"].getFilePathReference()
            sfDiagFilePath = outObjD["dst2"].getFilePathReference()
            logFilePath = outObjD["dst3"].getFilePathReference()
            #
            dirPath = outObjD["dst1"].getDirPathReference()
            #
            # logPath=os.path.join(dirPath,"mtz2pdbx.log")
            dmpPath = os.path.join(dirPath, "mtzdmp.log")
            #
            timeout = int(uD['timeout'])
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")

            if sfFmt == "pdbx":
                # See if we can handle specially
                sfc = SFConvert()
                if sfc.isSpecialSF(sfPath):
                    ok = sfc.handleSpecialSF(sfPath, sfPdbxFilePath, xyzPath, sfDiagFilePath, logFilePath)
                    self._lfh.write("Special handling of SF file returns %s\n" % ok)
                    if ok is True:
                        return True
                    # Else fall through

            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(sfPath)
            if ((xyzPath is not None) and os.path.exists(xyzPath)):
                dp.addInput(name="xyz_file_path", value=xyzPath, type='file')
            if timeout > 0:
                dp.setTimeout(timeout)
            dp.op("annot-sf-convert")
            dp.expLog(logFilePath)
            dp.expList(dstPathList=[sfPdbxFilePath, sfDiagFilePath, dmpPath])

            #
            myStatus = self.__checkMergeStatus(logFilePath)
            outObjD['dst4'].setValue(myStatus)

            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.mtz2pdbxOp() - timeout :      %d\n" % timeout)
                self._lfh.write("+FormatUtils.mtz2pdbxOp() - SF  input  file path:      %s\n" % sfPath)
                self._lfh.write("+FormatUtils.mtz2pdbxOp() - XYZ input  file path:      %s\n" % xyzPath)
                self._lfh.write("+FormatUtils.mtz2pdbxOp() - PDBx SF output file path:  %s\n" % sfPdbxFilePath)
                self._lfh.write("+FormatUtils.mtz2pdbxOp() - Log file path:             %s\n" % logFilePath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def nmrstar2pdbxOp(self, **kwArgs):
        """Perform NMRSTAR to PDBx(cif) format conversion operation.
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            strPath = inpObjD["src"].getFilePathReference()
            pdbxPath = outObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            dId = uD['data_set_id']
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(strPath)
            dp.addInput(name="data_set_id", value=dId)
            dp.op("annot-nmrstar2pdbx")
            dp.exp(pdbxPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.nmrstar2pdbxOp() - NMRSTAR  file path: %s\n" % strPath)
                self._lfh.write("+FormatUtils.nmrstar2pdbxOp() - PDBx file path:     %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def pdbx2nmrstarAnnotOp(self, **kwArgs):
        """Perform PDBx(cif) to NMRSTAR format conversion operation.  (using annotation package stack)
        """
        try:
            (inpObjD, outObjD, uD, pD) = self._getArgs(kwArgs)
            pdbxPath = outObjD["src"].getFilePathReference()
            strPath = inpObjD["dst"].getFilePathReference()
            dirPath = outObjD["dst"].getDirPathReference()
            #
            dId = uD['data_set_id']
            cI = ConfigInfo()
            siteId = cI.get("SITE_PREFIX")
            dp = RcsbDpUtility(tmpPath=dirPath, siteId=siteId, verbose=self._verbose, log=self._lfh)
            dp.imp(pdbxPath)
            dp.addInput(name="pdb_id", value=dId)
            dp.op("annot-pdbx2nmrstar")
            dp.exp(strPath)
            if (self.__cleanUp):
                dp.cleanup()
            if (self._verbose):
                self._lfh.write("+FormatUtils.pdbx2nmrstarAnnotOp() - NMRSTAR  file path: %s\n" % strPath)
                self._lfh.write("+FormatUtils.pdbx2nmrstarAnnotOp() - PDBx file path:     %s\n" % pdbxPath)
            return True
        except:
            traceback.print_exc(file=self._lfh)
            return False

    def __checkMergeStatus(self, logFilePath):
        status = 'ok'
        if os.access(logFilePath, os.R_OK):
            ifh = open(logFilePath, 'r')
            for line in ifh:
                if str(line).upper().startswith("++ERROR") or str(line).upper().startswith("ERROR:"):
                    return 'error'
                if str(line).upper().startswith("++WARN") or str(line).upper().startswith("WARN:"):
                    return 'warn'
            ifh.close()
        else:
            return 'error'
        return status
