#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""configure

Usage: configure <config_file> (--production | --development)


Options:

    config_file   A config file with all the information for compiling.
                  Example config_files are given in config/

   --production   You can only compile **all** the modules with this flag,
                  but it will compile lighting fast.

   --development  this will create a build.ninja for each directory which 
                  contains a binary. In a second step you may compile them
                  individually if you like.


Examples:

    ./configure config/gfortran.cfg --production
    ./configure config/ifort.cfg    --development


"""

OK="✓"
FAIL="✗"
import subprocess
import os
import sys

from os.path import join

if not any(i in ["--production", "--development"] for i in sys.argv):
    sys.argv += ["--development"]

if len(sys.argv) != 3:
    print __doc__
    sys.exit()

#  __                           _
# /__ |   _  |_   _. |   o ._ _|_ _
# \_| |_ (_) |_) (_| |   | | | | (_)
#

try:
  QP_ROOT = os.environ["QP_ROOT"]
except KeyError:
  QP_ROOT = os.getcwd()
  os.environ["QP_ROOT"] = QP_ROOT

QP_ROOT_BIN = join(QP_ROOT, "bin")
QP_ROOT_LIB = join(QP_ROOT, "lib")
QP_ROOT_INSTALL = join(QP_ROOT, "install")

os.environ["PATH"] = os.environ["PATH"] + ":" + QP_ROOT_BIN

d_dependency = {
    "ocaml": ["m4", "curl", "zlib", "patch", "gcc", "zeromq"],
    "m4": ["make"],
    "curl": ["make"],
    "zlib": ["gcc", "make"],
    "patch": ["make"],
    "ezfio": ["irpf90"],
    "irpf90": ["python"],
    "docopt": ["python"],
    "resultsFile": ["python"],
    "emsl": ["python"],
    "gcc": [],
    "g++": [],
    "zeromq" : [ "g++" ],
    "f77zmq" : [ "zeromq", "python" ],
    "python": [],
    "ninja": ["g++", "python"],
    "make": [],
    "p_graphviz": ["python"],
    "bats": []
}

from collections import namedtuple

Info = namedtuple("Info", ["url", "description", "default_path"])

path_github = {"head": "http://github.com", "tail": "archive/master.tar.gz"}

ocaml = Info(
    url='http://raw.github.com/ocaml/opam/master/shell/opam_installer.sh',
    description=' Ocaml, Opam and the Core library (it will take some time roughly 20min)',
    default_path=join(QP_ROOT_BIN, "opam"))

m4 = Info(
    url="http://ftp.gnu.org/gnu/m4/m4-latest.tar.gz",
    description=" m4",
    default_path=join(QP_ROOT_BIN, "m4"))

curl = Info(
    url="http://qmcchem.ups-tlse.fr/files/scemama/curl-7.30.0.ermine.tar.bz2",
    description=" curl",
    default_path=join(QP_ROOT_BIN, "curl"))

zlib = Info(
    url='http://zlib.net/zlib-1.2.8.tar.gz',
    description=' zlib',
    default_path=join(QP_ROOT_LIB, "libz.a"))

patch = Info(
    url='ftp://ftp.gnu.org/gnu/patch/patch-2.7.5.tar.gz',
    description=' patch',
    default_path=join(QP_ROOT_BIN, "patch"))

irpf90 = Info(
    url='{head}/LCPQ/irpf90/{tail}'.format(**path_github),
    description=' IRPF90',
    default_path=join(QP_ROOT_BIN, "irpf90"))

docopt = Info(
    url='{head}/docopt/docopt/{tail}'.format(**path_github),
    description=' docopt',
    default_path=join(QP_ROOT_INSTALL, "docopt"))

resultsFile = Info(
    url='{head}/LCPQ/resultsFile/{tail}'.format(**path_github),
    description=' resultsFile',
    default_path=join(QP_ROOT_INSTALL, "resultsFile"))

ninja = Info(
    url='{head}/martine/ninja/{tail}'.format(**path_github),
    description=' ninja',
    default_path=join(QP_ROOT_BIN, "ninja"))

emsl = Info(
    url='{head}/LCPQ/EMSL_Basis_Set_Exchange_Local/{tail}'.format(**path_github),
    description=' EMSL basis set library',
    default_path=join(QP_ROOT_INSTALL, "emsl"))

ezfio = Info(
    url='{head}/LCPQ/EZFIO/{tail}'.format(**path_github),
    description=' EZFIO',
    default_path=join(QP_ROOT_INSTALL, "EZFIO"))

zeromq = Info(
    url='https://github.com/zeromq/zeromq4-1/releases/download/v4.1.5/zeromq-4.1.5.tar.gz',
    description=' ZeroMQ',
    default_path=join(QP_ROOT_LIB, "libzmq.a"))

f77zmq = Info(
    url='{head}/zeromq/f77_zmq/{tail}'.format(**path_github),
    description=' F77-ZeroMQ',
    default_path=join(QP_ROOT_LIB, "libf77zmq.a") )
#                join(QP_ROOT, "src", "ZMQ", "f77zmq.h") )

p_graphviz = Info(
    url='https://github.com/xflr6/graphviz/archive/master.tar.gz',
    description=' Python library for graphviz',
    default_path=join(QP_ROOT_INSTALL, "p_graphviz"))

bats = Info(
    url='https://github.com/sstephenson/bats/archive/master.tar.gz',
    description=' Bash Automated Testing System',
    default_path=join(QP_ROOT_INSTALL, "bats"))

d_info = dict()

for m in ["ocaml", "m4", "curl", "zlib", "patch", "irpf90", "docopt",
          "resultsFile", "ninja", "emsl", "ezfio", "p_graphviz",
          "zeromq", "f77zmq","bats" ]:
    exec ("d_info['{0}']={0}".format(m))


def find_path(bin_, l_installed, var_for_qp_root=False):
    """Use the global variable
    * l_installed
    * d_info
    """

    try:
        locate = l_installed[bin_]
    except KeyError:
        locate = d_info[bin_].default_path

    if var_for_qp_root:
        locate = locate.replace(QP_ROOT, "${QP_ROOT}")

    return locate


#  _
# |_    ._   _ _|_ o  _  ._
# | |_| | | (_  |_ | (_) | |
#
def check_output(*popenargs, **kwargs):
    """Run command with arguments and return its output as a string.

    Backported from Python 2.7 as it's implemented as pure python on stdlib.

    >>> check_output(['/usr/bin/python', '--version'])
    Python 2.6.2
    """
    process = subprocess.Popen(stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE, *popenargs, **kwargs)
    output, unused_err = process.communicate()
    retcode = process.poll()
    if retcode:
        cmd = kwargs.get("args")
        if cmd is None:
            cmd = popenargs[0]
        error = subprocess.CalledProcessError(retcode, cmd)
        error.output = output
#        print output
#        print unused_err
        raise error
    return output


def checking(d_dependency):
    """
    For each key in d_dependency check if it is avalabie 
    """

    def check_python():
        req_version = (2, 6)
        cur_version = sys.version_info

        # Check python
        if cur_version >= req_version:
            return 1
        else:
            print "To old version (need >2.5). Abort"
            sys.exit(1)

    def check_availability(binary):
        """
        If avalable return the path where the binary 
        can be found, else return 0
        """

        if binary == "python":
            check_python()

        try:
            a = check_output(["which", binary])

            if binary == "irpf90":
                version = check_output("irpf90 -v".split()).strip()

                from distutils.version import LooseVersion
                if LooseVersion(version) < LooseVersion("1.6.7"):
                    return 0
                else:
                    return a

            return a

        except (OSError,subprocess.CalledProcessError):
            default_path = d_info[binary].default_path
            if os.path.exists(default_path):
                return default_path
            else:
                return 0

    def get_list_descendant(d_dependency, l_installed, l_needed):
        """
        Descendant : a node reachable by repeated proceeding from parent to child.
        """
        d_need_genealogy = dict()

        for need in l_needed:
            d_need_genealogy[need] = None
            for childen in d_dependency[need]:
                if childen not in l_installed:
                    d_need_genealogy[childen] = None
        return d_need_genealogy.keys()

        return d_need_genealogy.keys()
        print """
 _
|_)  _     o  _
| \ (/_ \/ | (/_ \/\/

    """

    print "Checking what you need to install and what is avalaible"
    print ""
    l_installed = dict()
    l_needed = []

    # Check all the others
    length = max(map(len, d_dependency))

    for i in d_dependency.keys():
        print "Checking {0:>{1}}...".format(i, length),

        r = check_availability(i)
        if r:
            print OK+" ( {0} )".format(r.strip())
            l_installed[i] = r.strip()
        else:
            print FAIL
            l_needed.append(i)
    print ""

    # Expand the needed stuff for all the genealogy
    l_install_descendant = get_list_descendant(d_dependency, l_installed,
                                               l_needed)

    return l_installed, l_install_descendant


def installation(l_install_descendant):
    """
    Installing all the list
    0 install ninja
    1 create ninja
    2 run ninja
    """

    def create_rule_ninja():

        l_rules = [
            "rule download",
            "  command = wget --no-check-certificate ${url} -O ${out} -o /dev/null",
            "  description = Downloading ${descr}", ""
        ]

        l_rules += [
            "rule install",
            "  command = ./scripts/install_${target}.sh > _build/${target}.log 2>&1",
            "  description = Installing ${descr}", ""
        ]

        l_rules += [
            "rule install_verbose",
            '  command = bash -o pipefail -c "./scripts/install_${target}.sh | tee _build/${target}.log 2>&1"  ',
            "  description = Installing ${descr}", "  pool = console", ""
        ]

        return l_rules

    def splitext(path):
        for ext in ['.tar.gz', '.tar.bz2']:
            if path.endswith(ext):
                return path[:-len(ext)], path[-len(ext):]
        return os.path.splitext(path)

    print """
___
 |  ._   _ _|_  _. | |  _. _|_ o  _  ._
_|_ | | _>  |_ (_| | | (_|  |_ | (_) | |

"""

    d_print = {
        "install_ninja": "Install ninja...",
        "build": "Creating build.ninja...",
        "install": "Installing the dependencies using Ninja..."
    }

    length = max(map(len, d_print.values()))

    def str_info(key):
        return "{0:<{1}}".format(d_print[key], length)

    if "ninja" in l_install_descendant:

        print str_info("install_ninja"),

        url = d_info["ninja"].url
        extension = splitext(url)[1]
        path_archive = "Downloads/{0}{1}".format("ninja", extension)

        l_cmd = ["set -x ;", "cd install &&",
                 "wget {0} -O {1} &&".format(url, path_archive),
                 "./scripts/install_ninja.sh &&", "cd -"]

        try:
            check_output(" ".join(l_cmd), shell=True)
        except:
            raise
        else:
            print OK

        l_install_descendant.remove("ninja")

    print str_info("build"),
    l_string = create_rule_ninja()

    l_build = []

    for need in l_install_descendant:

        url = d_info[need].url
        extension = splitext(url)[1]

        archive_path = "Downloads/{0}{1}".format(need, extension)

        descr = d_info[need].description
        default_path = d_info[need].default_path

        # Build to download
        l_build += ["build {0}: download".format(archive_path),
                    "   url = {0}".format(url), "   descr = {0}".format(descr),
                    ""]

        # Build to install
        l_dependency = [d_info[i].default_path for i in d_dependency[need]
                        if i in l_install_descendant]
        str_dependency = " ".join(l_dependency)

        rule = "install" if need != "ocaml" else "install_verbose"

        l_build += ["build {0}: {1} {2} {3}".format(default_path, rule,
                                                    archive_path,
                                                    str_dependency),
                    "   target = {0}".format(need),
                    "   descr = {0}".format(descr), ""]

    l_string += l_build

    path = join(QP_ROOT_INSTALL, "build.ninja")
    with open(path, "w+") as f:
        f.write("\n".join(l_string))

    print OK+" ({0})".format(path)

    print str_info("install"),
    print "Running"
    try:
        path_ninja = find_path("ninja", l_installed)
        subprocess.check_call("cd install ;{0}".format(path_ninja), shell=True)
    except:
        prefix = os.path.join('install', '_build')
        for filename in os.listdir(prefix):
            if filename.endswith(".log"):
                with open( os.path.join(prefix,filename) ,'r') as f:
                    print "\n\n"
                    print "=-=-=-=-=-=- %s =-=-=-=-=-=-" %(filename)
                    print f.read()
                    print "=-=-=-=-=-=-=-=-=-=-=-=-=-=-\n\n"
        print "Error in installation of dependencies"
        sys.exit(1)
    else:
        print r"""
 _________
< Success >
 ---------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
            """


def create_ninja_and_rc(l_installed):
    print """
 _
|_ o ._   _. | o _   _
|  | | | (_| | | /_ (/_

"""

    d_print = {"qp_root": "Creating quantum_package.rc...",
               "build": "Creating build.ninja..."}

    length = max(map(len, d_print.values()))

    def str_info(key):
        return "{0:<{1}}".format(d_print[key], length)

    print str_info("qp_root"),
    python_path = [join(QP_ROOT, "scripts"), join(QP_ROOT, "install")]

    l_python = [join("${QP_ROOT}", "scripts")]
    for dir_ in python_path:
        for folder in os.listdir(dir_):
            path = join(dir_, folder)
            if os.path.isdir(path):
                path = path.replace(QP_ROOT,"${QP_ROOT}")
                l_python.append(path)

    path_ezfio = find_path('ezfio', l_installed, var_for_qp_root=True)
    path_irpf90 = find_path("irpf90", l_installed, var_for_qp_root=True)
    path_ninja = find_path("ninja", l_installed, var_for_qp_root=True)

    l_rc = [
        'export QP_ROOT={0}'.format(QP_ROOT),
        'export QP_EZFIO={0}'.format(path_ezfio.replace(QP_ROOT,"${QP_ROOT}")),
        'export QP_PYTHON={0}'.format(":".join(l_python)), "",
        'export IRPF90={0}'.format(path_irpf90.replace(QP_ROOT,"${QP_ROOT}")),
        'export NINJA={0}'.format(path_ninja.replace(QP_ROOT,"${QP_ROOT}")),
        'export PYTHONPATH="${QP_EZFIO}/Python":"${QP_PYTHON}":"${PYTHONPATH}"',
        'export PATH="${QP_PYTHON}":"${QP_ROOT}"/bin:"${QP_ROOT}"/ocaml:"${PATH}"',
        'export LD_LIBRARY_PATH="${QP_ROOT}"/lib:"${LD_LIBRARY_PATH}"',
        'export LIBRARY_PATH="${QP_ROOT}"/lib:"${LIBRARY_PATH}"', "",
        'source ${QP_ROOT}/install/EZFIO/Bash/ezfio.sh', "",
        'source ${HOME}/.opam/opam-init/init.sh > /dev/null 2> /dev/null || true',
        '',
        '# Choose the correct network interface',
        '# export QP_NIC=ib0',
        '# export QP_NIC=eth0',
        ""
    ]

    path = join(QP_ROOT, "quantum_package.rc")
    with open(path, "w+") as f:
        f.write("\n".join(l_rc))

    print OK+" ({0})".format(path)

    command = ['bash', '-c', 'source {0} && env'.format(path)]
    proc = subprocess.Popen(command, stdout=subprocess.PIPE)
    for line in proc.stdout:
        (key, _, value) = line.partition("=")
        os.environ[key] = value.strip()

    print str_info("build"),

    qp_create_ninja = os.path.join(QP_ROOT, "scripts", "compilation",
                                   "qp_create_ninja.py")

    l = [qp_create_ninja, "create"] + sys.argv[1:]

    try:
        with open('/dev/null', 'w') as dnull:
            subprocess.check_call(" ".join(l), shell=True,stderr=dnull)
    except:
        print "[ FAIL ]"
        print "Check the validity of the config file provided ({0})".format(sys.argv[1])
        print "Exit..."
        sys.exit(1)

    else:
        print OK


def recommendation():
    path = join(QP_ROOT, "quantum_package.rc")
    print "Now :"
    print "   source {0}".format(path)
    print ""
    print "Then, install the modules you want to install using :"
    print "    qp_module.py"
    print ""
    print "Finally :"
    print "   ninja"
    print ""
    print "You can install more plugin with the qp_module.py install command" 
    print "PS : For more info on compiling the code, read the README.md"


if __name__ == '__main__':

    l_installed, l_install_descendant = checking(d_dependency)

    if l_install_descendant:
        print "You will need to install:"
        for i in l_install_descendant:
            print "* {0}".format(i)

        installation(l_install_descendant)
    else:
        print "Perfect, nothing to install"
    create_ninja_and_rc(l_installed)

    recommendation()

