#!/usr/bin/python

from optparse import OptionParser
import re
import sys

import pystache


ESCAPE_SEQUENCE = {
        '\\': '\\\\',
        '\n': '\\n',
        '\t': '\\t'
        }


def js_escape(s):
    return ''.join(ESCAPE_SEQUENCE.get(c, c) for c in s)


def glsl_compress(s, shortNames):
    # strip leading whitespace
    s = re.sub(r'\A\s+', '', s)
    # strip trailing whitespace
    s = re.sub(r'\s+\Z', '', s)
    # strip multi-line comments
    s = re.sub(r'/\*.*?\*/', '', s)
    # strip single line comments
    s = re.sub(r'//.*?\n', '', s)
    # replace multiple whitespace with a single space
    s = re.sub(r'\s+', ' ', s)
    # remove whitespace between non-word tokens
    s = re.sub(r'(\S)\s+([^\w])', r'\1\2', s)
    s = re.sub(r'([^\w])\s+(\S)', r'\1\2', s)
    # replace original names with short names
    for originalName, shortName in shortNames.items():
        s = s.replace(originalName, shortName)
    return s


def main(argv):
    option_parser = OptionParser()
    option_parser.add_option('--input')
    option_parser.add_option('--output')
    option_parser.add_option('--template')
    options, args = option_parser.parse_args(argv[1:])

    context = {}
    nextShortName = ord('a')
    shortNames = {}

    common, vertex, fragment = [], [], []
    attributes, uniforms, varyings = {}, {}, {}
    block = None
    for line in open(options.input, 'rU'):
        if line.startswith('//!'):
            m = re.match(r'//!\s+NAMESPACE=(\S+)\s*\Z', line)
            if m:
                context['namespace'] = m.group(1)
                continue
            m = re.match(r'//!\s+CLASS=(\S+)\s*\Z', line)
            if m:
                context['className'] = m.group(1)
                continue
            m = re.match(r'//!\s+COMMON\s*\Z', line)
            if m:
                block = common
                continue
            m = re.match(r'//!\s+VERTEX\s*\Z', line)
            if m:
                block = vertex
                continue
            m = re.match(r'//!\s+FRAGMENT\s*\Z', line)
            if m:
                block = fragment
                continue
        else:
            if block is None:
                assert line.rstrip() == ''
            else:
                block.append(line)
            m = re.match(r'attribute\s+\S+\s+(\S+);\s*\Z', line)
            if m:
                attribute = m.group(1)
                if attribute not in attributes:
                    shortName = chr(nextShortName)
                    nextShortName += 1
                    attributes[attribute] = {'originalName': attribute, 'shortName': shortName}
                    shortNames[attribute] = shortName
            m = re.match(r'uniform\s+\S+\s+(\S+);\s*\Z', line)
            if m:
                uniform = m.group(1)
                if uniform not in uniforms:
                    shortName = chr(nextShortName)
                    nextShortName += 1
                    uniforms[uniform] = {'originalName': uniform, 'shortName': shortName}
                    shortNames[uniform] = shortName
            m = re.match(r'varying\s+\S+\s+(\S+);\s*\Z', line)
            if m:
                varying = m.group(1)
                if varying not in varyings:
                    shortName = chr(nextShortName)
                    nextShortName += 1
                    shortNames[varying] = shortName

    context['getOriginalFragmentSource'] = js_escape(''.join(common + fragment))
    context['getOriginalVertexSource'] = js_escape(''.join(common + vertex))
    context['getFragmentSource'] = glsl_compress(''.join(common + fragment), shortNames)
    context['getVertexSource'] = glsl_compress(''.join(common + vertex), shortNames)
    context['getAttributes'] = [attributes[a] for a in sorted(attributes.keys())]
    context['getUniforms'] = [uniforms[u] for u in sorted(uniforms.keys())]

    if options.output and options.output != '-':
        output = open(options.output, 'wb')
    else:
        output = sys.stdout
    output.write(pystache.render(open(options.template, 'rb').read(), context))


if __name__ == '__main__':
    sys.exit(main(sys.argv))
