#!/usr/bin/python3
import argparse
import getpass
import os
import sys
import shutil
import subprocess
from datetime import datetime
from urllib.request import urlretrieve
from zipfile import ZipFile

NUSPEC_TEMPLATE = """<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
  <metadata>
    <id>{package_name}</id>
    <authors>{author}</authors>
    <owners>{owner}</owners>
    <licenseUrl>{license_url}</licenseUrl>
    <projectUrl>{project_url}</projectUrl>
    <iconUrl>{icon_url}</iconUrl>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>{description}.</description>
    <copyright>{copyright}</copyright>
    <tags>{tags}</tags>
    <version>{version}</version>
    <dependencies>
{dependencies}  </dependencies>
  </metadata>
  <files>
{files}  </files>
</package>
"""

TARGETS_TEMPLATE = r"""<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="{package_name}CopyMapConfigs" AfterTargets="AfterBuild">
    <CreateItem Include="$(MSBuildThisFileDirectory)\{frameworkdir}\*.config">
      <Output TaskParameter="Include" ItemName="MapConfigs" />
    </CreateItem>

    <Copy SourceFiles="@(MapConfigs)" DestinationFiles="@(MapConfigs->'$(OutDir)\%(RecursiveDir)%(Filename)%(Extension)')" />
  </Target>
</Project>"""


class Nugetifier:
    def cleanup_args(self):
        self.nugetdir = os.path.join(self.builddir,
                                     self.package_name + 'nupkg')
        self.frameworkdir = 'net45'
        self.nuget_build_dir = os.path.join(
            self.nugetdir, 'build', self.frameworkdir)
        self.nuget_lib_dir = os.path.join(
            self.nugetdir, 'lib', self.frameworkdir)
        self.nuspecfile = os.path.join(
            self.nugetdir, '%s.nuspec' % self.package_name)
        self.nugettargets = os.path.join(
            self.nuget_build_dir, "%s.targets" % self.package_name)
        self.nuget = shutil.which('nuget')
        if not self.nuget:
            print("Could not find the `nuget` tool, install it and retry!")
            return -1

        for d in [self.nugetdir, self.nuget_lib_dir, self.nuget_build_dir]:
            os.makedirs(d, exist_ok=True)
        if not self.description:
            self.description = "%s c# bindings" % self.package_name
        if not self.copyright:
            self.copyright = "Copyright %s" % datetime.now().year
        if not self.tags:
            self.tags = self.package_name

        return 0

    def run(self):
        res = self.cleanup_args()
        if res:
            return res

        self.files = ''

        def add_file(path, target="lib"):
            f = '    <file src="%s" target="%s"/>\n' % (
                path, os.path.join(target, os.path.basename(path)))
            self.files += f

        self.dependencies = ''
        for dependency in self.dependency:
            _id, version = dependency.split(":")
            self.dependencies += '    <dependency id="%s" version="%s" />\n' % (
                _id, version)

        for assembly in self.assembly:
            add_file(assembly, os.path.join('lib', self.frameworkdir))

            for f in [assembly + '.config', assembly[:-3] + 'pdb']:
                if os.path.exists(f):
                    add_file(f, os.path.join('build', self.frameworkdir))

        with open(self.nugettargets, 'w') as _:
            print(TARGETS_TEMPLATE.format(**self.__dict__), file=_)
            add_file(self.nugettargets, 'build')

        with open(self.nuspecfile, 'w') as _:
            print(NUSPEC_TEMPLATE.format(**self.__dict__), file=_)

        subprocess.check_call([self.nuget, 'pack', self.nuspecfile],
                              cwd=self.builddir)


class NugetDownloader:
    def reporthook(self, blocknum, blocksize, totalsize):
        readsofar = blocknum * blocksize
        if totalsize > 0:
            percent = readsofar * 1e2 / totalsize
            s = "\r%5.1f%% %*d / %d" % (
                percent, len(str(totalsize)), readsofar, totalsize)
            sys.stderr.write(s)
            if readsofar >= totalsize:  # near the end
                sys.stderr.write("\n")
        else:  # total size is unknown
            sys.stderr.write("read %d\n" % (readsofar,))

    def run(self):
        url = "https://www.nuget.org/api/v2/package/{nuget_name}/{nuget_version}".format(
            **self.__dict__)
        workdir = os.path.join(self.current_builddir,
                               self.nuget_name, self.nuget_version)
        os.makedirs(workdir, exist_ok=True)

        try:
            with open(os.path.join(workdir, 'linkline'), 'r') as f:
                print(f.read())
            return
        except FileNotFoundError:
            pass
        nugetpath = os.path.join(workdir, self.nuget_name) + '.zip'
        print("Downloading %s into %s" % (url, nugetpath), file=sys.stderr)
        urlretrieve(url, nugetpath, self.reporthook)

        lib_path = os.path.join('lib', self.csharp_version)
        dll_path = os.path.join(self.nuget_name, self.nuget_version)
        extract_dir = os.path.join(self.current_builddir, dll_path)
        os.makedirs(extract_dir, exist_ok=True)
        linkline = ''

        print("%s - %s" % (self.builddir, extract_dir), file=sys.stderr)
        with ZipFile(nugetpath) as zip:
            for f in zip.infolist():
                if f.filename.startswith(lib_path):
                    zip.extract(f, path=extract_dir)
                    if f.filename.endswith('.dll'):
                        linkline += ' -r:' + \
                            os.path.relpath(os.path.join(
                                extract_dir, f.filename), self.builddir)

        with open(os.path.join(workdir, 'linkline'), 'w') as f:
            print(linkline.strip(), file=f)

        print(linkline.strip())


if __name__ == "__main__":
    if "get" not in sys.argv:
        parser = argparse.ArgumentParser()
        parser.add_argument('--builddir')
        parser.add_argument('--package-name')
        parser.add_argument('--author', default=getpass.getuser())
        parser.add_argument('--owner', default=getpass.getuser())
        parser.add_argument('--native', action='append', default=[])
        parser.add_argument('--assembly', action='append', default=[])
        parser.add_argument('--out')
        parser.add_argument('--description')
        parser.add_argument('--copyright')
        parser.add_argument('--version')
        parser.add_argument('--icon-url', default='')
        parser.add_argument('--project-url', default='')
        parser.add_argument('--license-url', default='')
        parser.add_argument('--tags', default='')
        parser.add_argument('--dependency', default=[], action='append')

        runner = Nugetifier()
    else:
        sys.argv.remove('get')
        parser = argparse.ArgumentParser()
        parser.add_argument('--builddir')
        parser.add_argument('--current-builddir')
        parser.add_argument('--nuget-name')
        parser.add_argument('--nuget-version')
        parser.add_argument('--csharp-version')

        runner = NugetDownloader()

    options = parser.parse_args(namespace=runner)
    exit(runner.run())
