#! /usr/bin/env python

import os
import sys
import shutil
import platform
import tempfile
import zipfile
from optparse import OptionParser
import subprocess

try:
    from hashlib import sha1 as sha
except ImportError:
    from sha import sha

usage = """
This command creates a Firefox XPI installer for the Panda3D Firefox
plugin.  Also see make_installer.py.

  %prog [opts]"""

parser = OptionParser(usage = usage)
parser.add_option('-v', '--version', dest = 'version',
                  help = 'The product version',
                  default = None)
parser.add_option('-i', '--plugin_root', dest = 'plugin_root',
                  help = 'The root of a directory hierarchy in which the Firefox plugins for various platforms can be found, to build a Firefox xpi file.  This is normally the same as the staging directory populated by the -i parameter to ppackage.  This directory should contain a directory called "plugin", which contains in turn a number of directories named for the platform, by the Panda plugin convention, e.g. linux_i386, osx_ppc, and so on.  Each platform directory should contain a Firefox plugin, e.g. nppanda3d.so.')
parser.add_option('', '--host_url', dest = 'host_url',
                  help = "The URL at which plugin_root will be hosted.  This is used to construct the update URL for the xpi file.  This is required if you specify --plugin_root.")

(options, args) = parser.parse_args()

this_dir = os.path.split(sys.argv[0])[0]

assert options.version, "A version number must be supplied!"
assert options.plugin_root, "The plugin_root must be supplied!"
assert options.host_url, "The host_url must be supplied!"

# A mapping of Panda's platform strings to Firefox's equivalent
# strings.

# I'm leaving out the Linux platforms for now.  I think there is too
# much variance between distro's for this to be reliable; we'll make
# each Linux user install their distro-specific plugin instead of
# going through this mechanism.
FirefoxPlatformMap = {
    'win32' : 'WINNT_x86-msvc',
    'win64' : 'WINNT_x86_64-msvc',
#    'linux_i386' : 'Linux_x86-gcc3',
#    'linux_amd64' : 'Linux_x86_64-gcc3',
#    'linux_ppc' : 'Linux_ppc-gcc3',
    'osx_i386' : 'Darwin_x86-gcc3',
    'osx_amd64' : 'Darwin_x86_64-gcc3',
    'osx_ppc' : 'Darwin_ppc-gcc3',
    'freebsd_i386' : 'FreeBSD_x86-gcc3',
    'freebsd_amd64' : 'FreeBSD_x86_64-gcc3',
    }

##############################################################################
#
# This install.rdf file is used when building a Firefox XPI file.
#
##############################################################################

install_rdf = """<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
  <Description about="urn:mozilla:install-manifest">
    <em:id>%(package_id)s</em:id>
    <em:unpack>true</em:unpack>
    <em:name>Panda3D Game Engine Plug-In</em:name>
    <em:description>Runs 3-D games and interactive applets</em:description>
    <em:version>%(version)s</em:version>
    <em:targetApplication>
      <Description>
        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
        <em:minVersion>3.0</em:minVersion>
        <em:maxVersion>*</em:maxVersion>
      </Description>
    </em:targetApplication>
    <em:homepageURL>http://www.panda3d.org/</em:homepageURL>
    <em:updateURL>%(host_url)s/plugin/firefox/update.rdf</em:updateURL>
  </Description>
</RDF>
"""

##############################################################################
#
# This update.rdf file is used when building a Firefox XPI file.
#
##############################################################################

update_rdf = """<?xml version="1.0"?>
<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns:em="http://www.mozilla.org/2004/em-rdf#">

  <RDF:Description about="urn:mozilla:extension:%(package_id)s">
    <em:updates>
      <RDF:Seq>
        <RDF:li>
          <RDF:Description>
            <em:version>%(version)s</em:version>
            <em:targetApplication>
              <RDF:Description>
                <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
                <em:minVersion>3.0</em:minVersion>
                <em:maxVersion>*</em:maxVersion>
                <em:updateLink>%(host_url)s/plugin/firefox/nppanda3d.xpi</em:updateLink>
                <em:updateHash>sha1:%(xpi_hash)s</em:updateHash>
              </RDF:Description>
            </em:targetApplication>
          </RDF:Description>
        </RDF:li>
      </RDF:Seq>
    </em:updates>
  </RDF:Description>
</RDF:RDF>
"""

def makeXpiFile():
    """ Creates a Firefox XPI file, based on the various platform
    version files. """

    if not options.host_url:
        print "Cannot generate xpi file without --host-url."
        sys.exit(1)

    print "Generating xpi file"
    root = options.plugin_root
    if os.path.isdir(os.path.join(root, 'plugin')):
        root = os.path.join(root, 'plugin')

    xpi = zipfile.ZipFile('nppanda3d.xpi', 'w')

    package_id = 'runtime@panda3d.org' #TODO: maybe more customizable?

    tempFile = tempfile.mktemp('.txt', 'p3d_')
    rdf = open(tempFile, 'w')
    rdf.write(install_rdf % {
        'package_id' : package_id,
        'version' : options.version,
        'host_url' : options.host_url,
        })
    rdf.close()
    xpi.write(tempFile, 'install.rdf')
    os.unlink(tempFile)

    subdirs = os.listdir(root)
    for subdir in subdirs:
        platform = FirefoxPlatformMap.get(subdir, None)
        path = os.path.join(root, subdir)
        if platform and os.path.isdir(path):
            if subdir in ['win32', 'osx_i386']:
                pluginsXpiDir = 'plugins'
            else:
                # Create the XPI directory platform/<platform name>/plugins
                pluginsXpiDir = 'platform/%s/plugins' % (platform)

            # Copy the Firefox plugin into this directory.
            if subdir.startswith('win32'):
                pluginFilename = 'nppanda3d.dll'
            elif subdir.startswith('osx'):
                pluginFilename = 'nppanda3d.plugin'
            else:
                pluginFilename = 'nppanda3d.so'

            addZipTree(xpi, os.path.join(path, pluginFilename),
                       pluginsXpiDir + '/' + pluginFilename)
    xpi.close()

    # Now that we've generated the xpi file, get its hash.
    data = open('nppanda3d.xpi', 'rb').read()
    xpi_hash = sha(data).hexdigest()

    # And now we can generate the update.rdf file.
    update = open('update.rdf', 'w')
    update.write(update_rdf % {
        'package_id' : package_id,
        'version' : options.version,
        'host_url' : options.host_url,
        'xpi_hash' : xpi_hash,
        })
    update.close()

def addZipTree(zip, sourceFile, zipName):
    """ Adds the sourceFile to the zip archive at the indicated name.
    If it is a directory, recursively adds all nested files as
    well. """

    if os.path.isdir(sourceFile):
        subdirs = os.listdir(sourceFile)
        for subdir in subdirs:
            addZipTree(zip, os.path.join(sourceFile, subdir),
                       zipName + '/' + subdir)

    else:
        # Not a directory, just add the file.
        zip.write(sourceFile, zipName)

makeXpiFile()

