import os
import zipfile
import simplejson as json
from cuddlefish.util import filter_filenames, filter_dirnames

class HarnessOptionAlreadyDefinedError(Exception):
    """You cannot use --harness-option on keys that already exist in
    harness-options.json"""

ZIPSEP = "/" # always use "/" in zipfiles

def make_zipfile_path(localroot, localpath):
    return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep))

def mkzipdir(zf, path):
    dirinfo = zipfile.ZipInfo(path)
    dirinfo.external_attr = int("040755", 8) << 16L
    zf.writestr(dirinfo, "")

def build_xpi(template_root_dir, manifest, xpi_path,
              harness_options, limit_to=None, extra_harness_options={}):
    zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED)

    open('.install.rdf', 'w').write(str(manifest))
    zf.write('.install.rdf', 'install.rdf')
    os.remove('.install.rdf')

    if 'icon' in harness_options:
        zf.write(str(harness_options['icon']), 'icon.png')
        del harness_options['icon']

    if 'icon64' in harness_options:
        zf.write(str(harness_options['icon64']), 'icon64.png')
        del harness_options['icon64']

    IGNORED_FILES = [".hgignore", ".DS_Store", "install.rdf",
                     "application.ini", xpi_path]

    files_to_copy = {} # maps zipfile path to local-disk abspath
    dirs_to_create = set() # zipfile paths, no trailing slash

    for dirpath, dirnames, filenames in os.walk(template_root_dir):
        filenames = list(filter_filenames(filenames, IGNORED_FILES))
        dirnames[:] = filter_dirnames(dirnames)
        for dirname in dirnames:
            arcpath = make_zipfile_path(template_root_dir,
                                        os.path.join(dirpath, dirname))
            dirs_to_create.add(arcpath)
        for filename in filenames:
            abspath = os.path.join(dirpath, filename)
            arcpath = make_zipfile_path(template_root_dir, abspath)
            files_to_copy[arcpath] = abspath

    new_resources = {}
    for resource in harness_options['resources']:
        new_resources[resource] = ['resources', resource]
        base_arcpath = ZIPSEP.join(['resources', resource])
        # Always write the top directory, even if it contains no files, since
        # the harness will try to access it.
        dirs_to_create.add(base_arcpath)
        abs_dirname = harness_options['resources'][resource]
        # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/
        for dirpath, dirnames, filenames in os.walk(abs_dirname):
            goodfiles = list(filter_filenames(filenames, IGNORED_FILES))
            dirnames[:] = filter_dirnames(dirnames)
            for filename in goodfiles:
                abspath = os.path.join(dirpath, filename)
                if limit_to is not None and abspath not in limit_to:
                    continue  # strip unused files
                arcpath = ZIPSEP.join(
                    ['resources',
                     resource,
                     make_zipfile_path(abs_dirname,
                                       os.path.join(dirpath, filename)),
                     ])
                files_to_copy[str(arcpath)] = str(abspath)
    harness_options['resources'] = new_resources

    # now figure out which directories we need: all retained files parents
    for arcpath in files_to_copy:
        bits = arcpath.split("/")
        for i in range(1,len(bits)):
            parentpath = ZIPSEP.join(bits[0:i])
            dirs_to_create.add(parentpath)

    # create zipfile in alphabetical order, with each directory before its
    # files
    for name in sorted(dirs_to_create.union(set(files_to_copy))):
        if name in dirs_to_create:
            mkzipdir(zf, name+"/")
        if name in files_to_copy:
            zf.write(files_to_copy[name], name)

    harness_options = harness_options.copy()
    for key,value in extra_harness_options.items():
        if key in harness_options:
            msg = "Can't use --harness-option for existing key '%s'" % key
            raise HarnessOptionAlreadyDefinedError(msg)
        harness_options[key] = value
    open('.options.json', 'w').write(json.dumps(harness_options, indent=1,
                                                sort_keys=True))
    zf.write('.options.json', 'harness-options.json')
    os.remove('.options.json')

    zf.close()
