#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Podio class generator script"""

import os
import subprocess
import re

from podio_gen.podio_config_reader import PodioConfigReader
from podio_gen.generator_utils import DefinitionError
from podio_gen.cpp_generator import CPPClassGenerator
from podio_gen.julia_generator import JuliaClassGenerator


def has_clang_format():
    """Check if clang format is available"""
    try:
        # This one can raise if -fallback-style is not found
        out = subprocess.check_output(
            ["clang-format", "-style=file", "-fallback-style=llvm", "--help"],
            stderr=subprocess.STDOUT,
        )
        # This one doesn't raise
        out = subprocess.check_output(
            "echo | clang-format -style=file ", stderr=subprocess.STDOUT, shell=True
        )
        if b".clang-format" in out:
            return False
        return True
    except FileNotFoundError:
        print("ERROR: Cannot find clang-format executable")
        print("       Please make sure it is in the PATH.")
        return False
    except subprocess.CalledProcessError:
        print("ERROR: At least one argument was not recognized by clang-format")
        print("       Most likely the version you are using is old")
        return False


def clang_format_file(content, name):
    """Formatter function to run clang-format on generate c++ files"""
    if name.endswith(".jl"):
        return content

    clang_format = ["clang-format", "-style=file", "-fallback-style=llvm"]
    with subprocess.Popen(clang_format, stdin=subprocess.PIPE, stdout=subprocess.PIPE) as cfproc:
        return cfproc.communicate(input=content.encode())[0].decode()


def read_upstream_edm(name_path):
    """Read an upstream EDM yaml definition file to make the types that are defined
    in that available to the current EDM"""
    if name_path is None:
        return None

    try:
        name, path = name_path.split(":")
    except ValueError as err:
        raise argparse.ArgumentTypeError(
            "upstream-edm argument needs to be the upstream package "
            "name and the upstream edm yaml file separated by a colon"
        ) from err

    if not os.path.isfile(path):
        raise argparse.ArgumentTypeError(f"{path} needs to be an EDM yaml file")

    try:
        return PodioConfigReader.read(path, name)
    except DefinitionError as err:
        raise argparse.ArgumentTypeError(
            f"{path} does not contain a valid datamodel definition"
        ) from err


def parse_version(version_str):
    """Parse the version into a tuple of (major, minor, patch) from the passed
    version string.
    """
    if version_str is None:
        return None

    if re.match(r"v?\d+[\.|-]\d+([\.|-]\d+)?$", version_str):
        ver = version_str.replace("-", ".").replace("v", "").split(".")
        return tuple(int(v) for v in ver)

    raise argparse.ArgumentTypeError(f"{version_str} cannot be parsed as a valid version")


if __name__ == "__main__":
    import argparse

    # pylint: disable=invalid-name # before 2.5.0 pylint is too strict with the naming here
    parser = argparse.ArgumentParser(
        description="Given a description yaml file this script generates "
        "the necessary c++ or julia files in the target directory"
    )

    parser.add_argument("description", help="yaml file describing the datamodel")
    parser.add_argument(
        "targetdir",
        help="Target directory where the generated data classes will be put. "
        "Header files will be put under <targetdir>/<packagename>/*.h. "
        "Source files will be put under <targetdir>/src/*.cc. "
        "Julia files will be put under <targetdir>/<packagename>/*.jl.",
    )
    parser.add_argument("packagename", help="Name of the package.")
    parser.add_argument(
        "iohandlers",
        choices=["ROOT", "SIO"],
        nargs="*",
        help="The IO backend specific code that should be generated",
        default="ROOT",
    )
    parser.add_argument(
        "-l",
        "--lang",
        choices=["cpp", "julia"],
        default="cpp",
        help="Specify the programming language (default: cpp)",
    )
    parser.add_argument(
        "-q",
        "--quiet",
        dest="verbose",
        action="store_false",
        default=True,
        help="Don't write a report to screen",
    )
    parser.add_argument(
        "-d",
        "--dryrun",
        action="store_true",
        default=False,
        help="Do not actually write datamodel files",
    )
    parser.add_argument(
        "-c",
        "--clangformat",
        action="store_true",
        default=False,
        help="Apply clang-format when generating code (with -style=file)",
    )
    parser.add_argument(
        "--upstream-edm",
        help="Make datatypes of this upstream EDM available to the current"
        " EDM. Format is '<upstream-name>:<upstream.yaml>'. "
        "Note that only the code for the current EDM will be generated",
        default=None,
        type=read_upstream_edm,
    )
    parser.add_argument(
        "--old-description",
        help="Provide schema evolution relative to the old yaml file.",
        default=None,
        action="store",
    )
    parser.add_argument(
        "-e",
        "--evolution_file",
        help="yaml file clarifying schema evolutions",
        default=None,
        action="store",
    )
    parser.add_argument(
        "--datamodel-version",
        help="The version string of the generated datamodel",
        default=None,
        type=parse_version,
    )

    args = parser.parse_args()

    install_path = args.targetdir
    project = args.packagename

    for sub_dir in ("src", project):
        directory = os.path.join(install_path, sub_dir)
        if not os.path.exists(directory):
            os.makedirs(directory)

    if args.lang == "julia":
        gen = JuliaClassGenerator(
            args.description,
            args.targetdir,
            args.packagename,
            verbose=args.verbose,
            dryrun=args.dryrun,
            upstream_edm=args.upstream_edm,
        )
    if args.lang == "cpp":
        gen = CPPClassGenerator(
            args.description,
            args.targetdir,
            args.packagename,
            args.iohandlers,
            verbose=args.verbose,
            dryrun=args.dryrun,
            upstream_edm=args.upstream_edm,
            datamodel_version=args.datamodel_version,
            old_description=args.old_description,
            evolution_file=args.evolution_file,
        )

        if args.clangformat and has_clang_format():
            gen.formatter_func = clang_format_file

    gen.process()

    # pylint: enable=invalid-name