"""
Tools for converting luarocks packages to conda recipes.
"""

# TODO:
# - mingw32 support (really any windows support, completely untested)
# - replace manual "luajit -e require 'blah'" with built-in entry-point testing

import os
import subprocess
import tempfile
from glob import glob
import json
from sys import platform as _platform

INDENT = '\n    - '

rockspec_parser = """
local ok,cjson = pcall(require, "cjson")
if not ok then
   print("ERROR: lua-cjson not installed. Use conda to install luarocks, "
         "then run 'luarocks install lua-cjson'.")
   os.exit()
end

local rockspecFile = "%s"
local origPackage = package
local ok, _ = pcall(dofile, rockspecFile)
if not ok then
   print("ERROR: could not load rockspecFile " .. tostring(rockspecFile))
   os.exit()
end

-- Resolve name clash
if origPackage == package then
   package = nil
end
local out = {
   rockspec_format=rockspec_format,
   package=package,
   version=version,
   description=description,
   supported_platforms=supported_platforms,
   dependencies=dependencies,
   external_dependencies=external_dependencies,
   source=source,
   build=build,
   modules=modules,
}
print(cjson.encode(out))
"""


LUAROCKS_META = """\
package:
  name: {packagename}
  version: "{version}"

source:
  {usefile}fn: {filename}
  {usefile}url: {url}
  {usegit}git_url: {url}
  {usegittag}git_tag: {gittag} # can also be a branch, but that is highly discouraged
  {usegitrev}git_rev: {gitrev} # prefer tags over commits, commits over branches
  {usemd5}md5:{md5}
#  patches:
   # List any patch files here
   # - fix.patch

build:
  {noarch_python_comment}noarch: generic
  # Useful to leave this on by default, will allow relocating
  # packages that have hard-coded paths in them
  detect_binary_files_with_prefix: true
  # If this is a new build for the same version, increment the build
  # number. If you do not include this key, it defaults to 0.
  # number: 1

requirements:
  build:{build_depends}

  run:{run_depends}

{test_comment}test:
  {entry_comment}commands:
    # You can put test commands to be run here.  Use this to test that the
    # entry points work.
{test_commands}

  # You can also put a file called run_test.lua in the recipe that will be run
  # at test time.

about:
  {home_comment}home: {homeurl}
  license: {license}
  {summary_comment}summary: {summary}

# See
# https://docs.conda.io/projects/conda-build for
# more information about meta.yaml
"""

LUAROCKS_BUILD_SH = """\
#!/bin/bash

set -o errexit -o pipefail

# Make sure luarocks can see all local dependencies
"${{PREFIX}}"/bin/luarocks-admin make_manifest --local-tree

# Install
# Rocks aren't located in a standard location, although
# they tend to be top-level or in a rocks/ directory.
# NOTE: we're just picking the first rock we find. If there's
# more than one, specify it manually.
ROCK=$(find . -name "*.rockspec" | sort -n -r | head -n 1)
"${{PREFIX}}"/bin/luarocks install "${{ROCK}}" --local-tree

# Add more build steps here, if they are necessary.

# See
# https://docs.conda.io/projects/conda-build
# for a list of environment variables that are set during the build process.
"""

LUAROCKS_POSTLINK_SH = """\
# Let luarocks know that we've installed a new project
$PREFIX/bin/luarocks-admin make_manifest --local-tree
"""

LUAROCKS_PREUNLINK_SH = """\
# Tell luarocks we've removed the project
$PREFIX/bin/luarocks remove {rockname}
"""


def add_parser(repos):
    luarocks = repos.add_parser(
        "luarocks",
        help="""
    Create recipe skeleton for luarocks, hosted at luarocks.org
        """,
    )
    luarocks.add_argument(
        "packages",
        nargs='+',
        help="luarocks packages to create recipe skeletons for.",
    )
    luarocks.add_argument(
        "--output-dir",
        help="Directory to write recipes to (default: %(default)s).",
        default=".",
    )
    luarocks.add_argument(
        "--version",
        help="Version to use. Applies to all packages.",
    )
    luarocks.add_argument(
        "--recursive",
        action='store_true',
        help='Create recipes for dependencies if they do not already exist.')


def package_exists(package_name):
    return True
    # TODO: no current Lua packages work for me.  Need to verify this method.
    # subprocess.check_call(['luarocks', 'search', package_name])


def getval(spec, k):
    if k not in spec:
        raise Exception("Required key %s not in spec" % k)
    else:
        return spec[k]


def warn_against_branches(branch):
    print("")
    print("=========================================")
    print("")
    print("WARNING:")
    print("Building a rock referenced to branch %s." % branch)
    print("This is not a tag. This is dangerous, because rebuilding")
    print("at a later date may produce a different package.")
    print("Please replace with a tag, git commit, or tarball.")
    print("")
    print("=========================================")


def format_dep(dep):
    name_without_ver = "".join([c for c in dep if c.isalpha()])
    if name_without_ver not in ["lua"]:
        # Enforce conda naming convention.
        # lower case, no white-space, and prepended "lua-"
        # (all languages other than Python prepend their language to package names)
        if dep[:4] != "lua-":
            dep = "lua-" + dep
    dep = dep.replace(" ", "").lower()

    # Ensure a space between the first special-character that specifies version logic
    # Not "-", because that's used in e.g. lua-penlight
    special_char_test = [c in "<>=~" for c in dep]
    for i, v in enumerate(special_char_test):
        if v:
            split_dep = [c for c in dep]
            split_dep.insert(i, " ")
            dep = "".join(split_dep)
            break
    return dep


def ensure_base_deps(deps):
    basenames = ["".join([c for c in dep if c.isalpha()]) for dep in deps]
    extra_deps = []
    if "lua" not in basenames:
        extra_deps.append("lua")
    if "luarocks" not in basenames:
        extra_deps.append("luarocks")
    if len(extra_deps):
        deps = extra_deps + deps
    return deps


def skeletonize(packages, output_dir=".", version=None, recursive=False):

    # Check that we have Lua installed (any version)

    # Check that we have luarocks installed

    # Check that we have lua-cjson installed

    # Get the platform
    platform = "linux" if _platform == "linux2" else _platform

    # Make temporary directory
    cwd = os.getcwd()
    temp_dir = tempfile.mkdtemp()
    package_dicts = {}

    # Step into it
    os.chdir(temp_dir)

    while packages:
        package = packages.pop()

        packagename = "lua-%s" % package.lower() if package[:4] != "lua-" else package.lower()
        d = package_dicts.setdefault(package,
            {
                'packagename': packagename,
                'version': "0.0",
                'filename': "",
                'url': "",
                'md5': "",
                'usemd5': "# ",
                'usefile': "# ",
                'usegit': "# ",
                'usegittag': "# ",
                'usegitrev': "# ",
                'gittag': "",
                'gitrev': "",
                'noarch_python_comment': "# ",
                'build_depends': "",
                'run_depends': "",
                'test_comment': "",
                'entry_comment': "",
                'test_commands': "",
                'home_comment': "# ",
                'homeurl': "",
                'license': "Unknown",
                'summary_comment': "# ",
                'summary': "",
            })

        # Download rockspec
        o = subprocess.call(["luarocks", "download", package, "--rockspec"])
        if o != 0:
            raise Exception("Could not download rockspec for {}".format(package))

        # Find the downloaded rockspec
        fs = glob(package + "*.rockspec")
        if len(fs) != 1:
            raise Exception("Failed to download rockspec")
        d['rockspec_file'] = fs[0]

        # Parse the rockspec into a dictionary
        p = subprocess.Popen(["lua", "-e", rockspec_parser % d['rockspec_file']],
                             stdout=subprocess.PIPE)
        out, err = p.communicate()
        if "ERROR" in out:
            raise Exception(out.replace("ERROR: ", ""))
        spec = json.loads(out)

        # Gather the basic details
        d['rockname'] = getval(spec, "package")
        d['version'] = getval(spec, "version")
        d['version'] = "".join([c for c in d['version'] if c.isalnum()])
        source = getval(spec, "source")

        # Figure out how to download the package, and from where
        d['url'] = getval(source, "url")
        ext = os.path.splitext(d['url'])[-1]
        if ext in [".zip", ".tar", ".tar.bz2", ".tar.xz", ".tar.gz"]:
            d['usefile'] = ""
            d['filename'] = os.path.split(d['url'])[-1]
            if "md5" in source:
                md5 = getval(source, "md5")
                if len(md5):
                    d['md5'] = md5
                    d['usemd5'] = ""
        elif ext in [".git"] or d['url'][:4] == "git:":
            d['usegit'] = ""
            # Check if we're using a tag or a commit
            if "tag" in source:
                d['usegittag'] = ""
                d['gittag'] = getval(source, "tag")
            elif "branch" in source:
                d['usegittag'] = ""
                d['gittag'] = getval(source, "branch")
                warn_against_branches(d['gittag'])
            else:
                d['usegittag'] = ""
                d['gittag'] = "master"
                warn_against_branches(d['gittag'])

        # Gather the description
        if "description" in spec:
            desc = getval(spec, "description")
            if "homepage" in desc:
                d['homeurl'] = desc['homepage']
                d['home_comment'] = ""
            if "summary" in desc:
                d['summary'] = desc['summary']
                d['summary_comment'] = ""
            if "license" in desc:
                d['license'] = desc['license']

        # Gather the dependencies
        if "dependencies" in spec:
            deps = getval(spec, "dependencies")
            if len(deps):
                deps = ensure_base_deps([format_dep(dep) for dep in deps])
                d['build_depends'] = INDENT.join([''] + deps)
                d['run_depends'] = d['build_depends']

    # Build some entry-point tests.
    if "build" in spec:
        if platform == "darwin":
            our_plat = "macosx"
        elif platform == "linux":
            our_plat = "unix"

        modules = None
        if "modules" in spec['build']:
            modules = spec['build']["modules"]
        elif "platforms" in spec['build']:
            if our_plat in spec['build']['platforms']:
                if "modules" in spec['build']['platforms'][our_plat]:
                    modules = spec['build']['platforms'][our_plat]["modules"]
        if modules:
            d['test_commands'] = INDENT.join([''] +
                            ["""lua -e "require '%s'\"""" % r
                            for r in modules.keys()])

    # If we didn't find any modules to import, import the base name
    if d['test_commands'] == "":
        d['test_commands'] = INDENT.join([''] + ["""lua -e "require '%s'" """ % d['rockname']])

    # Build the luarocks skeleton
    os.chdir(cwd)
    for package in package_dicts:
        d = package_dicts[package]
        name = d['packagename']
        os.makedirs(os.path.join(output_dir, name))
        print("Writing recipe for %s to %s" % (package.lower(), os.path.join(output_dir, name)))
        with open(os.path.join(output_dir, name, 'meta.yaml'), 'w') as f:
            f.write(LUAROCKS_META.format(**d))
        with open(os.path.join(output_dir, name, 'build.sh'), 'w') as f:
            f.write(LUAROCKS_BUILD_SH.format(**d))
        with open(os.path.join(output_dir, name, 'post-link.sh'), 'w') as f:
            f.write(LUAROCKS_POSTLINK_SH)
        with open(os.path.join(output_dir, name, 'pre-unlink.sh'), 'w') as f:
            f.write(LUAROCKS_PREUNLINK_SH.format(**d))
