#!/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

# Stores result of `--list` argument.
list_only = False

deprecated_package_prefix = "packages"


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 = curr_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 = os.path.join(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(os.path.join(prefix, d, old_name + ".BUILD")):
#                 os.rename(
#                     os.path.join(prefix, d, old_name + ".BUILD"), 
#                     os.path.join(prefix, d, new_name + ".BUILD")
#                 )

def get_module_name_from_cyberfile(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("/", "\/") if prefix.endswith("/") else 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 = get_module_name_from_cyberfile(os.path.join(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 = os.path.join(prefix, d, c_d)
            replace_config_dir(full_c_d, prefix, pkg_module_dict)
            


def install_src(src, dst, filter, action_type=None):
    if list_only:
        return
    deprecated_flag = False
    if action_type is None:
        deprecated_flag = True
        # deprecated package install logic
        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)

    if deprecated_flag:
        dst_full = os.path.join(prefix, deprecated_package_prefix, dst)
    else:
        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 -rvfP --parents @@ {}/ > /dev/null"
        .format(src, filter, dst_full))


def main(args):
    global prefix
    global pkg_name
    global list_only
    # 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(
        '--list', action='store_true', default=False,
        help='print the list of installed files; do not install anything')
    # 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)

    list_only = args.list

    # 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.
    install_src("modules/planning/planning_base", "src/modules/planning/planning_base", "*", "neo")
    install_src("modules/planning/planning_base", "include/modules/planning/planning_base", "*.h", "neo")
    install_src("modules/planning/planning_base/learning_based/model_inference/proto", "src/modules/planning/planning_base/learning_based/model_inference/proto", "*", "neo")
    install_src("modules/planning/planning_base/learning_based/model_inference/proto", "include/modules/planning/planning_base/learning_based/model_inference/proto", "*.h", "neo")
    install_src("modules/planning/planning_base/proto", "src/modules/planning/planning_base/proto", "*", "neo")
    install_src("modules/planning/planning_base/proto", "include/modules/planning/planning_base/proto", "*.h", "neo")
    install_src("modules/planning/planning_base/proto/math", "src/modules/planning/planning_base/proto/math", "*", "neo")
    install_src("modules/planning/planning_base/proto/math", "include/modules/planning/planning_base/proto/math", "*.h", "neo")
    install_src("modules/planning/planning_base/scenario_base/proto", "src/modules/planning/planning_base/scenario_base/proto", "*", "neo")
    install_src("modules/planning/planning_base/scenario_base/proto", "include/modules/planning/planning_base/scenario_base/proto", "*.h", "neo")
    install_src("modules/planning/planning_base/traffic_rules_base/proto", "src/modules/planning/planning_base/traffic_rules_base/proto", "*", "neo")
    install_src("modules/planning/planning_base/traffic_rules_base/proto", "include/modules/planning/planning_base/traffic_rules_base/proto", "*.h", "neo")

    # Setup cyberfile
    # setup_cyberfiles(pkg_name)

    # Fix config path
    fix_configs_in_pkg()


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