import sys, os, base64, struct, time
from types import TupleType
import distutils
from distutils.command.bdist_wininst import bdist_wininst
from distutils.dir_util import create_tree, remove_tree

# (display, metadata_attr) or (display, [attr1, attr2...])
_metadata_names = [('Name', 'name'),
                   ('Version', 'version'),
                   ('Summary', 'description'),
                   ('Homepage', 'url'),
                   ('Author', ('maintainer', 'author')),
                   ('Email', ('maintainer_email', 'author_email')),
                   ('License', ('license', 'licence')),
                   ]

# This is, for the most part, the wininst command from Python 2.1.2 and 2.2
# modified to handle our installation scheme and run with Python 2.0 and 2.1

class BDistWininst(bdist_wininst):

    command_name = 'bdist_wininst'

    def initialize_options(self):
        # Features missing form distutils prior Python 2.1.2
        self.title = None
        self.bitmap = None
        return bdist_wininst.initialize_options(self)

    def finalize_options(self):
        if self.bdist_dir is None:
            bdist_base = self.get_finalized_command('bdist').bdist_base
            self.bdist_dir = os.path.join(bdist_base,
                                          'wininst-%s' % sys.version[:3])
        return bdist_wininst.finalize_options(self)

    def run(self):
        if (sys.platform != "win32" and
            (self.distribution.has_ext_modules() or
             self.distribution.has_c_libraries())):
            raise DistutilsPlatformError \
                  ("distribution contains extensions and/or C libraries; "
                   "must be compiled on a Windows 32 platform")

        self.run_command('build')

        install = self.reinitialize_command('install')
        install.root = self.bdist_dir
        install.warn_dir = 0
        install.with_docs = 1

        # Use a custom scheme for the zip-file, because we have to decide
        # at installation time which scheme to use.
        install.install_platlib = install.install_purelib = 'PURELIB'
        install.install_headers = 'HEADERS/Include/$dist_name'

        install_lib = self.reinitialize_command('install_lib')
        # we do not want to include pyc or pyo files
        install_lib.compile = 0
        install_lib.optimize = 0

        self.announce("installing to %s" % self.bdist_dir)
        install.ensure_finalized()

        install.run()

        # And make an archive relative to the root of the
        # pseudo-installation tree.
        from tempfile import mktemp
        archive_basename = mktemp()
        fullname = self.distribution.get_fullname()
        arcname = self.make_archive(archive_basename, "zip",
                                    root_dir=self.bdist_dir)
        # create an exe containing the zip-file
        self.create_exe(arcname, fullname, self.bitmap)
        # remove the zip-file again
        self.announce("removing temporary file '%s'" % arcname)
        os.remove(arcname)

        if not self.keep_temp:
            remove_tree(self.bdist_dir, self.verbose, self.dry_run)
        return

    def create_exe (self, arcname, fullname, bitmap=None):

        self.mkpath(self.dist_dir)

        cfgdata = self.get_inidata()

        if self.target_version:
            # if we create an installer for a specific python version,
            # it's better to include this in the name
            installer_name = os.path.join(self.dist_dir,
                                          "%s.win32-py%s.exe" %
                                           (fullname, self.target_version))
        else:
            installer_name = os.path.join(self.dist_dir,
                                          "%s.win32.exe" % fullname)
        self.announce("creating %s" % installer_name)

        if bitmap:
            bitmapdata = open(bitmap, "rb").read()
            bitmaplen = len(bitmapdata)
        else:
            bitmaplen = 0

        file = open(installer_name, "wb")
        file.write(base64.decodestring(EXEDATA))
        if bitmap:
            file.write(bitmapdata)

        file.write(cfgdata)
        header = struct.pack("<iii",
                             0x1234567A,       # tag
                             len(cfgdata),     # length
                             bitmaplen,        # number of bytes in bitmap
                             )
        file.write(header)
        file.write(open(arcname, "rb").read())
        return

    def get_inidata(self):
        # Return data describing the installation.

        lines = []
        metadata = self.distribution.metadata

        # Write the [metadata] section.  Values are written with
        # repr()[1:-1], so they do not contain unprintable characters, and
        # are not surrounded by quote chars.
        lines.append("[metadata]")

        # 'info' will be displayed in the installer's dialog box,
        # describing the items to be installed.
        info = (metadata.long_description or '') + '\n'

        for display, names in _metadata_names:
            if isinstance(names, TupleType):
                data = None
                for name in names:
                    data = getattr(metadata, name, '')
                    if data:
                        break
            else:
                name = names
                data = getattr(metadata, name, '')
            if data:
                info = info + ("    %s: %s\n" % \
                               (display, data))
                lines.append("%s=%s" % (name, repr(data)[1:-1]))

        # The [setup] section contains entries controlling
        # the installer runtime.
        lines.append("\n[Setup]")
        lines.append("info=%s" % repr(info)[1:-1])
        lines.append("target_compile=%d" % (not self.no_target_compile))
        lines.append("target_optimize=%d" % (not self.no_target_optimize))
        if self.target_version:
            lines.append("target_version=%s" % self.target_version)

        title = self.title or self.distribution.get_fullname()
        lines.append("title=%s" % repr(title)[1:-1])
        build_info = "Build %s with distutils-%s" % \
                     (time.ctime(time.time()), distutils.__version__)
        lines.append("build_info=%s" % build_info)
        return '\n'.join(lines)

if __name__ == '__main__':
    # recreate EXEDATA from wininst.exe by rewriting this file
    import re, base64
    moddata = open("bdist_wininst.py", "r").read()
    exedata = open("../../misc/wininst.exe", "rb").read()
    print "wininst.exe length is %d bytes" % len(exedata)
    print "wininst.exe encoded length is %d bytes" % len(base64.encodestring(exedata))
    exp = re.compile('EXE'+'DATA = """\\\\(\n.*)*\n"""', re.M)
    data = exp.sub('EXE' + 'DATA = """\\\\\n%s"""' %
                    base64.encodestring(exedata), moddata)
    open("bdist_wininst.py", "w").write(data)
    print "bdist_wininst.py recreated"

EXEDATA = """\
[encoded data, 25564 characters]
AAAAAAAAAAAAAAAAAAAAAAAAAAA=
"""

# --- EOF ---
