#!/usr/bin/python
#
# Copyright (C) 2009 Chia-I Wu <olv@0xlab.org>
#
# 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
# on the rights to use, copy, modify, merge, publish, distribute, sub
# license, 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 (including the next
# paragraph) 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 NON-INFRINGEMENT.  IN NO EVENT SHALL
# IBM AND/OR ITS SUPPLIERS 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.
"""
Minimal apiutil.py interface for use by es_generator.py.
"""

import sys
import libxml2

import APIspec

__spec = {}
__functions = {}
__aliases = {}

def _ParseXML(filename, apiname):
    conversions = {
        # from           to
        'GLfloat':  [ 'GLdouble' ],
        'GLclampf': [ 'GLclampd' ],
        'GLubyte':  [ 'GLfloat', 'GLdouble' ],
        'GLint':    [ 'GLfloat', 'GLdouble' ],
        'GLfixed':  [ 'GLfloat', 'GLdouble' ],
        'GLclampx': [ 'GLclampf', 'GLclampd' ],
    }

    doc = libxml2.readFile(filename, None,
            libxml2.XML_PARSE_DTDLOAD +
            libxml2.XML_PARSE_DTDVALID +
            libxml2.XML_PARSE_NOBLANKS)
    spec = APIspec.Spec(doc)
    impl = spec.get_impl()
    api = spec.get_api(apiname)
    doc.freeDoc()

    __spec["impl"] = impl
    __spec["api"] = api

    for func in api.functions:
        alias, need_conv = impl.match(func, conversions)
        if not alias:
            # external functions are manually dispatched
            if not func.is_external:
                print >>sys.stderr, "Error: unable to dispatch %s" % func.name
            alias = func
            need_conv = False

        __functions[func.name] = func
        __aliases[func.name] = (alias, need_conv)


def AllSpecials(notused=None):
    """Return a list of all external functions in the API."""
    api = __spec["api"]

    specials = []
    for func in api.functions:
        if func.is_external:
            specials.append(func.name)

    return specials


def GetAllFunctions(filename, api):
    """Return sorted list of all functions in the API."""
    if not __spec:
        _ParseXML(filename, api)

    api = __spec["api"]
    names = []
    for func in api.functions:
        names.append(func.name)
    names.sort()
    return names


def ReturnType(funcname):
    """Return the C return type of named function."""
    func = __functions[funcname]
    return func.return_type


def Properties(funcname):
    """Return list of properties of the named GL function."""
    func = __functions[funcname]
    return [func.direction]


def _ValidValues(func, param):
    """Return the valid values of a parameter."""
    valid_values = []
    switch = func.checker.switches.get(param.name, [])
    for desc in switch:
        # no dependent vector
        if not desc.checker.switches:
            for val in desc.values:
                valid_values.append((val, None, None, [], desc.error, None))
            continue

        items = desc.checker.switches.items()
        if len(items) > 1:
            print >>sys.stderr, "%s: more than one parameter depend on %s" % \
                    (func.name, desc.name)
        dep_name, dep_switch = items[0]

        for dep_desc in dep_switch:
            if dep_desc.index >= 0 and dep_desc.index != 0:
                print >>sys.stderr, "%s: not first element of a vector" % func.name
            if dep_desc.checker.switches:
                print >>sys.stderr, "%s: deep nested dependence" % func.name

            convert = None if dep_desc.convert else "noconvert"
            for val in desc.values:
                valid_values.append((val, dep_desc.size_str, dep_desc.name,
                                     dep_desc.values, dep_desc.error, convert))
    return valid_values


def _Conversion(func, src_param):
    """Return the destination type of the conversion, or None."""
    alias, need_conv = __aliases[func.name]
    if need_conv:
        dst_param = alias.get_param(src_param.name)
        if src_param.type == dst_param.type:
            need_conv = False
    if not need_conv:
        return (None, "none")

    converts = { True: 0, False: 0 }

    # In Fogx, for example,  pname may be GL_FOG_DENSITY/GL_FOG_START/GL_FOG_END
    # or GL_FOG_MODE.  In the former three cases, param is not checked and the
    # default is to convert.
    if not func.checker.always_check(src_param.name):
        converts[True] += 1

    for desc in func.checker.flatten(src_param.name):
        converts[desc.convert] += 1
        if converts[True] and converts[False]:
            break

    # it should be "never", "sometimes", and "always"...
    if converts[False]:
        if converts[True]:
            conversion = "some"
        else:
            conversion = "none"
    else:
        conversion = "all"

    return (dst_param.base_type(), conversion)


def _MaxVecSize(func, param):
    """Return the largest possible size of a vector."""
    if not param.is_vector:
        return 0
    if param.size:
        return param.size

    # need to look at all descriptions
    size = 0
    for desc in func.checker.flatten(param.name):
        if desc.size_str and desc.size_str.isdigit():
            s = int(desc.size_str)
            if s > size:
                size = s
    if not size:
        need_conv = __aliases[func.name][1]
        if need_conv:
            print >>sys.stderr, \
                    "Error: unable to dicide the max size of %s in %s" % \
                    (param.name, func.name)
    return size


def _ParameterTuple(func, param):
    """Return a parameter tuple.

    [0] -- parameter name
    [1] -- parameter type
    [2] -- max vector size or 0
    [3] -- dest type the parameter converts to, or None
    [4] -- valid values
    [5] -- how often does the conversion happen

    """
    vec_size = _MaxVecSize(func, param)
    dst_type, conversion = _Conversion(func, param)
    valid_values = _ValidValues(func, param)

    return (param.name, param.type, vec_size, dst_type, valid_values, conversion)


def Parameters(funcname):
    """Return list of tuples of function parameters."""
    func = __functions[funcname]
    params = []
    for param in func.params:
        params.append(_ParameterTuple(func, param))

    return params


def FindParamIndex(params, paramname):
    """Find the index of a named parameter."""
    for i in xrange(len(params)):
        if params[i][0] == paramname:
            return i
    return None


def MakeDeclarationString(params):
    """Return a C-style parameter declaration string."""
    string = []
    for p in params:
        sep = "" if p[1].endswith("*") else " "
        string.append("%s%s%s" % (p[1], sep, p[0]))
    if not string:
        return "void"
    return ", ".join(string)


def AliasPrefix(funcname):
    """Return the prefix of the function the named function is an alias of."""
    alias = __aliases[funcname][0]
    return alias.prefix


def Alias(funcname):
    """Return the name of the function the named function is an alias of."""
    alias, need_conv = __aliases[funcname]
    return alias.name if not need_conv else None


def ConversionFunction(funcname):
    """Return the name of the function the named function converts to."""
    alias, need_conv = __aliases[funcname]
    return alias.name if need_conv else None


def Categories(funcname):
    """Return all the categories of the named GL function."""
    api = __spec["api"]
    return [api.name]
