from __future__ import print_function, absolute_import
import os
import re
from distutils.errors import DistutilsSetupError
from .utils import Binding, Strip

import semantic_version


class RustExtension:
    """Just a collection of attributes that describes an rust extension
    module and everything needed to build it

    Instance attributes:
      target : string
        the full name of the extension, including any packages -- ie.
        *not* a filename or pathname, but Python dotted name
      path : string
        path to the cargo.toml manifest file
      args : [string]
        a list of extra argumenents to be passed to cargo.
      features : [string]
        a list of features to also build
      rust_version : string
        rust compiler version
      quiet : bool
        If True, doesn't echo cargo's output.
      debug : bool
        Controls whether --debug or --release is passed to cargo. If set to
        None then build type is auto-detect. Inplace build is debug build
        otherwise release. Default: None
      binding : setuptools_rust.Binding
        Controls which python binding is in use.
        Binding.PyO3 uses PyO3
        Binding.RustCPython uses Rust CPython.
        Binding.NoBinding uses no binding.
        Binding.Exec build executable.
      strip : setuptools_rust.Strip
        Strip symbols from final file. Does nothing for debug build.
        * Strip.No - do not strip symbols
        * Strip.Debug - strip debug symbols
        * Strip.All - strip all symbols
      script : bool
        Generate console script for executable if `Binding.Exec` is used.
      native : bool
        Build extension or executable with "target-cpu=native"
      optional : bool
        if it is true, a build failure in the extension will not abort the
        build process, but instead simply not install the failing extension.
    """

    def __init__(
        self,
        target,
        path="Cargo.toml",
        args=None,
        features=None,
        rustc_flags=None,
        rust_version=None,
        quiet=False,
        debug=None,
        binding=Binding.PyO3,
        strip=Strip.No,
        script=False,
        native=False,
        optional=False,
    ):
        if isinstance(target, dict):
            name = "; ".join("%s=%s" % (key, val) for key, val in target.items())
        else:
            name = target
            target = {"": target}

        self.name = name
        self.target = target
        self.args = args
        self.rustc_flags = rustc_flags
        self.binding = binding
        self.rust_version = rust_version
        self.quiet = quiet
        self.debug = debug
        self.strip = strip
        self.script = script
        self.native = native
        self.optional = optional

        if features is None:
            features = []

        self.features = [s.strip() for s in features]

        # get relative path to Cargo manifest file
        path = os.path.relpath(path)
        # file = sys._getframe(1).f_globals.get('__file__')
        # if file:
        #     dirname = os.path.dirname(file)
        #     print(dirname)
        #     if dirname:
        #         cwd = os.getcwd()
        #         os.chdir(dirname)
        #         path = os.path.abspath(path)
        #         os.chdir(cwd)

        self.path = path

    def get_lib_name(self):
        """ Parse Cargo.toml to get the name of the shared library. """
        # We import in here to make sure the the setup_requires are already installed
        import toml

        cfg = toml.load(self.path)
        name = cfg.get("lib", {}).get("name")
        if name is None:
            name = cfg.get("package", {}).get("name")
        if name is None:
            raise Exception(
                "Can not parse library name from Cargo.toml. "
                "Cargo.toml missing value for 'name' key "
                "in both the [package] section and the [lib] section"
            )
        name = re.sub(r"[./\\-]", "_", name)
        return name

    def get_rust_version(self):
        if self.rust_version is None:
            return None
        try:
            return semantic_version.Spec(self.rust_version)
        except ValueError:
            raise DistutilsSetupError(
                "Can not parse rust compiler version: %s", self.rust_version
            )

    def entry_points(self):
        entry_points = []
        if self.script and self.binding == Binding.Exec:
            for name, mod in self.target.items():
                base_mod, name = mod.rsplit(".")
                script = "%s=%s.%s:run" % (name, base_mod, "_gen_%s" % name)
                entry_points.append(script)

        return entry_points

    def install_script(self, ext_path):
        if self.script and self.binding == Binding.Exec:
            dirname, name = os.path.split(ext_path)
            file = os.path.join(dirname, "_gen_%s.py" % name)
            with open(file, "w") as f:
                f.write(TMPL.format({"name": name}))


TMPL = """from __future__ import absolute_import, print_function

import os
import sys


def run():
    path = os.path.split(__file__)[0]
    name = os.path.split(sys.argv[0])[1]
    file = os.path.join(path, name)
    if os.path.isfile(file):
        os.execv(file, sys.argv)
    else:
        print("Can not execute '%s'" % name)
"""