#!/usr/bin/env python

import sys, re, os.path, errno, fnmatch
import json
import logging
import codecs
from shutil import copyfile
from pprint import pformat
from string import Template
from distutils.dir_util import copy_tree

if sys.version_info[0] >= 3:
    from io import StringIO
else:
    import io
    class StringIO(io.StringIO):
        def write(self, s):
            if isinstance(s, str):
                s = unicode(s)  # noqa: F821
            return super(StringIO, self).write(s)

SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

# list of modules
config = None
ROOT_DIR = None

total_files = 0
updated_files = 0

module_imports = []

# list of class names, which should be skipped by wrapper generator
# the list is loaded from misc/objc/gen_dict.json defined for the module and its dependencies
class_ignore_list = []

# list of constant names, which should be skipped by wrapper generator
# ignored constants can be defined using regular expressions
const_ignore_list = []

# list of private constants
const_private_list = []

# { Module : { public : [[name, val],...], private : [[]...] } }
missing_consts = {}

type_dict = {
    ""        : {"objc_type" : ""}, # c-tor ret_type
    "void"    : {"objc_type" : "void", "is_primitive" : True},
    "bool"    : {"objc_type" : "BOOL", "is_primitive" : True, "to_cpp": "(bool)%(n)s"},
    "char"    : {"objc_type" : "char", "is_primitive" : True},
    "int"     : {"objc_type" : "int", "is_primitive" : True, "out_type" : "int*", "out_type_ptr": "%(n)s", "out_type_ref": "*(int*)(%(n)s)"},
    "long"    : {"objc_type" : "long", "is_primitive" : True},
    "float"   : {"objc_type" : "float", "is_primitive" : True, "out_type" : "float*", "out_type_ptr": "%(n)s", "out_type_ref": "*(float*)(%(n)s)"},
    "double"  : {"objc_type" : "double", "is_primitive" : True, "out_type" : "double*", "out_type_ptr": "%(n)s", "out_type_ref": "*(double*)(%(n)s)"},
    "size_t"  : {"objc_type" : "size_t", "is_primitive" : True},
    "int64"   : {"objc_type" : "long", "is_primitive" : True},
    "string"  : {"objc_type" : "NSString*", "is_primitive" : True, "from_cpp": "[NSString stringWithUTF8String:%(n)s.c_str()]", "cast_to": "std::string"}
}

# Defines a rule to add extra prefixes for names from specific namespaces.
# In example, cv::fisheye::stereoRectify from namespace fisheye is wrapped as fisheye_stereoRectify
namespaces_dict = {}

# { class : [ header ] }
AdditionalImports = {}

# { class : { func : {declaration, implementation} } }
ManualFuncs = {}

# { class : { func : { arg_name : {"ctype" : ctype, "attrib" : [attrib]} } } }
func_arg_fix = {}

# { class : { enum: fixed_enum } }
enum_fix = {}

# { (class, func) : objc_signature }
method_dict = {
    ("Mat", "convertTo") : "-convertTo:rtype:alpha:beta:",
    ("Mat", "setTo") : "-setToScalar:mask:",
    ("Mat", "zeros") : "+zeros:cols:type:",
    ("Mat", "ones") : "+ones:cols:type:",
    ("Mat", "dot") : "-dot:"
}

def read_contents(fname):
    with open(fname, 'r') as f:
        data = f.read()
    return data

def mkdir_p(path):
    ''' mkdir -p '''
    try:
        os.makedirs(path)
    except OSError as exc:
        if exc.errno == errno.EEXIST and os.path.isdir(path):
            pass
        else:
            raise

T_OBJC_CLASS_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_header.template'))
T_OBJC_CLASS_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_class_body.template'))
T_OBJC_MODULE_HEADER = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_header.template'))
T_OBJC_MODULE_BODY = read_contents(os.path.join(SCRIPT_DIR, 'templates/objc_module_body.template'))

class GeneralInfo():
    def __init__(self, type, decl, namespaces):
        self.namespace, self.classpath, self.classname, self.name = self.parseName(decl[0], namespaces)

        # parse doxygen comments
        self.params={}

        self.deprecated = False
        if type == "class":
            docstring = "// C++: class " + self.name + "\n"
        else:
            docstring=""

        if len(decl)>5 and decl[5]:
            doc = decl[5]

            if re.search("(@|\\\\)deprecated", doc):
                self.deprecated = True

            docstring += sanitize_documentation_string(doc, type)
        elif type == "class":
            docstring += "/**\n* The " + self.name + " module\n*/\n"

        self.docstring = docstring

    def parseName(self, name, namespaces):
        '''
        input: full name and available namespaces
        returns: (namespace, classpath, classname, name)
        '''
        name = name[name.find(" ")+1:].strip() # remove struct/class/const prefix
        spaceName = ""
        localName = name # <classes>.<name>
        for namespace in sorted(namespaces, key=len, reverse=True):
            if name.startswith(namespace + "."):
                spaceName = namespace
                localName = name.replace(namespace + ".", "")
                break
        pieces = localName.split(".")
        if len(pieces) > 2: # <class>.<class>.<class>.<name>
            return spaceName, ".".join(pieces[:-1]), pieces[-2], pieces[-1]
        elif len(pieces) == 2: # <class>.<name>
            return spaceName, pieces[0], pieces[0], pieces[1]
        elif len(pieces) == 1: # <name>
            return spaceName, "", "", pieces[0]
        else:
            return spaceName, "", "" # error?!

    def fullName(self, isCPP=False):
        result = ".".join([self.fullClass(), self.name])
        return result if not isCPP else get_cname(result)

    def fullClass(self, isCPP=False):
        result = ".".join([f for f in [self.namespace] + self.classpath.split(".") if len(f)>0])
        return result if not isCPP else get_cname(result)

class ConstInfo(GeneralInfo):
    def __init__(self, decl, addedManually=False, namespaces=[], enumType=None):
        GeneralInfo.__init__(self, "const", decl, namespaces)
        self.cname = get_cname(self.name)
        self.value = decl[1]
        self.enumType = enumType
        self.addedManually = addedManually
        if self.namespace in namespaces_dict:
            self.name = '%s_%s' % (namespaces_dict[self.namespace], self.name)

    def __repr__(self):
        return Template("CONST $name=$value$manual").substitute(name=self.name,
                                                                 value=self.value,
                                                                 manual="(manual)" if self.addedManually else "")

    def isIgnored(self):
        for c in const_ignore_list:
            if re.match(c, self.name):
                return True
        return False

def normalize_field_name(name):
    return name.replace(".","_").replace("[","").replace("]","").replace("_getNativeObjAddr()","_nativeObj")

def normalize_class_name(name):
    return re.sub(r"^cv\.", "", name).replace(".", "_")

def get_cname(name):
    return name.replace(".", "::")

def cast_from(t):
    if t in type_dict and "cast_from" in type_dict[t]:
        return type_dict[t]["cast_from"]
    return t

def cast_to(t):
    if t in type_dict and "cast_to" in type_dict[t]:
        return type_dict[t]["cast_to"]
    return t

class ClassPropInfo():
    def __init__(self, decl): # [f_ctype, f_name, '', '/RW']
        self.ctype = decl[0]
        self.name = decl[1]
        self.rw = "/RW" in decl[3]

    def __repr__(self):
        return Template("PROP $ctype $name").substitute(ctype=self.ctype, name=self.name)

class ClassInfo(GeneralInfo):
    def __init__(self, decl, namespaces=[]): # [ 'class/struct cname', ': base', [modlist] ]
        GeneralInfo.__init__(self, "class", decl, namespaces)
        self.cname = get_cname(self.name)
        self.methods = []
        self.methods_suffixes = {}
        self.consts = [] # using a list to save the occurrence order
        self.private_consts = []
        self.imports = set()
        self.props= []
        self.objc_name = self.name
        self.smart = None # True if class stores Ptr<T>* instead of T* in nativeObj field
        self.additionalImports = None # additional import files
        self.enum_declarations = None # Objective-C enum declarations stream
        self.method_declarations = None # Objective-C method declarations stream
        self.method_implementations = None # Objective-C method implementations stream
        self.objc_header_template = None # Objective-C header code
        self.objc_body_template = None # Objective-C body code
        for m in decl[2]:
            if m.startswith("="):
                self.objc_name = m[1:]
        self.base = ''
        self.is_base_class = True
        self.native_ptr_name = "nativePtr"
        if decl[1]:
            self.base = re.sub(r"^.*:", "", decl[1].split(",")[0]).strip().replace(self.objc_name, "")
            if self.base:
                self.is_base_class = False
                self.native_ptr_name = "nativePtr" + self.objc_name

    def __repr__(self):
        return Template("CLASS $namespace::$classpath.$name : $base").substitute(**self.__dict__)

    def getImports(self, module):
        return ["#import \"%s.h\"" % c for c in sorted(filter(lambda m: m != self.name, map(lambda m: type_dict[m]["import_module"] if m in type_dict and "import_module" in type_dict[m] else m, self.imports)))]

    def isEnum(self, c):
        return c in type_dict and type_dict[c].get("is_enum", False)

    def getForwardDeclarations(self, module):
        enum_decl = filter(lambda x:self.isEnum(x) and type_dict[x]["import_module"] != module, self.imports)
        class_decl = filter(lambda x: not self.isEnum(x), self.imports)
        return ["#import \"%s.h\"" % type_dict[c]["import_module"] for c in enum_decl] + [""] + ["@class %s;" % c for c in sorted(class_decl)]

    def addImports(self, ctype, is_out_type):
        if ctype == self.cname:
            return
        if ctype in type_dict:
            objc_import = None
            if "v_type" in type_dict[ctype]:
                objc_import = type_dict[type_dict[ctype]["v_type"]]["objc_type"]
            elif "v_v_type" in type_dict[ctype]:
                objc_import = type_dict[type_dict[ctype]["v_v_type"]]["objc_type"]
            elif not type_dict[ctype].get("is_primitive", False):
                objc_import = type_dict[ctype]["objc_type"]
            if objc_import is not None and objc_import not in ["NSNumber*", "NSString*"] and not (objc_import in type_dict and type_dict[objc_import].get("is_primitive", False)):
                objc_import = objc_import[:-1] if objc_import[-1] == "*" else objc_import   # remove trailing "*"
                if objc_import != self.cname:
                    self.imports.add(objc_import)   # remove trailing "*"

    def getAllMethods(self):
        result = []
        result.extend([fi for fi in sorted(self.methods) if fi.isconstructor])
        result.extend([fi for fi in sorted(self.methods) if not fi.isconstructor])
        return result

    def addMethod(self, fi):
        self.methods.append(fi)

    def getConst(self, name):
        for cand in self.consts + self.private_consts:
            if cand.name == name:
                return cand
        return None

    def addConst(self, constinfo):
        # choose right list (public or private)
        consts = self.consts
        for c in const_private_list:
            if re.match(c, constinfo.name):
                consts = self.private_consts
                break
        consts.append(constinfo)

    def initCodeStreams(self, Module):
        self.additionalImports = StringIO()
        self.enum_declarations = StringIO()
        self.method_declarations = StringIO()
        self.method_implementations = StringIO()
        if self.base:
            self.objc_header_template = T_OBJC_CLASS_HEADER
            self.objc_body_template = T_OBJC_CLASS_BODY
        else:
            self.base = "NSObject"
            if self.name != Module:
                self.objc_header_template = T_OBJC_CLASS_HEADER
                self.objc_body_template = T_OBJC_CLASS_BODY
            else:
                self.objc_header_template = T_OBJC_MODULE_HEADER
                self.objc_body_template = T_OBJC_MODULE_BODY
        # misc handling
        if self.name == Module:
          for i in module_imports or []:
              self.imports.add(i)

    def cleanupCodeStreams(self):
        self.additionalImports.close()
        self.enum_declarations.close()
        self.method_declarations.close()
        self.method_implementations.close()

    def generateObjcHeaderCode(self, m, M, objcM):
        return Template(self.objc_header_template + "\n\n").substitute(
                            module = M,
                            additionalImports = self.additionalImports.getvalue(),
                            importBaseClass = '#import "' + self.base + '.h"' if not self.is_base_class else "",
                            forwardDeclarations = "\n".join(filter(None, self.getForwardDeclarations(objcM))),
                            enumDeclarations = self.enum_declarations.getvalue(),
                            nativePointerHandling = Template(
"""
#ifdef __cplusplus
@property(readonly)cv::Ptr<$cName> $native_ptr_name;
#endif

#ifdef __cplusplus
- (instancetype)initWithNativePtr:(cv::Ptr<$cName>)nativePtr;
+ (instancetype)fromNative:(cv::Ptr<$cName>)nativePtr;
#endif
"""
                            ).substitute(
                                cName = self.fullName(isCPP=True),
                                native_ptr_name = self.native_ptr_name
                            ),
                            manualMethodDeclations = "",
                            methodDeclarations = self.method_declarations.getvalue(),
                            name = self.name,
                            objcName = self.objc_name,
                            cName = self.cname,
                            imports = "\n".join(self.getImports(M)),
                            docs = self.docstring,
                            base = self.base)

    def generateObjcBodyCode(self, m, M):
        return Template(self.objc_body_template + "\n\n").substitute(
                            module = M,
                            nativePointerHandling=Template(
"""
- (instancetype)initWithNativePtr:(cv::Ptr<$cName>)nativePtr {
    self = [super $init_call];
    if (self) {
        _$native_ptr_name = nativePtr;
    }
    return self;
}

+ (instancetype)fromNative:(cv::Ptr<$cName>)nativePtr {
    return [[$objcName alloc] initWithNativePtr:nativePtr];
}
"""
                            ).substitute(
                                cName = self.fullName(isCPP=True),
                                objcName = self.objc_name,
                                native_ptr_name = self.native_ptr_name,
                                init_call = "init" if self.is_base_class else "initWithNativePtr:nativePtr"
                            ),
                            manualMethodDeclations = "",
                            methodImplementations = self.method_implementations.getvalue(),
                            name = self.name,
                            objcName = self.objc_name,
                            cName = self.cname,
                            imports = "\n".join(self.getImports(M)),
                            docs = self.docstring,
                            base = self.base)

class ArgInfo():
    def __init__(self, arg_tuple): # [ ctype, name, def val, [mod], argno ]
        self.pointer = False
        ctype = arg_tuple[0]
        if ctype.endswith("*"):
            ctype = ctype[:-1]
            self.pointer = True
        self.ctype = ctype
        self.name = arg_tuple[1]
        self.defval = arg_tuple[2]
        self.out = ""
        if "/O" in arg_tuple[3]:
            self.out = "O"
        if "/IO" in arg_tuple[3]:
            self.out = "IO"

    def __repr__(self):
        return Template("ARG $ctype$p $name=$defval").substitute(ctype=self.ctype,
                                                                  p=" *" if self.pointer else "",
                                                                  name=self.name,
                                                                  defval=self.defval)

class FuncInfo(GeneralInfo):
    def __init__(self, decl, namespaces=[]): # [ funcname, return_ctype, [modifiers], [args] ]
        GeneralInfo.__init__(self, "func", decl, namespaces)
        self.cname = get_cname(decl[0])
        self.objc_name = self.name
        self.swift_name = self.name
        self.cv_name = self.fullName(isCPP=True)
        self.isconstructor = self.name == self.classname
        if "[" in self.name:
            self.objc_name = "getelem"
        if self.namespace in namespaces_dict:
            self.objc_name = '%s_%s' % (namespaces_dict[self.namespace], self.objc_name)
        for m in decl[2]:
            if m.startswith("="):
                self.objc_name = m[1:]
        self.static = ["","static"][ "/S" in decl[2] ]
        self.ctype = re.sub(r"^CvTermCriteria", "TermCriteria", decl[1] or "")
        self.args = []
        func_fix_map = func_arg_fix.get(self.objc_name, {})
        for a in decl[3]:
            arg = a[:]
            arg_fix_map = func_fix_map.get(arg[1], {})
            arg[0] = arg_fix_map.get('ctype',  arg[0]) #fixing arg type
            arg[2] = arg_fix_map.get('defval', arg[2]) #fixing arg defval
            arg[3] = arg_fix_map.get('attrib', arg[3]) #fixing arg attrib
            self.args.append(ArgInfo(arg))

        if type_complete(self.args, self.ctype):
            func_fix_map = func_arg_fix.get(self.signature(self.args), {})
            name_fix_map = func_fix_map.get(self.name, {})
            self.objc_name = name_fix_map.get('name', self.objc_name)
            for arg in self.args:
                arg_fix_map = func_fix_map.get(arg.name, {})
                arg.ctype = arg_fix_map.get('ctype', arg.ctype) #fixing arg type
                arg.defval = arg_fix_map.get('defval', arg.defval) #fixing arg type
                arg.name = arg_fix_map.get('name', arg.name) #fixing arg name

    def __repr__(self):
        return Template("FUNC <$ctype $namespace.$classpath.$name $args>").substitute(**self.__dict__)

    def __lt__(self, other):
        return self.__repr__() < other.__repr__()

    def signature(self, args):
        objc_args = build_objc_args(args)
        return "(" + type_dict[self.ctype]["objc_type"] + ")" + self.objc_name + " ".join(objc_args)

def type_complete(args, ctype):
    for a in args:
        if a.ctype not in type_dict:
            if not a.defval and a.ctype.endswith("*"):
                a.defval = 0
            if a.defval:
                a.ctype = ''
                continue
            return False
    if ctype not in type_dict:
        return False
    return True

def build_objc_args(args):
    objc_args = []
    for a in args:
        if a.ctype not in type_dict:
            if not a.defval and a.ctype.endswith("*"):
                a.defval = 0
            if a.defval:
                a.ctype = ''
                continue
        if not a.ctype:  # hidden
            continue
        objc_type = type_dict[a.ctype]["objc_type"]
        if "v_type" in type_dict[a.ctype]:
            if "O" in a.out:
                objc_type = "NSMutableArray<" + objc_type + ">*"
            else:
                objc_type = "NSArray<" + objc_type + ">*"
        elif "v_v_type" in type_dict[a.ctype]:
            if "O" in a.out:
                objc_type = "NSMutableArray<NSMutableArray<" + objc_type + ">*>*"
            else:
                objc_type = "NSArray<NSArray<" + objc_type + ">*>*"

        if a.out and type_dict[a.ctype].get("out_type", ""):
            objc_type = type_dict[a.ctype]["out_type"]
        objc_args.append((a.name if len(objc_args) > 0 else '') + ':(' + objc_type + ')' + a.name)
    return objc_args

def build_objc_method_name(args):
    objc_method_name = ""
    for a in args[1:]:
        if a.ctype not in type_dict:
            if not a.defval and a.ctype.endswith("*"):
                a.defval = 0
            if a.defval:
                a.ctype = ''
                continue
        if not a.ctype:  # hidden
            continue
        objc_method_name += a.name + ":"
    return objc_method_name

def build_swift_signature(args):
    swift_signature = ""
    for a in args:
        if a.ctype not in type_dict:
            if not a.defval and a.ctype.endswith("*"):
                a.defval = 0
            if a.defval:
                a.ctype = ''
                continue
        if not a.ctype:  # hidden
            continue
        swift_signature += a.name + ":"
    return swift_signature

def add_method_to_dict(class_name, fi):
    static = fi.static if fi.classname else True
    if not method_dict.has_key((class_name, fi.objc_name)):
        objc_method_name = ("+" if static else "-") + fi.objc_name + ":" + build_objc_method_name(fi.args)
        method_dict[(class_name, fi.objc_name)] = objc_method_name

def see_lookup(objc_class, see):
    semi_colon = see.find("::")
    see_class = see[:semi_colon] if semi_colon > 0 else objc_class
    see_method = see[(semi_colon + 2):] if semi_colon != -1 else see
    if method_dict.has_key((see_class, see_method)):
        method = method_dict[(see_class, see_method)]
        if see_class == objc_class:
            return method
        else:
            return ("-" if method[0] == "-" else "") + "[" + see_class + " " + method[1:] + "]"
    else:
        return see


class ObjectiveCWrapperGenerator(object):
    def __init__(self):
        self.header_files = []
        self.clear()

    def clear(self):
        self.namespaces = set(["cv"])
        mat_class_info = ClassInfo([ 'class Mat', '', [], [] ], self.namespaces)
        mat_class_info.namespace = "cv"
        self.classes = { "Mat" : mat_class_info }
        self.classes["Mat"].namespace = "cv"
        self.module = ""
        self.Module = ""
        self.ported_func_list = []
        self.skipped_func_list = []
        self.def_args_hist = {} # { def_args_cnt : funcs_cnt }

    def add_class(self, decl):
        classinfo = ClassInfo(decl, namespaces=self.namespaces)
        if classinfo.name in class_ignore_list:
            logging.info('ignored: %s', classinfo)
            return
        name = classinfo.name
        if self.isWrapped(name) and not classinfo.base:
            logging.warning('duplicated: %s', classinfo)
            return
        self.classes[name] = classinfo
        if name in type_dict and not classinfo.base:
            logging.warning('duplicated: %s', classinfo)
            return
        if name != self.Module:
            type_dict.setdefault(name, {}).update(
                { "objc_type" : classinfo.objc_name + "*",
                  "from_cpp" : "[" + classinfo.objc_name + " fromNative:%(n)s]",
                  "to_cpp" : "*(%(n)s." + classinfo.native_ptr_name + ")" }
            )

        # missing_consts { Module : { public : [[name, val],...], private : [[]...] } }
        if name in missing_consts:
            if 'public' in missing_consts[name]:
                for (n, val) in missing_consts[name]['public']:
                    classinfo.consts.append( ConstInfo([n, val], addedManually=True) )

        # class props
        for p in decl[3]:
            if True: #"vector" not in p[0]:
                classinfo.props.append( ClassPropInfo(p) )
            else:
                logging.warning("Skipped property: [%s]" % name, p)

        if name != self.Module:
            type_dict.setdefault("Ptr_"+name, {}).update(
                { "objc_type" : classinfo.objc_name + "*",
                  "c_type" : name,
                  "to_cpp": "%(n)s." + classinfo.native_ptr_name,
                  "from_cpp_ptr": "[" + name + " fromNativePtr:%(n)s]"}
            )
        logging.info('ok: class %s, name: %s, base: %s', classinfo, name, classinfo.base)

    def add_const(self, decl, scope=None, enumType=None): # [ "const cname", val, [], [] ]
        constinfo = ConstInfo(decl, namespaces=self.namespaces, enumType=enumType)
        if constinfo.isIgnored():
            logging.info('ignored: %s', constinfo)
        else:
            if not self.isWrapped(constinfo.classname):
                logging.info('class not found: %s', constinfo)
                constinfo.name = constinfo.classname + '_' + constinfo.name
                constinfo.classname = ''

            ci = self.getClass(constinfo.classname)
            duplicate = ci.getConst(constinfo.name)
            if duplicate:
                if duplicate.addedManually:
                    logging.info('manual: %s', constinfo)
                else:
                    logging.warning('duplicated: %s', constinfo)
            else:
                ci.addConst(constinfo)
                logging.info('ok: %s', constinfo)

    def add_enum(self, decl, scope): # [ "enum cname", "", [], [] ]
        enumType = decl[0].rsplit(" ", 1)[1]
        if enumType.endswith("<unnamed>"):
            enumType = None
        else:
            ctype = normalize_class_name(enumType)
            constinfo = ConstInfo(decl[3][0], namespaces=self.namespaces, enumType=enumType)
            objc_type = enumType.rsplit(".", 1)[-1]
            if enum_fix.has_key(constinfo.classname):
                objc_type = enum_fix[constinfo.classname].get(objc_type, objc_type)
            import_module = constinfo.classname if constinfo.classname and constinfo.classname != objc_type else self.Module
            type_dict[ctype] = { "cast_from" : "int",
                                 "cast_to" : get_cname(enumType),
                                 "objc_type" : objc_type,
                                 "is_enum" : True,
                                 "import_module" : import_module,
                                 "from_cpp" : "(" + objc_type + ")%(n)s"}
            type_dict[objc_type] = { "cast_to" : get_cname(enumType),
                                     "objc_type": objc_type,
                                     "is_enum": True,
                                     "import_module": import_module}
        const_decls = decl[3]

        for decl in const_decls:
            self.add_const(decl, scope, enumType)

    def add_func(self, decl):
        fi = FuncInfo(decl, namespaces=self.namespaces)
        classname = fi.classname or self.Module
        if classname in class_ignore_list:
            logging.info('ignored: %s', fi)
        elif classname in ManualFuncs and fi.objc_name in ManualFuncs[classname]:
            logging.info('manual: %s', fi)
            if ManualFuncs[classname][fi.objc_name].has_key("objc_method_name"):
                method_dict[(classname, fi.objc_name)] = ManualFuncs[classname][fi.objc_name]["objc_method_name"]
        elif not self.isWrapped(classname):
            logging.warning('not found: %s', fi)
        else:
            self.getClass(classname).addMethod(fi)
            logging.info('ok: %s', fi)
            # calc args with def val
            cnt = len([a for a in fi.args if a.defval])
            self.def_args_hist[cnt] = self.def_args_hist.get(cnt, 0) + 1
            add_method_to_dict(classname, fi)

    def save(self, path, buf):
        global total_files, updated_files
        total_files += 1
        if os.path.exists(path):
            with open(path, "rt") as f:
                content = f.read()
                if content == buf:
                    return
        with codecs.open(path, "w", "utf-8") as f:
            f.write(buf)
        updated_files += 1

    def get_namespace_prefix(self, cname):
        namespace = self.classes[cname].namespace if self.classes.has_key(cname) else "cv"
        return namespace.replace(".", "::") + "::"

    def gen(self, srcfiles, module, output_path, output_objc_path, common_headers):
        self.clear()
        self.module = module
        self.Module = module.capitalize()
        # TODO: support UMat versions of declarations (implement UMat-wrapper for Java)
        parser = hdr_parser.CppHeaderParser(generate_umat_decls=False)

        self.add_class( ['class ' + self.Module, '', [], []] ) # [ 'class/struct cname', ':bases', [modlist] [props] ]

        # scan the headers and build more descriptive maps of classes, consts, functions
        includes = []
        for hdr in common_headers:
            logging.info("\n===== Common header : %s =====", hdr)
            includes.append('#include "' + hdr + '"')
        for hdr in srcfiles:
            decls = parser.parse(hdr)
            self.namespaces = parser.namespaces
            logging.info("\n\n===== Header: %s =====", hdr)
            logging.info("Namespaces: %s", parser.namespaces)
            if decls:
                includes.append('#include "' + hdr + '"')
            else:
                logging.info("Ignore header: %s", hdr)
            for decl in decls:
                logging.info("\n--- Incoming ---\n%s", pformat(decl[:5], 4)) # without docstring
                name = decl[0]
                if name.startswith("struct") or name.startswith("class"):
                    self.add_class(decl)
                elif name.startswith("const"):
                    self.add_const(decl)
                elif name.startswith("enum"):
                    # enum
                    self.add_enum(decl, self.Module)
                else: # function
                    self.add_func(decl)

        logging.info("\n\n===== Generating... =====")
        package_path = os.path.join(output_objc_path, module)
        mkdir_p(package_path)
        for ci in self.classes.values():
            if ci.name == "Mat":
                continue
            ci.initCodeStreams(self.Module)
            self.gen_class(ci)
            classObjcHeaderCode = ci.generateObjcHeaderCode(self.module, self.Module, ci.objc_name)
            header_file = "%s/%s/%s.h" % (output_objc_path, module, ci.objc_name)
            self.save(header_file, classObjcHeaderCode)
            self.header_files.append(header_file)
            classObjcBodyCode = ci.generateObjcBodyCode(self.module, self.Module)
            self.save("%s/%s/%s.mm" % (output_objc_path, module, ci.objc_name), classObjcBodyCode)
            ci.cleanupCodeStreams()
        self.save(os.path.join(output_path, module+".txt"), self.makeReport())

    def makeReport(self):
        '''
        Returns string with generator report
        '''
        report = StringIO()
        total_count = len(self.ported_func_list)+ len(self.skipped_func_list)
        report.write("PORTED FUNCs LIST (%i of %i):\n\n" % (len(self.ported_func_list), total_count))
        report.write("\n".join(self.ported_func_list))
        report.write("\n\nSKIPPED FUNCs LIST (%i of %i):\n\n" % (len(self.skipped_func_list), total_count))
        report.write("".join(self.skipped_func_list))
        for i in self.def_args_hist.keys():
            report.write("\n%i def args - %i funcs" % (i, self.def_args_hist[i]))
        return report.getvalue()

    def fullTypeName(self, t):
        if not type_dict[t].get("is_primitive", False) or type_dict[t].has_key("cast_to"):
            if type_dict[t].has_key("cast_to"):
                return type_dict[t]["cast_to"]
            else:
                namespace_prefix = self.get_namespace_prefix(t)
                return namespace_prefix + t
        else:
            return t

    def build_objc2cv_prologue(self, prologue, vector_type, vector_full_type, objc_type, vector_name, array_name):
        if not (type_dict.has_key(vector_type) and type_dict[vector_type].has_key("to_cpp") and type_dict[vector_type]["to_cpp"] != "%(n)s.nativeRef"):
            prologue.append("OBJC2CV(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");")
        else:
            conv_macro = "CONV_" + array_name
            prologue.append("#define " + conv_macro + "(e) " + type_dict[vector_type]["to_cpp"] % {"n": "e"})
            prologue.append("OBJC2CV_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + conv_macro + ");")
            prologue.append("#undef " + conv_macro)

    def build_cv2objc_epilogue(self, epilogue, vector_type, vector_full_type, objc_type, vector_name, array_name):
        if not (type_dict.has_key(vector_type) and type_dict[vector_type].has_key("from_cpp") and type_dict[vector_type]["from_cpp"] != ("[" + objc_type[:-1] + " fromNative:%(n)s]")):
            epilogue.append("CV2OBJC(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ");")
        else:
            unconv_macro = "UNCONV_" + array_name
            epilogue.append("#define " + unconv_macro + "(e) " + type_dict[vector_type]["from_cpp"] % {"n": "e"})
            epilogue.append("CV2OBJC_CUSTOM(" + vector_full_type + ", " + objc_type[:-1] + ", " + vector_name + ", " + array_name + ", " + unconv_macro + ");")
            epilogue.append("#undef " + unconv_macro)

    def gen_func(self, ci, fi):
        logging.info("%s", fi)
        method_declarations = ci.method_declarations
        method_implementations = ci.method_implementations

        decl_args = []
        for a in fi.args:
            s = a.ctype or ' _hidden_ '
            if a.pointer:
                s += "*"
            elif a.out:
                s += "&"
            s += " " + a.name
            if a.defval:
                s += " = " + str(a.defval)
            decl_args.append(s)
        c_decl = "%s %s %s(%s)" % ( fi.static, fi.ctype, fi.cname, ", ".join(decl_args) )

        # comment
        method_declarations.write( "\n//\n// %s\n//\n" % c_decl )
        method_implementations.write( "\n//\n// %s\n//\n" % c_decl )
        # check if we 'know' all the types
        if fi.ctype not in type_dict: # unsupported ret type
            msg = "// Return type '%s' is not supported, skipping the function\n\n" % fi.ctype
            self.skipped_func_list.append(c_decl + "\n" + msg)
            method_declarations.write( " "*4 + msg )
            logging.warning("SKIP:" + c_decl.strip() + "\t due to RET type " + fi.ctype)
            return
        for a in fi.args:
            if a.ctype not in type_dict:
                if not a.defval and a.ctype.endswith("*"):
                    a.defval = 0
                if a.defval:
                    a.ctype = ''
                    continue
                msg = "// Unknown type '%s' (%s), skipping the function\n\n" % (a.ctype, a.out or "I")
                self.skipped_func_list.append(c_decl + "\n" + msg)
                method_declarations.write( msg )
                logging.warning("SKIP:" + c_decl.strip() + "\t due to ARG type " + a.ctype + "/" + (a.out or "I"))
                return

        self.ported_func_list.append(c_decl)

        # args
        args = fi.args[:] # copy
        objc_signatures=[]
        while True:
            # method args
            cv_args = []
            prologue = []
            epilogue = []
            if fi.ctype:
                ci.addImports(fi.ctype, False)
            for a in args:
                if not "v_type" in type_dict[a.ctype] and not "v_v_type" in type_dict[a.ctype]:
                    cast = ("(" + type_dict[a.ctype]["cast_to"] + ")") if "cast_to" in type_dict[a.ctype] else ""
                    cv_name = type_dict[a.ctype].get("to_cpp", cast + "%(n)s") if a.ctype else a.defval
                    if a.pointer and not cv_name == "0":
                        cv_name = "&(" + cv_name + ")"
                    if "O" in a.out and type_dict[a.ctype].get("out_type", ""):
                        cv_name = type_dict[a.ctype].get("out_type_ptr" if a.pointer else "out_type_ref", "%(n)s")
                    cv_args.append(type_dict[a.ctype].get("cv_name", cv_name) % {"n": a.name})
                    if not a.ctype: # hidden
                        continue
                    ci.addImports(a.ctype, "O" in a.out)
                if "v_type" in type_dict[a.ctype]: # pass as vector
                    vector_cpp_type = type_dict[a.ctype]["v_type"]
                    objc_type = type_dict[a.ctype]["objc_type"]
                    has_namespace = vector_cpp_type.find("::") != -1
                    ci.addImports(a.ctype, False)
                    vector_full_cpp_type = self.fullTypeName(vector_cpp_type) if not has_namespace else vector_cpp_type
                    vector_cpp_name = a.name + "Vector"
                    cv_args.append(vector_cpp_name)
                    self.build_objc2cv_prologue(prologue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name)
                    if "O" in a.out:
                        self.build_cv2objc_epilogue(epilogue, vector_cpp_type, vector_full_cpp_type, objc_type, vector_cpp_name, a.name)

                if "v_v_type" in type_dict[a.ctype]: # pass as vector of vector
                    vector_cpp_type = type_dict[a.ctype]["v_v_type"]
                    objc_type = type_dict[a.ctype]["objc_type"]
                    ci.addImports(a.ctype, False)
                    vector_full_cpp_type = self.fullTypeName(vector_cpp_type)
                    vector_cpp_name = a.name + "Vector2"
                    cv_args.append(vector_cpp_name)
                    prologue.append("OBJC2CV2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name +  ");")
                    if "O" in a.out:
                        epilogue.append(
                            "CV2OBJC2(" + vector_full_cpp_type + ", " + objc_type[:-1] + ", " + vector_cpp_name + ", " + a.name + ");")

            # calculate method signature to check for uniqueness
            objc_args = build_objc_args(args)
            objc_signature = fi.signature(args)
            logging.info("Objective-C: " + objc_signature)

            if objc_signature in objc_signatures:
                if args:
                    args.pop()
                    continue
                else:
                    break

            # doc comment
            if fi.docstring:
                lines = fi.docstring.splitlines()
                toWrite = []
                for index, line in enumerate(lines):
                    p0 = line.find("@param")
                    if p0 != -1:
                        p0 += 7 # len("@param" + 1)
                        p1 = line.find(' ', p0)
                        p1 = len(line) if p1 == -1 else p1
                        name = line[p0:p1]
                        for arg in args:
                            if arg.name == name:
                                toWrite.append(re.sub('\*\s*@param ', '* @param ', line))
                                break
                    else:
                        s0 = line.find("@see")
                        if s0 != -1:
                            sees = line[(s0 + 5):].split(",")
                            toWrite.append(line[:(s0 + 5)] + ", ".join(["`" + see_lookup(ci.objc_name, see.strip()) + "`" for see in sees]))
                        else:
                            toWrite.append(line)

                for line in toWrite:
                    method_declarations.write(line + "\n")

            # public wrapper method impl (calling native one above)
            # e.g.
            # public static void add( Mat src1, Mat src2, Mat dst, Mat mask, int dtype )
            # { add_0( src1.nativeObj, src2.nativeObj, dst.nativeObj, mask.nativeObj, dtype );  }
            ret_type = fi.ctype
            if fi.ctype.endswith('*'):
                ret_type = ret_type[:-1]
            ret_val = self.fullTypeName(fi.ctype) + " retVal = "
            ret = "return retVal;"
            tail = ""
            constructor = False
            if "v_type" in type_dict[ret_type]:
                objc_type = type_dict[ret_type]["objc_type"]
                vector_type = type_dict[ret_type]["v_type"]
                full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type
                prologue.append("NSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];")
                ret_val = "std::vector<" + full_cpp_type + "> retValVector = "
                self.build_cv2objc_epilogue(epilogue, vector_type, full_cpp_type, objc_type, "retValVector", "retVal")
            elif "v_v_type" in type_dict[ret_type]:
                objc_type = type_dict[ret_type]["objc_type"]
                cpp_type = type_dict[ret_type]["v_v_type"]
                if cpp_type.find("::") == -1:
                    cpp_type = self.get_namespace_prefix(cpp_type) + cpp_type
                prologue.append("NSMutableArray<NSMutableArray<" + objc_type + ">*>* retVal = [NSMutableArray new];")
                ret_val = "std::vector<" + cpp_type + "> retValVector = "
                epilogue.append("CV2OBJC2(" + cpp_type + ", " + objc_type[:-1] + ", retValVector, retVal);")
            elif ret_type.startswith("Ptr_"):
                cpp_type = type_dict[ret_type]["c_type"]
                namespace_prefix = self.get_namespace_prefix(cpp_type)
                ret_val = "cv::Ptr<" + namespace_prefix + cpp_type + "> retVal = "
                ret = "return [" + type_dict[ret_type]["objc_type"][:-1] + " fromNative:retVal];"
            elif ret_type == "void":
                ret_val = ""
                ret = ""
            elif ret_type == "": # c-tor
                constructor = True
                ret_val = "return [self initWithNativePtr:cv::Ptr<" + fi.fullClass(isCPP=True) + ">(new "
                tail = ")]"
                ret = ""
            elif self.isWrapped(ret_type): # wrapped class
                namespace_prefix = self.get_namespace_prefix(ret_type)
                ret_val = "cv::Ptr<" + namespace_prefix + ret_type + "> retVal = new " + namespace_prefix + ret_type + "("
                tail = ")"
                ret_type_dict = type_dict[ret_type]
                from_cpp = ret_type_dict["from_cpp_ptr"] if ret_type_dict.has_key("from_cpp_ptr") else ret_type_dict["from_cpp"]
                ret = "return " + (from_cpp % { "n" : "retVal" }) + ";"
            elif "from_cpp" in type_dict[ret_type]:
                ret = "return " + (type_dict[ret_type]["from_cpp"] % { "n" : "retVal" }) + ";"

            static = fi.static if fi.classname else True

            objc_ret_type = type_dict[fi.ctype]["objc_type"] if type_dict[fi.ctype]["objc_type"] else "void" if not constructor else "instancetype"
            if "v_type" in type_dict[ret_type]:
                objc_ret_type = "NSArray<" + objc_ret_type + ">*"
            elif "v_v_type" in type_dict[ret_type]:
                objc_ret_type = "NSArray<NSArray<" + objc_ret_type + ">*>*"

            prototype = Template("$static ($objc_ret_type)$objc_name$objc_args").substitute(
                    static = "+" if static else "-",
                    objc_ret_type = objc_ret_type,
                    objc_args = " ".join(objc_args),
                    objc_name = fi.objc_name if not constructor else ("init" + ("With" + (args[0].name[0].upper() + args[0].name[1:]) if len(args) > 0 else ""))
                )

            method_declarations.write( Template(
"""$prototype$swift_name$deprecation_decl;

"""
                ).substitute(
                    prototype = prototype,
                    swift_name = " NS_SWIFT_NAME(" + fi.swift_name + "(" + build_swift_signature(args) + "))" if not constructor else "",
                    deprecation_decl = " DEPRECATED_ATTRIBUTE" if fi.deprecated else ""
                )
            )

            method_implementations.write( Template(
"""$prototype {$prologue
    $ret_val$obj_deref$cv_name($cv_args)$tail;$epilogue$ret
}

"""
                ).substitute(
                    prototype = prototype,
                    ret = "\n    " + ret if ret else "",
                    ret_val = ret_val,
                    prologue = "\n    " + "\n    ".join(prologue) if prologue else "",
                    epilogue = "\n    " + "\n    ".join(epilogue) if epilogue else "",
                    static = "+" if static else "-",
                    obj_deref = ("self." + ci.native_ptr_name + "->") if not static and not constructor else "",
                    cv_name = fi.cv_name if static else fi.fullClass(isCPP=True) if constructor else fi.name,
                    cv_args = ", ".join(cv_args),
                    tail = tail
                )
            )
            # adding method signature to dictionary
            objc_signatures.append(objc_signature)

            # processing args with default values
            if args and args[-1].defval:
                args.pop()
            else:
                break

    def gen_class(self, ci):
        logging.info("%s", ci)
        if ci.name in AdditionalImports:
            ci.additionalImports.write("\n".join(["#import %s" % h for h in AdditionalImports[ci.name]]))

        # constants
        wrote_consts_pragma = False
        consts_map = {c.name: c for c in ci.private_consts}
        consts_map.update({c.name: c for c in ci.consts})
        def const_value(v):
            if v in consts_map:
                target = consts_map[v]
                assert target.value != v
                return const_value(target.value)
            return v
        if ci.consts:
            enumTypes = set(map(lambda c: c.enumType, ci.consts))
            grouped_consts = {enumType: [c for c in ci.consts if c.enumType == enumType] for enumType in enumTypes}
            for typeName, consts in grouped_consts.items():
                logging.info("%s", consts)
                if typeName:
                    typeName = typeName.rsplit(".", 1)[-1]
                    if enum_fix.has_key(ci.cname):
                        typeName = enum_fix[ci.cname].get(typeName, typeName)

                    ci.enum_declarations.write("""
// C++: enum {1}
typedef NS_ENUM(int, {2}) {{
    {0}\n}};\n\n""".format(",\n    ".join(["%s = %s" % (c.name, c.value) for c in consts]), typeName, typeName)
                    )
                else:
                    if not wrote_consts_pragma:
                        ci.method_declarations.write("#pragma mark - Class Constants\n\n")
                        wrote_consts_pragma = True
                    ci.method_declarations.write("""
{0}\n\n""".format("\n".join(["@property (class, readonly) int %s NS_SWIFT_NAME(%s);" % (c.name, c.name) for c in consts]))
                    )
                    declared_consts = []
                    match_alphabet = re.compile("[a-zA-Z]")
                    for c in consts:
                        value = str(c.value)
                        if match_alphabet.search(value):
                            for declared_const in sorted(declared_consts, key=len, reverse=True):
                                regex = re.compile("(?<!" + ci.cname + ".)" + declared_const)
                                value = regex.sub(ci.cname + "." + declared_const, value)
                        ci.method_implementations.write("+ (int)%s {\n    return %s;\n}\n\n" % (c.name, value))
                        declared_consts.append(c.name)

        ci.method_declarations.write("#pragma mark - Methods\n\n")

        # methods
        for fi in ci.getAllMethods():
            self.gen_func(ci, fi)
        # props
        for pi in ci.props:
            ci.method_declarations.write("\n    //\n    // C++: %s %s::%s\n    //\n\n" % (pi.ctype, ci.fullName(isCPP=True), pi.name))
            type_data = type_dict[pi.ctype] if pi.ctype != "uchar" else {"objc_type" : "unsigned char", "is_primitive" : True}
            objc_type = type_data.get("objc_type", pi.ctype)
            ci.addImports(pi.ctype, False)
            ci.method_declarations.write("@property " + ("(readonly) " if not pi.rw else "") + objc_type + " " + pi.name + ";\n")
            ptr_ref = "self." + ci.native_ptr_name + "->" if not ci.is_base_class else "self.nativePtr->"
            if type_data.has_key("v_type"):
                vector_type = type_data["v_type"]
                full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type
                ret_val = "std::vector<" + full_cpp_type + "> retValVector = "
                ci.method_implementations.write("-(NSArray<" + objc_type + ">*)" + pi.name + "{\n")
                ci.method_implementations.write("\tNSMutableArray<" + objc_type + ">* retVal = [NSMutableArray new];\n")
                ci.method_implementations.write("\t" + ret_val + ptr_ref + pi.name + ";\n")
                epilogue = []
                self.build_cv2objc_epilogue(epilogue, vector_type, full_cpp_type, objc_type, "retValVector", "retVal")
                ci.method_implementations.write("\t" + ("\n\t".join(epilogue)) + "\n")
                ci.method_implementations.write("\treturn retVal;\n}\n\n")
            else:
                from_cpp = type_data.get("from_cpp", "%(n)s")
                retVal = from_cpp % {"n": (ptr_ref + pi.name)}
                ci.method_implementations.write("-(" + objc_type + ")" + pi.name + "{\n\treturn " + retVal + ";\n}\n\n")
            if pi.rw:
                if type_data.has_key("v_type"):
                    vector_type = type_data["v_type"]
                    full_cpp_type = (self.get_namespace_prefix(vector_type) if (vector_type.find("::") == -1) else "") + vector_type
                    ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(NSArray<" + objc_type + ">*)" + pi.name + "{\n")
                    prologue = []
                    self.build_objc2cv_prologue(prologue, vector_type, full_cpp_type, objc_type, "valVector", pi.name)
                    ci.method_implementations.write("\t" + ("\n\t".join(prologue)) + "\n")
                    ci.method_implementations.write("\t" + ptr_ref + pi.name + " = valVector;\n}\n\n")
                else:
                    to_cpp = type_data.get("to_cpp", "%(n)s")
                    val = to_cpp % {"n": pi.name}
                    ci.method_implementations.write("-(void)set" + pi.name[0].upper() + pi.name[1:] + ":(" + objc_type + ")" + pi.name + " {\n\t" + ptr_ref + pi.name + " = " + val + ";\n}\n\n")

        # manual ports
        if ci.name in ManualFuncs:
            for func in ManualFuncs[ci.name].keys():
                ci.method_declarations.write( "\n".join(ManualFuncs[ci.name][func]["declaration"]) )
                ci.method_implementations.write( "\n".join(ManualFuncs[ci.name][func]["implementation"]) )

    def getClass(self, classname):
        return self.classes[classname or self.Module]

    def isWrapped(self, classname):
        name = classname or self.Module
        return name in self.classes

    def isSmartClass(self, ci):
        '''
        Check if class stores Ptr<T>* instead of T* in nativeObj field
        '''
        if ci.smart != None:
            return ci.smart

        # if parents are smart (we hope) then children are!
        # if not we believe the class is smart if it has "create" method
        ci.smart = False
        if ci.base or ci.name == 'Algorithm':
            ci.smart = True
        else:
            for fi in ci.methods:
                if fi.name == "create":
                    ci.smart = True
                    break

        return ci.smart

    def smartWrap(self, ci, fullname):
        '''
        Wraps fullname with Ptr<> if needed
        '''
        if self.isSmartClass(ci):
            return "Ptr<" + fullname + ">"
        return fullname

    def finalize(self, output_objc_path):
        opencv_header_file = os.path.join(output_objc_path, framework_name + ".h")
        self.save(opencv_header_file, '\n'.join(['#import "%s"' % os.path.basename(f) for f in self.header_files if os.path.basename(f) != "CVObjcUtil.h"]))
        cmakelist_template = read_contents(os.path.join(SCRIPT_DIR, 'templates/cmakelists.template'))
        cmakelist = Template(cmakelist_template).substitute(modules = ";".join(modules), framework = framework_name)
        self.save(os.path.join(dstdir, "CMakeLists.txt"), cmakelist)
        mkdir_p("./framework_build")
        mkdir_p("./test_build")
        mkdir_p("./doc_build")
        copyfile(os.path.join(SCRIPT_DIR, '../doc/README.md'), "./doc_build/README.md")
        if framework_name != "OpenCV":
            for dirname, dirs, files in os.walk(os.path.join(testdir, "test")):
                for filename in files:
                    filepath = os.path.join(dirname, filename)
                    with open(filepath) as file:
                        body = file.read()
                    body = body.replace("import OpenCV", "import " + framework_name)
                    body = body.replace("#import <OpenCV/OpenCV.h>", "#import <" + framework_name + "/" + framework_name + ".h>")
                    with open(filepath, "w") as file:
                        file.write(body)


def copy_objc_files(objc_files_dir, objc_base_path, module_path, include = False):
    global total_files, updated_files
    objc_files = []
    re_filter = re.compile(r'^.+\.(h|m|mm|swift)$')
    for root, dirnames, filenames in os.walk(objc_files_dir):
       objc_files += [os.path.join(root, filename) for filename in filenames if re_filter.match(filename)]
    objc_files = [f.replace('\\', '/') for f in objc_files]

    re_prefix = re.compile(r'^.+/(.+)\.(h|m|mm|swift)$')
    for objc_file in objc_files:
        src = objc_file
        m = re_prefix.match(objc_file)
        target_fname = (m.group(1) + '.' + m.group(2)) if m else os.path.basename(objc_file)
        dest = os.path.join(objc_base_path, os.path.join(module_path, target_fname))
        mkdir_p(os.path.dirname(dest))
        total_files += 1
        if include and m.group(2) == 'h':
            generator.header_files.append(dest)
        if (not os.path.exists(dest)) or (os.stat(src).st_mtime - os.stat(dest).st_mtime > 1):
            copyfile(src, dest)
            updated_files += 1

def unescape(str):
    return str.replace("&lt;", "<").replace("&gt;", ">").replace("&amp;", "&")

def escape_underscore(str):
    return str.replace('_', '\\_')

def escape_texttt(str):
    return re.sub(re.compile('texttt{(.*?)\}', re.DOTALL), lambda x: 'texttt{' + escape_underscore(x.group(1)) + '}', str)

def get_macros(tex):
    out = ""
    if re.search("\\\\fork\s*{", tex):
        out += "\\newcommand{\\fork}[4]{ \\left\\{ \\begin{array}{l l} #1 & \\text{#2}\\\\\\\\ #3 & \\text{#4}\\\\\\\\ \\end{array} \\right.} "
    if re.search("\\\\vecthreethree\s*{", tex):
        out += "\\newcommand{\\vecthreethree}[9]{ \\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\end{bmatrix} } "
    return out

def fix_tex(tex):
    macros = get_macros(tex)
    fix_escaping = escape_texttt(unescape(tex))
    return macros + fix_escaping

def sanitize_documentation_string(doc, type):
    if type == "class":
        doc = doc.replace("@param ", "")

    doc = re.sub(re.compile('`\\$\\$(.*?)\\$\\$`', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
    doc = re.sub(re.compile('\\\\f\\{align\\*\\}\\{?(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc)
    doc = re.sub(re.compile('\\\\f\\{equation\\*\\}\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$\\begin{aligned} ' + fix_tex(x.group(1)) + ' \\end{aligned}$$`', doc)
    doc = re.sub(re.compile('\\\\f\\$(.*?)\\\\f\\$', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
    doc = re.sub(re.compile('\\\\f\\[(.*?)\\\\f\\]', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)
    doc = re.sub(re.compile('\\\\f\\{(.*?)\\\\f\\}', re.DOTALL), lambda x: '`$$' + fix_tex(x.group(1)) + '$$`', doc)

    doc = doc.replace("@anchor", "") \
        .replace("@brief ", "").replace("\\brief ", "") \
        .replace("@cite", "CITE:") \
        .replace("@code{.cpp}", "<code>") \
        .replace("@code{.txt}", "<code>") \
        .replace("@code", "<code>") \
        .replace("@copydoc", "") \
        .replace("@copybrief", "") \
        .replace("@date", "") \
        .replace("@defgroup", "") \
        .replace("@details ", "") \
        .replace("@endcode", "</code>") \
        .replace("@endinternal", "") \
        .replace("@file", "") \
        .replace("@include", "INCLUDE:") \
        .replace("@ingroup", "") \
        .replace("@internal", "") \
        .replace("@overload", "") \
        .replace("@param[in]", "@param") \
        .replace("@param[out]", "@param") \
        .replace("@ref", "REF:") \
        .replace("@returns", "@return") \
        .replace("@sa ", "@see ") \
        .replace("@snippet", "SNIPPET:") \
        .replace("@todo", "TODO:") \

    lines = doc.splitlines()

    in_code = False
    for i,line in enumerate(lines):
        if line.find("</code>") != -1:
            in_code = False
            lines[i] = line.replace("</code>", "")
        if in_code:
            lines[i] = unescape(line)
        if line.find("<code>") != -1:
            in_code = True
            lines[i] = line.replace("<code>", "")

    lines = list(map(lambda x: x[x.find('*'):].strip() if x.lstrip().startswith("*") else x, lines))
    lines = list(map(lambda x: "* " + x[1:].strip() if x.startswith("*") and x != "*" else x, lines))
    lines = list(map(lambda x: x if x.startswith("*") else "* " + x if x and x != "*" else "*", lines))

    hasValues = False
    for line in lines:
        if line != "*":
            hasValues = True
            break
    return "/**\n " + "\n ".join(lines) + "\n */" if hasValues else ""

if __name__ == "__main__":
    # initialize logger
    logging.basicConfig(filename='gen_objc.log', format=None, filemode='w', level=logging.INFO)
    handler = logging.StreamHandler()
    handler.setLevel(logging.WARNING)
    logging.getLogger().addHandler(handler)

    # parse command line parameters
    import argparse
    arg_parser = argparse.ArgumentParser(description='OpenCV Objective-C Wrapper Generator')
    arg_parser.add_argument('-p', '--parser', required=True, help='OpenCV header parser')
    arg_parser.add_argument('-c', '--config', required=True, help='OpenCV modules config')
    arg_parser.add_argument('-t', '--target', required=True, help='Target (either ios or osx)')
    arg_parser.add_argument('-f', '--framework', required=True, help='Framework name')

    args=arg_parser.parse_args()

    # import header parser
    hdr_parser_path = os.path.abspath(args.parser)
    if hdr_parser_path.endswith(".py"):
        hdr_parser_path = os.path.dirname(hdr_parser_path)
    sys.path.append(hdr_parser_path)
    import hdr_parser

    with open(args.config) as f:
        config = json.load(f)

    ROOT_DIR = config['rootdir']; assert os.path.exists(ROOT_DIR)

    dstdir = "./gen"
    testdir = "./test"
    objc_base_path = os.path.join(dstdir, 'objc'); mkdir_p(objc_base_path)
    objc_test_base_path = testdir; mkdir_p(objc_test_base_path)
    copy_objc_files(os.path.join(SCRIPT_DIR, '../test/test'), objc_test_base_path, 'test', False)
    copy_objc_files(os.path.join(SCRIPT_DIR, '../test/dummy'), objc_test_base_path, 'dummy', False)
    copyfile(os.path.join(SCRIPT_DIR, '../test/cmakelists.template'), os.path.join(objc_test_base_path, 'CMakeLists.txt'))

    # launch Objective-C Wrapper generator
    generator = ObjectiveCWrapperGenerator()

    gen_dict_files = []
    modules = []
    framework_name = args.framework

    print("Objective-C: Processing OpenCV modules: %d" % len(config['modules']))
    for e in config['modules']:
        (module, module_location) = (e['name'], os.path.join(ROOT_DIR, e['location']))
        logging.info("\n=== MODULE: %s (%s) ===\n" % (module, module_location))
        modules.append(module)

        module_imports = []
        srcfiles = []
        common_headers = []

        misc_location = os.path.join(module_location, 'misc/objc')

        srcfiles_fname = os.path.join(misc_location, 'filelist')
        if os.path.exists(srcfiles_fname):
            with open(srcfiles_fname) as f:
                srcfiles = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()]
        else:
            re_bad = re.compile(r'(private|.inl.hpp$|_inl.hpp$|.details.hpp$|_winrt.hpp$|/cuda/|/legacy/)')
            # .h files before .hpp
            h_files = []
            hpp_files = []
            for root, dirnames, filenames in os.walk(os.path.join(module_location, 'include')):
               h_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.h')]
               hpp_files += [os.path.join(root, filename) for filename in fnmatch.filter(filenames, '*.hpp')]
            srcfiles = h_files + hpp_files
            srcfiles = [f for f in srcfiles if not re_bad.search(f.replace('\\', '/'))]
        logging.info("\nFiles (%d):\n%s", len(srcfiles), pformat(srcfiles))

        common_headers_fname = os.path.join(misc_location, 'filelist_common')
        if os.path.exists(common_headers_fname):
            with open(common_headers_fname) as f:
                common_headers = [os.path.join(module_location, str(l).strip()) for l in f.readlines() if str(l).strip()]
        logging.info("\nCommon headers (%d):\n%s", len(common_headers), pformat(common_headers))

        gendict_fname = os.path.join(misc_location, 'gen_dict.json')
        if os.path.exists(gendict_fname):
            with open(gendict_fname) as f:
                gen_type_dict = json.load(f)
            class_ignore_list += gen_type_dict.get("class_ignore_list", [])
            const_ignore_list += gen_type_dict.get("const_ignore_list", [])
            const_private_list += gen_type_dict.get("const_private_list", [])
            missing_consts.update(gen_type_dict.get("missing_consts", {}))
            type_dict.update(gen_type_dict.get("type_dict", {}))
            AdditionalImports.update(gen_type_dict.get("AdditionalImports", {}))
            ManualFuncs.update(gen_type_dict.get("ManualFuncs", {}))
            func_arg_fix.update(gen_type_dict.get("func_arg_fix", {}))
            enum_fix.update(gen_type_dict.get("enum_fix", {}))
            namespaces_dict.update(gen_type_dict.get("namespaces_dict", {}))
            module_imports += gen_type_dict.get("module_imports", [])

        objc_files_dir = os.path.join(misc_location, 'common')
        if os.path.exists(objc_files_dir):
            copy_objc_files(objc_files_dir, objc_base_path, module, True)
        if args.target == 'ios':
            ios_files_dir = os.path.join(misc_location, 'ios')
            if os.path.exists(ios_files_dir):
                copy_objc_files(ios_files_dir, objc_base_path, module, True)

        objc_test_files_dir = os.path.join(misc_location, 'test')
        if os.path.exists(objc_test_files_dir):
            copy_objc_files(objc_test_files_dir, objc_test_base_path, 'test', False)
            objc_test_resources_dir = os.path.join(objc_test_files_dir, 'resources')
            if os.path.exists(objc_test_resources_dir):
                copy_tree(objc_test_resources_dir, os.path.join(objc_test_base_path, 'test', 'resources'))

        if len(srcfiles) > 0:
            generator.gen(srcfiles, module, dstdir, objc_base_path, common_headers)
        else:
            logging.info("No generated code for module: %s", module)
    generator.finalize(objc_base_path)

    print('Generated files: %d (updated %d)' % (total_files, updated_files))
