#!/usr/bin/env python3
##########################################################################
#
# Copyright 2010 VMware, Inc.
# All Rights Reserved.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
##########################################################################/

"""Parser for OpenGL .spec files in http://www.opengl.org/registry/."""


import sys
import re
import optparse


def stderr(x):
    sys.stderr.write(str(x) + '\n')


class Parser:

    def __init__(self, stream):
        pass


class LineParser:
    """Base class for parsers that read line-based formats."""

    def __init__(self, stream):
        self._stream = stream
        self._line = None
        self._eof = False
        # read lookahead
        self.readline()
    
    def parse(self):
        raise NotImplementedError

    def readline(self):
        line = self._stream.readline()
        if not line:
            self._line = ''
            self._eof = True
        self._line = line.rstrip('\r\n')

    def lookahead(self):
        assert self._line is not None
        return self._line

    def consume(self):
        assert self._line is not None
        line = self._line
        self.readline()
        return line

    def eof(self):
        assert self._line is not None
        return self._eof
    
    def skip_whitespace(self):
        while not self.eof() and self.match_whitespace() or self.match_comment():
            self.consume()

    def match_whitespace(self):
        line = self.lookahead()
        return not line.strip()

    def match_comment(self):
        return False


class TypemapParser(LineParser):

    def parse(self):
        typemap = {}
        self.skip_whitespace()
        while not self.eof():
            line = self.consume()
            fields = [field.strip() for field in line.split(',')]
            src = fields[0]
            dst = fields[3]
            if dst != '*':
                typemap[src] = dst
            self.skip_whitespace()
        return typemap
    
    def match_comment(self):
        line = self.lookahead()
        return line.startswith('#')


class SpecParser(LineParser):

    property_re = re.compile(r'^\w+:')
    prototype_re = re.compile(r'^(\w+)\((.*)\)$')

    def __init__(self, stream, prefix='', typemap = None):
        LineParser.__init__(self, stream)
        if typemap is None:
            self.typemap = {}
        else:
            self.typemap = typemap
        self.prefix = prefix
        self.category = None

    def parse(self):
        self.skip_whitespace()
        while not self.eof():
            line = self.lookahead()
            if self.property_re.match(line):
                self.parse_property()
            elif self.prototype_re.match(line):
                self.parse_prototype()
            else:
                self.consume()
            self.skip_whitespace()

    def parse_property(self):
        line = self.consume()
        name, value = line.split(':', 1)
        if name == 'category':
            values = value.split()
            #self.prefix = values[0]

    get_function_re = re.compile(r'^(Get|Is|Are)[A-Z]\w+')

    def parse_prototype(self):
        line = self.consume()
        mo = self.prototype_re.match(line)
        function_name, arg_names = mo.groups()
        arg_names = [arg_name.strip() for arg_name in arg_names.split(',') if arg_name.strip()]
        
        extra = ''
        if self.get_function_re.match(function_name):
            extra += ', sideeffects=False'
        function_name = self.prefix + function_name

        ret_type = 'Void'
        arg_types = {}
        category = None
        line = self.lookahead()
        while line.startswith('\t'):
            fields = line.split(None, 2)
            if fields[0] == 'return':
                ret_type = self.parse_type(fields[1])
            elif fields[0] == 'param':
                arg_name, arg_type = fields[1:3]
                arg_types[fields[1]] = self.parse_arg(function_name, arg_name, arg_type)
            elif fields[0] == 'category':
                category = fields[1]
            else:
                pass
            self.consume()
            line = self.lookahead()
        self.consume()
        args = [arg_types[arg_name] for arg_name in arg_names]

        if category is not None:
            if category == self.prefix:
                category = self.prefix.upper()
            else:
                category = self.prefix.upper() + '_' + category
            if category != self.category:
                if self.category is not None:
                    print()
                print('    # %s' % category)
                self.category = category

        if self.prefix == 'wgl':
            constructor = 'StdFunction'
        else:
            constructor = 'GlFunction'

        print('    %s(%s, "%s", [%s]%s),' % (constructor, ret_type, function_name, ', '.join(args), extra))

    array_re = re.compile(r'^array\s+\[(.*)\]$')

    string_typemap = {
        'GLchar': 'GLstring',
        'GLcharARB': 'GLstringARB',
    }

    def parse_arg(self, function_name, arg_name, arg_type):
        orig_type, inout, kind = arg_type.split(' ', 2)

        base_type = self.parse_type(orig_type)

        if kind == 'value':
            arg_type = base_type
        elif kind == 'reference':
            if inout == 'in':
                base_type = 'Const(%s)' % base_type
            arg_type = 'Pointer(%s)' % base_type
        elif kind.startswith("array"):
            if inout == 'in':
                base_type = 'Const(%s)' % base_type

            arg_type = 'OpaquePointer(%s)' % base_type

            if base_type in ('Void', 'void', 'GLvoid'):
                constructor = 'Blob'
            else:
                constructor = 'Array'

            mo = self.array_re.match(kind)
            if mo:
                length = mo.group(1).strip()
                if length == '':
                    try:
                        arg_type = self.string_typemap[base_type]
                    except KeyError:
                        pass
                elif length == '1':
                    arg_type = 'Pointer(%s)' % base_type
                elif length.find("COMPSIZE") == -1:
                    try:
                        int(length)
                    except ValueError:
                        length = '"%s"' % length
                    arg_type = '%s(%s, %s)' % (constructor, base_type, length)
                else:
                    if length == "COMPSIZE(pname)":
                        length = "_gl_param_size(pname)"
                        arg_type = '%s(%s, "%s")' % (constructor, base_type, length)
                    else:
                        length = length.replace("COMPSIZE", "_%s_size" % function_name)
                        length = length.replace("/", ", ")
                        arg_type = 'Opaque%s(%s, "%s")' % (constructor, base_type, length)
        else:
            assert False
        
        arg = '(%s, "%s")' % (arg_type, arg_name)
        if inout == 'out':
            arg = 'Out' + arg
        return arg

    semantic_typemap = {
        'String': 'CString',
        'Texture': 'GLtexture',
    }

    post_typemap = {
        'void': 'Void',
        'int': 'Int',
        'float': 'Float',
    }

    def parse_type(self, type):
        try:
            return self.semantic_typemap[type]
        except KeyError:
            pass
        type = self.typemap.get(type, type)
        type = self.post_typemap.get(type, type)
        return type

    def match_comment(self):
        line = self.lookahead()
        return line.startswith('#')


def main():
    prefix = sys.argv[1]

    parser = TypemapParser(open(sys.argv[2], 'rt'))
    typemap = parser.parse()

    for arg in sys.argv[3:]:
        parser = SpecParser(open(arg, 'rt'), prefix=prefix, typemap=typemap)
        parser.parse()
    

if __name__ == '__main__':
    main()
