#! /usr/bin/env python

# Imports
import os
import re
import shlex
import string
import subprocess
import sys

############################################################
# Run a command, given as a single string argument.  Print the command, run it
# and wait for the process to end.  The **kwargs argument can be used to pass
# any keyword arguments that are appropriate for the subprocess.Popen
# constructor.  If the command returns with a non-zero code, print the code and
# exit.
############################################################
def runCommand(cmd, **kwargs):
    print cmd
    args = shlex.split(cmd)
    p = subprocess.Popen(args, **kwargs)
    returncode = p.wait()
    if returncode:
        print "Command '%s' exited with code %s" % (args[0], str(returncode))
        sys.exit(returncode)

############################################################
# PyTrilinos Documentation Builder class
############################################################
class PyTrilinosDocBuilder:

    # Constructor
    def __init__(self):
        self.__docStringsUpdated  = False
        self.__buildDirConfigured = False
        self.__pyTrilinosBuilt    = False
        self.__docsGenerated      = False
        self.__devGuideBuilt      = False
        self.wrapperTargets       = None
        self._getTrilinosPathNames()
        self._getTrilinosPackages()

    # Get the Trilinos source directory
    def _getTrilinosPathNames(self):
        # Find the Trilinos source directory based upon the directory of this script
        head, build_docs        = os.path.split(os.path.abspath(__file__))
        self.pyTrilinosDocDir   = head
        self.pyTrilinosDoxDir   = os.path.join(self.pyTrilinosDocDir, "Doxygen")
        self.pyTrilinosDevDir   = os.path.join(self.pyTrilinosDocDir, "DevelopersGuide")
        head, doc               = os.path.split(head)
        head, pytrilinos        = os.path.split(head)
        head, packages          = os.path.split(head)
        self.trilinosSrcDir     = head
        self.trilinosBuildDir   = os.path.join(self.trilinosSrcDir, "DOC_BUILD")
        self.pyTrilinosBuildDir = os.path.join(self.trilinosBuildDir,
                                               "packages", "PyTrilinos", "src")
        self.pyTrilinosPkgDir   = os.path.join(self.pyTrilinosBuildDir,
                                               "PyTrilinos")

    # Get the Trilinos package names that have PyTrilinos wrappers by reading
    # PyTrilinos/cmake/Dependencies.cmake
    def _getTrilinosPackages(self):
        packagesRE = re.compile(r"SET\(LIB_OPTIONAL_DEP_PACKAGES([^)]*)\)", re.MULTILINE)
        dependencies = os.path.join(self.trilinosSrcDir, "packages",
                                    "PyTrilinos", "cmake", "Dependencies.cmake")
        data = open(dependencies,'r').read()
        match = re.search(packagesRE, data)
        if (match):
            self.trilinosPackages = string.split(match.group(1))

    # Update the PyTrilinos doc strings
    def updateDocStrings(self):
        print "\n*** Updating Docstrings ***\n"
        runCommand('make depend', cwd=self.pyTrilinosDoxDir)
        runCommand('make',        cwd=self.pyTrilinosDoxDir)
        self.__docStringsUpdated = True

    # Return PyTrilinos docstrings status
    def docStringsUpdated(self):
        return self.__docStringsUpdated

    # Configure the build directory.  First create the build directory, if it
    # does not exist.  Then remove the CMakeCache.txt file, if it exists.  Run
    # cmake to configure the build.
    def configureBuildDirectory(self):
        if not os.path.exists(self.trilinosBuildDir):
            print "Creating", self.trilinosBuildDir
            os.mkdir(self.trilinosBuildDir)
        else:
            if not os.path.isdir(self.trilinosBuildDir):
                raise OSError, "File " + self.trilinosBuildDir + \
                      " already exists and is not a directory"
        cacheFile = os.path.join(self.trilinosBuildDir, "CMakeCache.txt")
        if os.path.exists(cacheFile):
            print "Removing", cacheFile
            os.remove(cacheFile)
        do_configure = "cmake " + \
                       "-D TPL_ENABLE_MPI:BOOL=ON " + \
                       "-D BUILD_SHARED_LIBS:BOOL=ON " + \
                       "-D Trilinos_ENABLE_Fortran:BOOL=OFF " + \
                       "-D Trilinos_ENABLE_ALL_PACKAGES:BOOL=OFF " + \
                       "-D Trilinos_ENABLE_ALL_OPTIONAL_PACKAGES:BOOL=OFF " + \
                       "-D Trilinos_ENABLE_TESTS:BOOL=OFF " + \
                       "-D Trilinos_ENABLE_EXAMPLES:BOOL=OFF "
        for package in self.trilinosPackages:
            do_configure += "-D Trilinos_ENABLE_" + package + ":BOOL=ON "
        do_configure += "-D Trilinos_ENABLE_PyTrilinos:BOOL=ON .."
        print "\n*** Configuring Build Directory ***\n"
        runCommand(do_configure, cwd=self.trilinosBuildDir)
        self.__buildDirConfigured = True

    # Return the build directory status
    def buildDirConfigured(self):
        return self.__buildDirConfigured

    # Get a list of Makefile targets corresponding to PyTrilinos wrappers
    def getWrapperTargets(self):
        if not self.__buildDirConfigured:
            self.configureBuildDirectory()
        p1 = subprocess.Popen(["make", "help"], cwd=self.pyTrilinosBuildDir,
                              stdout=subprocess.PIPE)
        # Note: I would much rather grep on "PYTHON_wrap.cpp", but the CMake
        # Makefiles are too convoluted to allow me to do that.
        p2 = subprocess.Popen(["grep", "PYTHON_wrap.o"], stdin=p1.stdout,
                              stdout=subprocess.PIPE)
        self.wrapperTargets = p2.communicate()[0].split()
        dots = '...'
        while dots in self.wrapperTargets:
            self.wrapperTargets.remove(dots)

    # Build a skeletal PyTrilinos, suitable for extracting documentation from
    def buildPyTrilinos(self):
        # Update the PyTrilinos doc strings
        if not self.__docStringsUpdated:
            self.updateDocStrings()
        # Configure the Trilinos build directory
        if not self.__buildDirConfigured:
            self.configureBuildDirectory()
        # Get the wrapper targets
        self.getWrapperTargets()
        # Build each of the wrappers
        print "\n*** Building PyTrilinos ***\n"
        for target in self.wrapperTargets:
            runCommand('make ' + target, cwd=self.pyTrilinosBuildDir)
        # Finish building the package
        runCommand('make', cwd=self.pyTrilinosPkgDir)
        self.__pyTrilinosBuilt = True

    # Return PyTrilinos build status
    def pyTrilinosBuilt(self):
        return self.__pyTrilinosBuilt

    # Generate the PyTrilinos documentation
    def generateDocs(self):
        if not self.__pyTrilinosBuilt:
            self.buildPyTrilinos()
        print "\n*** Generating PyTrilinos Documentation ***\n"
        runCommand('doxygen DoxyfileWeb', cwd=self.pyTrilinosDocDir)
        self.__docsGenerated = True

    # Return PyTrilinos documentation status
    def docsGenerated(self):
        return self.__docsGenerated

    # Build the Developers Guide
    def buildDevelopersGuide(self):
        print "\n*** Building PyTrilinos Developers Guide ***\n"
        runCommand('make', cwd=self.pyTrilinosDevDir)
        self.__devGuideBuilt = True

    # Return PyTrilinos Developers Guide status
    def developersGuideBuilt(self):
        return self.__devGuideBuilt

    # String output
    def __str__(self):
        docStringQualifier = ""
        if not self.__docStringsUpdated: docStringQualifier = "not "
        if self.__pyTrilinosBuilt:
            buildDirStatus = "built"
        else:
            if self.__buildDirConfigured:
                buildDirStatus = "configured"
            else:
                buildDirStatus = "not configured"
        documentationQualifier = ""
        if not self.__docsGenerated: documentationQualifier = "not "
        devGuideQualifier = ""
        if not self.__devGuideBuilt: devGuideQualifier = "not "
        result = "PyTrilinos Doc Builder:\n\n" + \
                 "  Trilinos source directory: %s\n" + \
                 "  PyTrilinos packages:\n    %s\n" + \
                 "  PyTrilinos doc directory: %s\n" + \
                 "  PyTrilinos docstring directory: %s\n" + \
                 "  PyTrilinos Developers Guide directory: %s\n" + \
                 "  Trilinos build directory: %s\n" + \
                 "  PyTrilinos build directory: %s\n\n" + \
                 "  Docstring status: %supdated\n" + \
                 "  Build directory status: %s\n" + \
                 "  Documentation status: %sgenerated\n" + \
                 "  Developers Guide status: %sbuilt"
        result = result % (self.trilinosSrcDir,
                           ",".join(self.trilinosPackages),
                           self.pyTrilinosDocDir,
                           self.pyTrilinosDoxDir,
                           self.pyTrilinosDevDir,
                           self.trilinosBuildDir,
                           self.pyTrilinosBuildDir,
                           docStringQualifier,
                           buildDirStatus,
                           documentationQualifier,
                           devGuideQualifier)
        return result

############################################################
# Main routine
############################################################
def main():
    builder = PyTrilinosDocBuilder()
    builder.generateDocs()
    builder.buildDevelopersGuide()
    print
    print builder

if __name__ == "__main__":
    main()
