#!/usr/bin/env python3
"""
Installation script generated from a Bazel `install` target.
"""

import argparse
import collections
import filecmp
import itertools
import os
import re
import shutil
import stat
import sys
from subprocess import check_output, check_call, Popen, PIPE
import xml.etree.ElementTree as ET

prefix = None
pkg_name = None
dbg = False
gpu = False
dev = True


def shell_cmd(cmd, alert_on_failure=True):
    """Execute shell command and return (ret-code, stdout, stderr)."""
    print("SHELL > {}".format(cmd))
    proc = Popen(cmd, shell=True, close_fds=True, stdout=PIPE, stderr=PIPE)
    ret = proc.wait()
    stdout = proc.stdout.read().decode('utf-8') if proc.stdout else None
    stderr = proc.stderr.read().decode('utf-8') if proc.stderr else None
    if alert_on_failure and stderr and ret != 0:
        sys.stderr.write('{}\n'.format(stderr))
    return (ret, stdout, stderr)


def get_pkg_real_name(name, dev=False, dbg=False, gpu=False):
    """Get real package name by install parameters"""
    new_name = name
    if dev:
        new_name += "-dev"
    return new_name


def rename_package_name(dest):
    """Get packages name from file install destination."""
    if not dev and not dbg and not gpu:
        return dest
    if dest.startswith("lib/"):
        return dest
    curr_pkg_name = dest.split("/")[0]
    new_pkg_name = get_pkg_real_name(curr_pkg_name, dev, dbg, gpu)

    # Local build package version is fiexed `local`
    pkg_name_with_ver = new_pkg_name + "/local"

    return dest.replace(curr_pkg_name, pkg_name_with_ver, 1)


def setup_cyberfile(cyberfile_path, replace=True, dev=False, dbg=False, gpu=False):
    """Setup final cyberfile by install parameters. """
    cyberfile = ET.parse(cyberfile_path)
    root = cyberfile.getroot()

    name = root.find("name")
    old_name = name.text
    name.text = get_pkg_real_name(name.text, dev, dbg, gpu)

    for dep in root.findall("depend"):
        if dep.get("condition"):
            if not eval(dep.get("condition")):
                root.remove(dep)
            else:
                del dep.attrib["condition"]

    if replace:
        cyberfile.write(cyberfile_path)

    return old_name, name.text, ET.tostring(root)


def setup_cyberfiles(pkg_name=None):
    """Setup final cyberfiles by install parameters. """
    for d in os.listdir(prefix):
        if pkg_name is not None and d != pkg_name:
            # only handle target package
            return
        cyberfile_path = prefix + d + "/local/cyberfile.xml"
        if os.path.exists(cyberfile_path):
            old_name, new_name, _ = setup_cyberfile(cyberfile_path, True, dev, dbg, gpu)
            if os.path.exists(prefix + d + "/" + old_name + ".BUILD"):
                os.rename(prefix + d + "/" + old_name + ".BUILD", prefix + d + "/" + new_name + ".BUILD")

def METHOD_NAME(cyberfile_path):
    if not os.path.exists(cyberfile_path):
        return None
    cyberfile = ET.parse(cyberfile_path)
    root = cyberfile.getroot()
    src_path = root.find("src_path")
    if src_path is None:
        return None
    pkg_type = root.find("type")
    if pkg_type is None:
        return None
    if pkg_type.text == "module" or pkg_type.text == "module-wrapper":
        return src_path.text.replace("//", "", 1).replace("/", "\/")
    return None


def replace_config(pkg_module_dict, prefix, config_file):
    """
    e.g.
    1. /apollo/bazel-bin/modules/planning/libplanning_component.so ==> /opt/apollo/neo/packages/planning-dev/latest/lib/libplanning_component.so
    # 2. /apollo/modules/planning/conf/planning_config_navi.pb.txt ==> /opt/apollo/neo/packages/planning-dev/latest/conf/planning_config_navi.pb.txt
    # 3. /apollo/modules/planning/dag/planning.dag ==> /opt/apollo/neo/packages/planning-dev/local/dag/planning.dag
    """
    for pkg_name, module_name in pkg_module_dict.items():
        shell_cmd("sed -i 's/\/apollo\/bazel-bin\/{}\//{}{}\/latest\/lib\//g' {}".format(module_name, prefix.replace("/", "\/"), pkg_name, config_file))
        #shell_cmd("sed -i 's/\/apollo\/{}\//{}{}\/local\//g' {}".format(module_name, prefix.replace("/", "\/"), pkg_name, config_file))

def replace_config_dir(full_c_d, prefix, pkg_module_dict):
    if not os.path.exists(full_c_d):
        return
    for c in os.listdir(full_c_d):
        c_f = full_c_d + "/" + c
        if os.path.isdir(c_f):
            replace_config_dir(c_f, prefix, pkg_module_dict)
        elif c_f.endswith(".dag"):
            replace_config(pkg_module_dict, prefix, c_f)
        else:
            continue

def fix_configs_in_pkg():
    conf_dirs = [
        "/local",
        # "/local/launch",
        # "/local/conf"
    ]
    pkg_module_dict = {}
    for d in os.listdir(prefix):
        pkg_name = d.replace("/", "\/")
        module_name = METHOD_NAME(prefix + d + "/local/cyberfile.xml")
        if module_name is None:
            continue
        pkg_module_dict[pkg_name] = module_name

    for d in os.listdir(prefix):
        for c_d in conf_dirs:
            full_c_d = prefix + d + c_d
            replace_config_dir(full_c_d, prefix, pkg_module_dict)



def install_src(src, dst, filter):
    dst = rename_package_name(dst)

    if not os.path.isdir(src):
        sys.stderr.write("install_src only support dir, {} is not dir.".format(dst))
        sys.exit(-1)

    dst_full = os.path.join(prefix, dst)
    if not os.path.exists(dst_full):
        os.makedirs(dst_full)
    shell_cmd("cd {} && find . -name '{}'|xargs -i -I@@ cp -rvnfP --parents @@ {}/ > /dev/null"
        .format(src, filter, dst_full))


def main(args):
    global prefix
    global pkg_name
    global dbg
    global gpu
    global dev

    # Set up options.
    parser = argparse.ArgumentParser()
    parser.add_argument('prefix', type=str, help='Install prefix')

    parser.add_argument('--pkg_name', type=str, default=None,
                                    help='Install target package name.')
    parser.add_argument('--dbg', action='store_true', default=False,
                                help='debug package with debugging symbols.')
    parser.add_argument('--gpu', action='store_true', default=False,
                            help='build with gpu.')
    parser.add_argument('--dev', action='store_true', default=True,
                            help='dev package with headers.')
    args = parser.parse_args(args)

    # Get install prefix.
    prefix = args.prefix

    pkg_name = args.pkg_name
    dbg = args.dbg
    gpu = args.gpu
    dev = True

    # Transform install prefix if DESTDIR is set.
    # https://www.gnu.org/prep/standards/html_node/DESTDIR.html
    destdir = os.environ.get('DESTDIR')
    if destdir:
        prefix = destdir + prefix

    # Because Bazel executes us in a strange working directory and not the
    # working directory of the user's shell, enforce that the install
    # location is an absolute path so that the user is not surprised.
    if not os.path.isabs(prefix):
        parser.error("Install prefix must be an absolute path (got '{}')\n".format(prefix))

    # Execute the install actions.
    <<actions>>

    # Setup cyberfile
    setup_cyberfiles(pkg_name)

    # Fix config path
    fix_configs_in_pkg()


if __name__ == "__main__":
    main(sys.argv[1:])