# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

"""
This script implements the `mach devtools-css-db` command. It runs an xpcshell script
that uses inIDOMUtils to query the CSS properties used by the browser. This information
is used to generate the properties-db.js file.
"""

import json
import os
import sys
import string
import subprocess
from mozbuild import shellutil
from mozbuild.base import (
    MozbuildObject,
    MachCommandBase,
)
from mach.decorators import (
    CommandProvider,
    Command,
)

def resolve_path(start, relativePath):
    """Helper to resolve a path from a start, and a relative path"""
    return os.path.normpath(os.path.join(start, relativePath))

def stringify(obj):
    """Helper to stringify to JSON"""
    return json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '))

@CommandProvider
class MachCommands(MachCommandBase):
    @Command(
        'devtools-css-db', category='post-build',
        description='Rebuild the devtool\'s static css properties database.')
    def generate_css_db(self):
        """Generate the static css properties database for devtools and write it to file."""

        print("Re-generating the css properties database...")
        preferences = self.get_preferences()
        db = self.get_properties_db_from_xpcshell()

        self.output_template({
            'preferences': stringify(preferences),
            'cssProperties': stringify(db['cssProperties']),
            'pseudoElements': stringify(db['pseudoElements'])})

    def get_preferences(self):
        """Get all of the preferences associated with enabling and disabling a property."""
        # Build the command to run the preprocessor on PythonCSSProps.h
        headerPath = resolve_path(self.topsrcdir, 'layout/style/PythonCSSProps.h')

        cpp = self.substs['CPP']

        if not cpp:
            print("Unable to find the cpp program. Please do a full, nonartifact")
            print("build and try this again.")
            sys.exit(1)

        if type(cpp) is list:
            cmd = cpp
        else:
            cmd = shellutil.split(cpp)
        cmd += shellutil.split(self.substs['ACDEFINES'])
        cmd.append(headerPath)

        # The preprocessed list takes the following form:
        # [ (name, prop, id, flags, pref, proptype), ... ]
        preprocessed = eval(subprocess.check_output(cmd))

        # Map this list
        # (name, prop, id, flags, pref, proptype) => (name, pref)
        preferences = [
            (name, pref)
            for name, prop, id, flags, pref, proptype in preprocessed
            if 'CSS_PROPERTY_INTERNAL' not in flags and pref]

        return preferences

    def get_properties_db_from_xpcshell(self):
        """Generate the static css properties db for devtools from an xpcshell script."""
        build = MozbuildObject.from_environment()

        # Get the paths
        script_path = resolve_path(self.topsrcdir,
            'devtools/shared/css/generated/generate-properties-db.js')
        gre_path = resolve_path(self.topobjdir, 'dist/bin')
        browser_path = resolve_path(self.topobjdir, 'dist/bin/browser')
        xpcshell_path = build.get_binary_path(what='xpcshell')
        print(browser_path)

        sub_env = dict(os.environ)
        if sys.platform.startswith('linux'):
            sub_env["LD_LIBRARY_PATH"] = gre_path

        # Run the xcpshell script, and set the appdir flag to the browser path so that
        # we have the proper dependencies for requiring the loader.
        contents = subprocess.check_output([xpcshell_path, '-g', gre_path,
                                            '-a', browser_path, script_path],
                                           env = sub_env)
        # Extract just the output between the delimiters as the xpcshell output can
        # have extra output that we don't want.
        contents = contents.split('DEVTOOLS_CSS_DB_DELIMITER')[1]

        return json.loads(contents)

    def output_template(self, substitutions):
        """Output a the properties-db.js from a template."""
        js_template_path = resolve_path(self.topsrcdir,
            'devtools/shared/css/generated/properties-db.js.in')
        destination_path = resolve_path(self.topsrcdir,
            'devtools/shared/css/generated/properties-db.js')

        with open(js_template_path, 'rb') as handle:
            js_template = handle.read()

        preamble = '/* THIS IS AN AUTOGENERATED FILE.  DO NOT EDIT */\n\n'
        contents = string.Template(js_template).substitute(substitutions)

        with open(destination_path, 'wb') as destination:
            destination.write(preamble + contents)

        print('The database was successfully generated at ' + destination_path)
