#!/bin/python3
import argparse
import hashlib
import json
import logging
import os
import sys


def cleanup_json(data):
    """Cleans up the json structure by removing empty "", and empty key value
    pairs."""
    if isinstance(data, str):
        copy = data.strip()
        return None if len(copy) == 0 else copy

    if isinstance(data, dict):
        copy = {}
        for key, value in data.items():
            rem = cleanup_json(value)
            if rem is not None:
                copy[key] = rem
        return None if len(copy) == 0 else copy

    if isinstance(data, list):
        copy = []
        for elem in data:
            rem = cleanup_json(elem)
            if rem is not None:
                if rem not in copy:
                    copy.append(rem)

        if len(copy) == 0:
            return None
        return copy


class AttrDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttrDict, self).__init__(*args, **kwargs)
        self.__dict__ = self

    def as_list(self, name):
        v = self.get(name, [])
        if isinstance(v, list):
            return v

        return [v]


def remove_lib_prefix(module):
    """Removes the lib prefix, as we are not using them in CMake."""
    if module.startswith("lib"):
        return module[3:]
    else:
        return module


def escape(msg):
    """Escapes the "."""
    return '"' + msg.replace('"', '\\"') + '"'


def header():
    """The auto generate header."""
    return [
        "# This is an autogenerated file! Do not edit!",
        "# instead run make from .../device/generic/goldfish-opengl",
        "# which will re-generate this file.",
    ]


def checksum(fname):
    """Calculates a SHA256 digest of the given file name."""
    m = hashlib.sha256()
    with open(fname, "r", encoding="utf-8") as mk:
        m.update(mk.read().encode("utf-8"))
    return m.hexdigest()


def generate_module(module):
    """Generates a cmake module."""
    name = remove_lib_prefix(module["module"])
    make = header()
    mkfile = os.path.join(module["path"], "Android.mk")
    sha256 = checksum(mkfile)
    make.append(
        'android_validate_sha256("${GOLDFISH_DEVICE_ROOT}/%s" "%s")' % (mkfile, sha256)
    )
    make.append("set(%s_src %s)" % (name, " ".join(module["src"])))
    if module["type"] == "SHARED_LIBRARY":
        make.append(
            "android_add_library(TARGET {} SHARED LICENSE Apache-2.0 SRC {})".format(
                name, " ".join(module["src"])
            )
        )
    elif module["type"] == "STATIC_LIBRARY":
        make.append(
            "android_add_library(TARGET {} LICENSE Apache-2.0 SRC {})".format(
                name, " ".join(module["src"])
            )
        )
    else:
        raise ValueError("Unexpected module type: %s" % module["type"])

    # Fix up the includes.
    includes = ["${GOLDFISH_DEVICE_ROOT}/" + s for s in module["includes"]]
    make.append(
        "target_include_directories(%s PRIVATE %s)" % (name, " ".join(includes))
    )

    # filter out definitions
    defs = [escape(d) for d in module["cflags"] if d.startswith("-D")]

    #  And the remaining flags.
    flags = [escape(d) for d in module["cflags"] if not d.startswith("-D")]

    # Make sure we remove the lib prefix from all our dependencies.
    libs = [remove_lib_prefix(l) for l in module.get("libs", [])]
    staticlibs = [
        remove_lib_prefix(l)
        for l in module.get("staticlibs", [])
        if l != "libandroidemu"
    ]

    # Configure the target.
    make.append("target_compile_definitions(%s PRIVATE %s)" % (name, " ".join(defs)))
    make.append("target_compile_options(%s PRIVATE %s)" % (name, " ".join(flags)))

    if len(staticlibs) > 0:
        make.append(
            "target_link_libraries(%s PRIVATE %s PRIVATE %s)"
            % (name, " ".join(libs), " ".join(staticlibs))
        )
    else:
        make.append("target_link_libraries(%s PRIVATE %s)" % (name, " ".join(libs)))
    return make


def main(argv=None):
    parser = argparse.ArgumentParser(
        description="Generates a set of cmake files"
        "based up the js representation."
        "Use this to generate cmake files that can be consumed by the emulator build"
    )
    parser.add_argument(
        "-i",
        "--input",
        dest="input",
        type=str,
        required=True,
        help="json file containing the build tree",
    )
    parser.add_argument(
        "-v",
        "--verbose",
        action="store_const",
        dest="loglevel",
        const=logging.INFO,
        default=logging.ERROR,
        help="Log what is happening",
    )
    parser.add_argument(
        "-o",
        "--output",
        dest="outdir",
        type=str,
        default=None,
        help="Output directory for create CMakefile.txt",
    )
    parser.add_argument(
        "-c",
        "--clean",
        dest="output",
        type=str,
        default=None,
        help="Write out the cleaned up js",
    )
    args = parser.parse_args()

    logging.basicConfig(level=args.loglevel)

    with open(args.input) as data_file:
        data = json.load(data_file)

    modules = cleanup_json(data)

    # Write out cleaned up json, mainly useful for debugging etc.
    if args.output is not None:
        with open(args.output, "w") as out_file:
            out_file.write(json.dumps(modules, indent=2))

    # Location --> CMakeLists.txt
    cmake = {}

    # The root, it will basically just include all the generated files.
    root = os.path.join(args.outdir, "CMakeLists.txt")
    mkfile = os.path.join(args.outdir, "Android.mk")
    sha256 = checksum(mkfile)
    cmake[root] = header()
    cmake[root].append("set(GOLDFISH_DEVICE_ROOT ${CMAKE_CURRENT_SOURCE_DIR})")
    cmake[root].append(
        'android_validate_sha256("${GOLDFISH_DEVICE_ROOT}/%s" "%s")' % (mkfile, sha256)
    )

    # Generate the modules.
    for module in modules:
        location = os.path.join(args.outdir, module["path"], "CMakeLists.txt")

        # Make sure we handle the case where we have >2 modules in the same dir.
        if location not in cmake:
            path = module["path"]
            if path.startswith("../"):
                path_binary_dir = path
                path_binary_dir = path_binary_dir.replace("../", "")
                path_binary_dir = path_binary_dir.replace("/", "-")
                path_binary_dir = path_binary_dir.lower()

                cmake[root].append("add_subdirectory(%s %s)" % (path, path_binary_dir))
            else:
                cmake[root].append("add_subdirectory(%s)" % path)
            cmake[location] = []
        cmake[location].extend(generate_module(module))

    # Write them to disk.
    for (loc, cmklist) in cmake.items():
        logging.info("Writing to %s", loc)
        with open(loc, "w") as fn:
            fn.write("\n".join(cmklist))


if __name__ == "__main__":
    sys.exit(main())
