#!/usr/bin/env python

# Copyright 2015-2016 Samsung Electronics Co., Ltd.
# Copyright (c) 2016 ARM Limited. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import glob
import os
import re
import subprocess
import sys

def extractName(path):
    return os.path.splitext(os.path.basename(path))[0]

def writeLine(fo, content, indent=0):
    buf = '  ' * indent + content + '\n'
    fo.write(buf)

def regroup(l, n):
    return [ l[i:i+n] for i in range(0, len(l), n) ]

def removeComments(code):
    pattern = r'(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)'
    regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
    def _replacer(match):
        if match.group(2) is not None:
            return ""
        else:
            return match.group(1)
    return regex.sub(_replacer, code)

def removeWhitespaces(code):
    return re.sub('\n+', '\n', re.sub('\n +', '\n', code))


LICENSE = '''/* Copyright 2015-2016 Samsung Electronics Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the \"License\");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an \"AS IS\" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * This file is generated by js2c.py. Please do not modify.
 */

'''

HEADER = '''#include "jsmbed_js_source.h"

'''

OUT_PATH = './source/'
SRC_PATH = './js/'

parser = argparse.ArgumentParser()
parser.add_argument("-b", "--buildtype")
parser.add_argument("-m", "--mcu")
args = parser.parse_args();

buildtype = 'release'
if args.buildtype:
    buildtype = args.buildtype

# TODO: follow the same logic the mbed build tools use to determine the correct location
#       for a given MCU name (i.e., check the classes in targets.py)
if args.mcu:
    cmd = "find {} -iname \"{}\"".format(SRC_PATH + "pin_defs", "TARGET_" + args.mcu)
    intended_target = subprocess.check_output(cmd, shell = True).strip()
    if intended_target == "":
        cmd = "find {} -iname \"{}\"".format(SRC_PATH + "pin_defs", "TARGET_MCU_" + args.mcu)
        intended_target = subprocess.check_output(cmd, shell=True).strip()
    if intended_target == "":
        print("ERROR: Cannot find pins.js file for provided MCU.")
        sys.exit(1)
    if "\n" in intended_target:
        print("ERROR: Found multiple targets with the provided MCU name.")
        print("Found targets:")
        print(intended_target)
        sys.exit(1)

    os.system("cp {}/pins.js js/pins.js".format(intended_target))
    print("Updated pins.js")

jshint_is_installed = False
try:
    with open("/dev/null", "w") as dev_null:
        subprocess.check_call("which jshint", shell=True, stdout=dev_null)
        jshint_is_installed = True
except:
    print("No JSHint detected, cannot statically check if PinNames you use are valid for your platform.")
    print("Consider installing JSHint for this feature.")

if jshint_is_installed:
    try:
        subprocess.check_call("tools/check_pins.sh")
    except:
        print("JSHint picked up an undefined variable in your code.")
        print("Are you using a PinName that doesn't exist for your platform?")
        sys.exit(1)

fout = open(OUT_PATH + 'js_encoded.cpp', 'w')
fout.write(LICENSE);
fout.write(HEADER);

pins = list()
pin_def_re = re.compile("var ([a-zA-Z_0-9]+)\s*=\s*([^;]*);")

def exportOneFile(path, name):
    # Prepend pins.js onto main.js, don't include pins.js itself.
    code = ""
    if name == "pins":
        global pins, pin_def_re
        fin = open(path, "r")
        for line in fin.readlines():
            m = pin_def_re.search(line.strip())
            if m:
                pins.append((m.group(1), m.group(2)))
        fin.close()
        return

    fout.write('const static char ' + name + '_n[] = "' + name + '";\n')
    fout.write('const static char ' + name + '_s[] =\n{\n')

    fin = open(path, 'r');
    code += fin.read() + '\0'

    # minimize code when release mode
    if buildtype != 'debug':
        code = removeComments(code)
        code = removeWhitespaces(code)

    for line in regroup(code, 10):
        buf = ', '.join(map(lambda ch: format(ord(ch),"#04x"), line))
        if line[-1] != '\0':
            buf += ','
        writeLine(fout, buf, 1)
    writeLine(fout, '};')
    writeLine(fout, 'const static int ' + name + '_l = ' + str(len(code)-1) + ';')
    writeLine(fout, '')

    fin.close();

def exportOneName(name):
    writeLine(fout, '{ ' + name + '_n, ' + name + '_s, ' + name + '_l },', 1)

files = glob.glob(SRC_PATH + '*.js')
for path in files:
    name = extractName(path)
    exportOneFile(path, name)


NATIVE_STRUCT = '''
struct jsmbed_js_source_t jsmbed_js_code_storage[] =
{
'''

fout.write(NATIVE_STRUCT)
exportOneName('main')
filenames = sorted(map(extractName, files), key=str.lower)
for name in filenames:
    if name != 'main' and name != 'pins':
        exportOneName(name)

writeLine(fout, '{ 0, 0, 0 }', 1)
writeLine(fout, '};')

pin_count = len(pins)
writeLine(fout, 'unsigned int jsmbed_js_magic_string_count = {};'.format(pin_count))

writeLine(fout, 'const char *jsmbed_js_magic_strings[] = {')
comma = ","

for (idx, (pinname, value)) in enumerate(pins):
    if idx == (pin_count-1):
        comma = ""
    writeLine(fout, '  "{}"{}'.format(pinname, comma), 1)
writeLine(fout, '};')

writeLine(fout, 'unsigned int jsmbed_js_magic_string_lengths[] = {')
comma = ","

for (idx, (pinname, value)) in enumerate(pins):
    if idx == (pin_count-1):
        comma = ""
    writeLine(fout, '  {}{}'.format(len(pinname), comma), 1)
writeLine(fout, '};')

writeLine(fout, 'unsigned int jsmbed_js_magic_string_values[] = {')
comma = ","

for (idx, (pinname, value)) in enumerate(pins):
    if idx == (pin_count-1):
        comma = ""
    writeLine(fout, '  {}{}'.format(value, comma), 1)
writeLine(fout, '};')

fout.close()