#!/usr/bin/env python

import os, sys, re, string

# the list only for debugging. The real list, used in the real OpenCV build, is specified in CMakeLists.txt
opencv_hdr_list = [
"../../core/include/opencv2/core/core.hpp",
"../../flann/include/opencv2/flann/miniflann.hpp",
"../../ml/include/opencv2/ml/ml.hpp",
"../../imgproc/include/opencv2/imgproc/imgproc.hpp",
"../../calib3d/include/opencv2/calib3d/calib3d.hpp",
"../../features2d/include/opencv2/features2d/features2d.hpp",
"../../video/include/opencv2/video/tracking.hpp",
"../../video/include/opencv2/video/background_segm.hpp",
"../../objdetect/include/opencv2/objdetect/objdetect.hpp",
"../../contrib/include/opencv2/contrib/contrib.hpp",
"../../highgui/include/opencv2/highgui/highgui.hpp"
]

"""
Each declaration is [funcname, return_value_type /* in C, not in Python */, <list_of_modifiers>, <list_of_arguments>],
where each element of <list_of_arguments> is 4-element list itself:
[argtype, argname, default_value /* or "" if none */, <list_of_modifiers>]
where the list of modifiers is yet another nested list of strings
   (currently recognized are "/O" for output argument, "/S" for static (i.e. class) methods
   and "/A value" for the plain C arrays with counters)
"""

class CppHeaderParser(object):

    def __init__(self):
        self.BLOCK_TYPE = 0
        self.BLOCK_NAME = 1
        self.PROCESS_FLAG = 2
        self.PUBLIC_SECTION = 3
        self.CLASS_DECL = 4

    def batch_replace(self, s, pairs):
        for before, after in pairs:
            s = s.replace(before, after)
        return s

    def get_macro_arg(self, arg_str, npos):
        npos2 = npos3 = arg_str.find("(", npos)
        if npos2 < 0:
            print "Error: no arguments for the macro at %d" % (self.lineno,)
            sys.exit(-1)
        balance = 1
        while 1:
            t, npos3 = self.find_next_token(arg_str, ['(', ')'], npos3+1)
            if npos3 < 0:
                print "Error: no matching ')' in the macro call at %d" % (self.lineno,)
                sys.exit(-1)
            if t == '(':
                balance += 1
            if t == ')':
                balance -= 1
                if balance == 0:
                    break

        return arg_str[npos2+1:npos3].strip(), npos3

    def parse_arg(self, arg_str, argno):
        """
        Parses <arg_type> [arg_name]
        Returns arg_type, arg_name, modlist, argno, where
        modlist is the list of wrapper-related modifiers (such as "output argument", "has counter", ...)
        and argno is the new index of an anonymous argument.
        That is, if no arg_str is just an argument type without argument name, the argument name is set to
        "arg" + str(argno), and then argno is incremented.
        """
        modlist = []

        # pass 0: extracts the modifiers
        if "CV_OUT" in arg_str:
            modlist.append("/O")
            arg_str = arg_str.replace("CV_OUT", "")

        if "CV_IN_OUT" in arg_str:
            modlist.append("/IO")
            arg_str = arg_str.replace("CV_IN_OUT", "")

        isarray = False
        npos = arg_str.find("C
V_CARRAY")
        if npos >= 0:
            isarray = True
            macro_arg, npos3 = self.get_macro_arg(arg_str, npos)

            modlist.append("/A " + macro_arg)
            arg_str = arg_str[:npos] + arg_str[npos3+1:]

        npos = arg_str.find("CV_CUSTOM_CARRAY")
        if npos >= 0:
            isarray = True
            macro_arg, npos3 = self
.get_macro_arg(arg_str, npos)

            modlist.append("/CA " + macro_arg)
            arg_str = arg_str[:npos] + arg_str[npos3+1:]

        arg_str = arg_str.strip()
        word_start = 0
        word_list = []
        npos = -1

        #print self.lineno, ":\t", arg_str

        # pass 1: split argument type into tokens
        while 1:
            npos += 1
            t, npos = self.find_next_token(arg_str, [" ", "&", "*", "<", ">", ","], npos)
            w = arg_str[word_start:npos].strip()
            if w == "operator":
                word_list.append("operator " + arg_str[npos:].strip())
                break
            if w not in ["", "const"]:
                word_list.append(w)
            if t not in ["", " ", "&"]:
                word_list.append(t)
            if not t:
                break
            word_start = npos+1
            npos = word_start - 1

        arg_type = ""
        arg_name = ""
        angle_stack = []

        #print self.lineno, ":\t", word_list

        # pass 2: decrypt the list
        wi = -1
        prev_w = ""
        for w in word_list:
            wi += 1
            if w == "*":
                if prev_w == "char" and not isarray:
                    arg_type = arg_type[:-len("char")] + "c_string"
                else:
                    arg_type += w
                continue
            elif w == "<":
                arg_type += "_"
                angle_stack.append(0)
            elif w == "," or w == '>':
                if not angle_stack:
                    print "Error at %d: argument contains ',' or '>' not within template arguments" % (self.lineno,)
                    sys.exit(-1)
                if w == ",":
                    arg_type += "_and_"
                elif w == ">":
                    if angle_stack[0] == 0:
                        print "Error at %s:%d: template has no arguments" % (self.hname, self.lineno)
                        sys.exit(-1)
                    if angle_stack[0] > 1:
                        arg_type += "_end_"
                    angle_stack[-1:] = []
            elif angle_stack:
                arg_type += w
                angle_stack[-1] += 1
            elif arg_type == "struct":
                arg_type += " " + w
            elif arg_type and arg_type != "~":
                arg_name = " ".join(word_list[wi:])
                break
            else:
                arg_type += w
            prev_w = w

        counter_str = ""
        add_star = False
        if ("[" in arg_name) and not ("operator" in arg_str):
            #print arg_str
            p1 = arg_name.find("[")
            p2 = arg_name.find("]",p1+1)
            if p2 < 0:
                print "Error at %d: no closing ]" % (self.lineno,)
                sys.exit(-1)
            counter_str = arg_name[p1+1:p2].strip()
            if counter_str == "":
                counter_str = "?"
            if not isarray:
                modlist.append("/A " + counter_str.strip())
            arg_name = arg_name[:p1]
            add_star = True

        if not arg_name:
            if arg_type.startswith("operator"):
                arg_type, arg_name = "", arg_type
            else:
                arg_name = "arg" + str(argno)
                argno += 1

        while arg_type.endswith("_end_"):
            arg_type = arg_type[:-len("_end_")]

        if add_star:
            arg_type += "*"

        arg_type = self.batch_replace(arg_type, [("std::", ""), ("cv::", ""), ("::", "_")])

        return arg_type, arg_name, modlist, argno

    def parse_enum(self, decl_str):
        l = decl_str
        ll = l.split(",")
        prev_val = ""
        prev_val_delta = -1
        decl = []
        for pair in ll:
            pv = pair.split("=")
            if len(pv) == 1:
                prev_val_delta += 1
                val = ""
                if prev_val:
                    val = prev_val + "+"
                val += str(prev_val_delta)
            else:
                prev_val_delta = 0
                prev_val = val = pv[1].strip()
            decl.append(["const " + self.get_dotted_name(pv[0].strip()), val, [], []])
        return decl

    def parse_class_decl(self, decl_str):
        """
        Parses class/struct declaration start in the form:
           {class|struct} [CV_EXPORTS] <class_name> [: public <base_class1> [, ...]]
        Returns class_name1, <list of base_classes>
        """
        l = decl_str
        modlist = []
        if "CV_EXPORTS_W_MAP" in l:
            l = l.replace("CV_EXPORTS_W_MAP", "")
            modlist.append("/Map")
        if "CV_EXPORTS_W_SIMPLE" in l:
            l = l.replace("CV_EXPORTS_W_SIMPLE", ""