"""
Functionality for creating JS modules of various formats, including AMD and UMD.
"""

import re


# Immediately Invoked Function Expression (IIFE)
HIDDEN = """
(function () {

"use strict";

{code}

})();
""".lstrip()


SIMPLE = """
(function (root, factory) {
    root.{save_name} = factory();
}(this, function () {

"use strict";

{code}

return {exports};
}));
""".lstrip()


AMD = """
define("{name}", [{dep_strings}], function ({dep_names}) {

"use strict";

{code}

return {exports};
});
""".lstrip()

AMD_FLEXX = "flexx." + AMD


# https://github.com/umdjs/umd/blob/master/returnExports.js
UMD = """
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define("{name}", [{dep_strings}], factory);
    } else if (typeof exports !== 'undefined') {
        // Node or CommonJS
        module.exports = factory({dep_requires});
        if (typeof window === 'undefined') {
            root.{save_name} = module.exports;  // also create global module in Node
        }
    } else {
        // Browser globals (root is window)
        root.{save_name} = factory({dep_fullnames});
    }
}(this, function ({dep_names}) {

"use strict";

{code}

return {exports};
})); 
""".lstrip()


def isidentifier(s):
    # http://stackoverflow.com/questions/2544972/
    if not isinstance(s, str):
        return False
    return re.match(r'^\w+$', s, re.UNICODE) and re.match(r'^[0-9]', s) is None


def create_js_module(name, code, imports, exports, type='umd'):
    """ Wrap the given code in an AMD module.
    
    Note that "use strict" is added to the top of the module body. PScript
    does not deal with license strings; the caller should do that.
    
    Parameters:
        name (str): the name of the module.
        code (str): the JS code to wrap.
        imports (list): the imports for this module, as string names of the
            dependencies. Optionally, 'as' can  be used to make a dependency
            available under a specific name (e.g. 'foo.js as foo').
        exports (str, list): the result of this module (i.e. what other modules
            get when they import this module. Can be a JS expression or a list
            of names to export.
        type (str): the type of module to export, valid values are
            'hidden', 'simple' (save module on root), 'amd' , 'amd-flexx' and
            'umd' (case insensitive). Default 'umd'.
    """
    
    # Check input args
    if not isinstance(name, str) or not name:
        raise ValueError('create_module() name arg must be a (nonempty) string.')
    if not isinstance(code, str):
        raise ValueError('create_module() code arg must be a string.')
    if not isinstance(imports, (tuple, list)):
        raise ValueError('create_module() imports arg must be a string.')
    if not isinstance(exports, (str, tuple, list)):
        raise ValueError('create_module() exports arg must be a string or list.')
    
    # Process imports
    deps, dep_names = [], []
    for imp in imports:
        if not isinstance(imp, str):
            raise ValueError('Elements in create_module() imports must be str.')
        if ' as ' in imp:
            dep, dep_name = imp.split(' as ', 1)
        else:
            dep = dep_name = imp
        if '.' in dep_name:
            raise ValueError('Import %r has dots, have you used "as"?' % dep_name)
        deps.append(dep)
        dep_names.append(dep_name)
    
    # Process exports
    if isinstance(exports, str):
        return_val = exports
    else:  # list
        for exp in exports:
            if not isinstance(exp, str):
                raise ValueError('Elements in create_module() exports must be str.')
        return_val = ', '.join(['%s: %s' % (exp, exp) for exp in exports])
        return_val = '{' + return_val + '}'
    
    # Process type -> select template
    types = {'hidden': HIDDEN, 'simple': SIMPLE,
             'amd': AMD, 'umd': UMD, 'amd-flexx': AMD_FLEXX}
    if not isinstance(type, str):
        raise ValueError('create_js_module() type must be str.')
    if type.lower() not in types:
        raise ValueError('create_js_module() got invalid type %r' % type)
    template = types[type.lower()]
    
    # Derived information needed to populate the module templates
    save_name = lambda n: n.split('/')[-1].split('.')[0].replace('-', '_')
    dep_strings = ['"%s"' % dep for dep in deps]
    dep_fullnames = ['root.' + save_name(dep) for dep in deps]
    dep_requires = ['require("%s")' % dep for dep in deps]
    
    # Fill in the template
    for key, val in [('{name}', name),
                     ('{save_name}', save_name(name)),
                     ('{exports}', return_val),
                     ('{dep_names}', ', '.join(dep_names)),
                     ('{dep_strings}', ', '.join(dep_strings)),
                     ('{dep_fullnames}', ', '.join(dep_fullnames)),
                     ('{dep_requires}', ', '.join(dep_requires)),
                     ('{code}', code),  # last!
                    ]:
        template = template.replace(key, val)
    
    return template
