#
# This module overrides distutils (also compatible with setuptools) "sdist"
# command to perform pre- and post-processing as required for MicroPython's
# upip package manager.
#
# Preprocessing steps:
#  * Creation of Python resource module (R.py) from each top-level package's
#    resources.
# Postprocessing steps:
#  * Removing metadata files not used by upip (this includes setup.py)
#  * Recompressing gzip archive with 4K dictionary size so it can be
#    installed even on low-heap targets.
#
import sys
import os
import zlib
from subprocess import Popen, PIPE
import glob
import tarfile
import re
import io

from distutils.filelist import FileList
from setuptools.command.sdist import sdist as _sdist


def gzip_4k(inf, fname):
    comp = zlib.compressobj(level=9, wbits=16 + 12)
    with open(fname + ".out", "wb") as outf:
        while 1:
            data = inf.read(1024)
            if not data:
                break
            outf.write(comp.compress(data))
        outf.write(comp.flush())
    os.rename(fname, fname + ".orig")
    os.rename(fname + ".out", fname)


FILTERS = [
    # include, exclude, repeat
    (r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"),
    (r".+\.py$", r"[^/]+$"),
    (None, r".+\.egg-info/.+"),
]


outbuf = io.BytesIO()

def filter_tar(name):
    fin = tarfile.open(name, "r:gz")
    fout = tarfile.open(fileobj=outbuf, mode="w")
    for info in fin:
#        print(info)
        if not "/" in info.name:
            continue
        fname = info.name.split("/", 1)[1]
        include = None

        for inc_re, exc_re in FILTERS:
            if include is None and inc_re:
                if re.match(inc_re, fname):
                    include = True

            if include is None and exc_re:
                if re.match(exc_re, fname):
                    include = False

        if include is None:
            include = True

        if include:
            print("including:", fname)
        else:
            print("excluding:", fname)
            continue

        farch = fin.extractfile(info)
        fout.addfile(info, farch)
    fout.close()
    fin.close()


def make_resource_module(manifest_files):
        resources = []
        # Any non-python file included in manifest is resource
        for fname in manifest_files:
            ext = fname.rsplit(".", 1)[-1]
            if ext != "py":
                resources.append(fname)

        if resources:
            print("creating resource module R.py")
            resources.sort()
            last_pkg = None
            r_file = None
            for fname in resources:
                try:
                    pkg, res_name = fname.split("/", 1)
                except ValueError:
                    print("not treating %s as a resource" % fname)
                    continue
                if last_pkg != pkg:
                    last_pkg = pkg
                    if r_file:
                        r_file.write("}\n")
                        r_file.close()
                    r_file = open(pkg + "/R.py", "w")
                    r_file.write("R = {\n")

                with open(fname, "rb") as f:
                    r_file.write("%r: %r,\n" % (res_name, f.read()))

            if r_file:
                r_file.write("}\n")
                r_file.close()


class sdist(_sdist):

    def run(self):
        self.filelist = FileList()
        self.get_file_list()
        make_resource_module(self.filelist.files)

        r = super().run()

        assert len(self.archive_files) == 1
        print("filtering files and recompressing with 4K dictionary")
        filter_tar(self.archive_files[0])
        outbuf.seek(0)
        gzip_4k(outbuf, self.archive_files[0])

        return r


# For testing only
if __name__ == "__main__":
    filter_tar(sys.argv[1])
    outbuf.seek(0)
    gzip_4k(outbuf, sys.argv[1])
