#!/usr/bin/env python

import argparse
import CppHeaderParser
import re
import sys
import yaml
import copy
import six
import os.path
import traceback

class CppDefine(dict):
    def __init__(self):
        self["name"] = None
        self["parameters"] = []
        self["line_number"] = -1

class CppDefineParameter(dict):
    def __init__(self):
        self["name"] = None
        self["line_number"] = -1

class CppNamespace(dict):
    def __init__(self):
        self["name"] = None
        self["line_number"] = -1

class CppFileName(dict):
    def __init__(self):
        self["name"] = None
        self["line_number"] = -1

class Application(object):
    def __init__(self):
        description='''A styler just target to naming conventions of source
        code'''

        parser = argparse.ArgumentParser(description=description)
        parser.add_argument("-c", "--config",
            help="Configuration file path (In YAML format)",
            required=True)
        parser.add_argument("-o", "--output", help="Output file path")
        parser.add_argument("-d", "--debug", action='store_true', help="Print trace stack")
        parser.add_argument("file_path", help="Source file path")

        self.__args = parser.parse_args()

        # If user does not specific output path, we default it to input file
        # path
        if self.__args.output is None:
            self.__args.output = self.__args.file_path

        self.__config = yaml.load(open(self.__args.config))
        old_base = self.__config["_base_"]
        self.__config["_base_"] = {
            "re":"[a-zA-Z0-9_]+",
            "error": "",
            }
        self.__config["_base_"].update(old_base)

    def parse_define(self, adefine):
        matched = re.match(r"[^\w]*(\w+)(?:\(([^\)]*)\)|\s*).*", adefine)
        name = matched.group(1)
        parameters = []
        if matched.group(2) is not None:
            parameter_names = matched.group(2).split(',')
            for parameter_name in parameter_names:
                aparameter = CppDefineParameter()
                aparameter["name"] = parameter_name.strip()
                parameters.append(aparameter)

        result = CppDefine()
        result["name"] = name
        result["parameters"] = parameters
        return result

    def _is_special_method(self, amethod):
        if isinstance(amethod, six.string_types):
            amethod_name = amethod
        else:
            amethod_name = amethod["name"]

        founded = re.findall(r"(?:^|[^\w]+)operator[^\w]+", amethod_name)
        if len(founded) <= 0:
            if re.match(r"(?:^|.*\W)operator\W.*", amethod["debug"]) is not None:
                return True

            return False

        return True

    def _get_argument_name(self, an_argument):
        if isinstance(an_argument, six.string_types):
            return an_argument

        if len(an_argument["name"]) > 0:
            return an_argument["name"]

        # If it's a functor?? with "class name::function" style
        matched = re.match(r"^\w+\s*\(\w*::\*(\w+)\)\(.*$", an_argument["type"])
        if matched is None:
            # with normal "function" style
            matched = re.match(r"[^\(]*\([^\)]*\W(\w+)\W.*\).*", an_argument["type"])

        if matched is None:
            return ""
        else:
            return matched.group(1)

    def _get_config(self, name):
        override_table = {
            "class": "_base_",
            "function": "_base_",
            "variant": "_base_",
            "namespace": "_base_",
            "define": "_base_",
            "filename": "_base_", # Special config use to define filename rule

            "argument": "variant",
            "static_variant": "variant",
            "global_variant": "variant",
            "function_argument": "argument",
            "class_method_argument": "function_argument",
            "struct_method_argument": "class_method_argument",
            "define_function_argument": "function_argument",
            "define_function": "function",
            "class_method": "function",
            "struct_method": "class_method",
            "class_variant": "variant",
            "struct_variant": "class_variant",
            "typedef": "class",
            "struct": "class",
            "enum": "class",
            "enum_value": "define",
            "union": "struct",
        }

        my_config = dict()

        if name in override_table:
            base_name = override_table[name]
            my_config.update(self._get_config(base_name))

        if name in self.__config:
            my_config.update(self.__config[name])

        return my_config

    def _is_valid_variable(self, cpp_variable):
        if cpp_variable["type"] == "return":
            return False

        if len(cpp_variable["type"]) <= 0:
            return False

        return True

    def _get_cpp_method_re(self, name):
        prefix = "operator"
        if not name.startswith(prefix):
            return re.escape(name)

        # Operator methods
        chars = []
        for achar in name[len(prefix):]:
            chars.append("\\s*")
            if achar.isalnum():
                chars.append(achar)
            else:
                chars.append("\\")
                chars.append(achar)

        return "operator%s" % ''.join(chars)

    def _validate_codes_of_cpp_method(self, cpp_method):
        start_line_index = cpp_method["line_number"] - 1
        # Extract cpp method codes
        rest_lines = self._source_lines[start_line_index:]
        content = '\n'.join(rest_lines)
        code_lines = []
        name_re = self._get_cpp_method_re(cpp_method["name"])
        name_start_pos = re.search(name_re, content).span()[0]

        parameters_start_pos = content.index('(', name_start_pos)
        parameters_stop_pos = content.index(')', parameters_start_pos)

        stack = []

        try:
            i = content.index('{', parameters_stop_pos + 1)
        except ValueError:
            return;

        try:
            semicolonPos = content.index(';', parameters_stop_pos + 1)
            if semicolonPos <= i:
                return;
        except ValueError:
            # Not found a semicolon, just ignored.
            pass

        skipped_lines = cpp_method["line_number"] + content.count("\n", 0, i) - 2

        stack.append(i)
        i += 1
        first_i = i
        last_i = 0
        is_finding_block_comment = False
        is_finding_single_comment = False
        while (len(stack) > 0) and (i < len(content)):
            c = content[i]

            if is_finding_block_comment:
                # If finding block comment, then skip all other searching
                if (c == "*") and (content[i + 1] == "/"):
                    is_finding_block_comment = False
            elif (c == "/") and (content[i + 1] == "*"):
                is_finding_block_comment = True
            elif is_finding_single_comment:
                # If finding single comment, then skip all other searching
                if c == "\n":
                    is_finding_single_comment = False
            elif (c == "/") and (content[i + 1] == "/"):
                is_finding_single_comment = True
            elif c == "{":
                stack.append(i)
            elif c == "}":
                last_i = i
                del stack[len(stack) - 1]

            i += 1

        if len(stack) <= 0:
            content = content[first_i:last_i]
            founded = re.findall(r"\w+\W+(\w+)\s*=[^=]", content)
            for aname in founded:
                avariant = dict()
                avariant["name"] = aname
                avariant["line_number"] = cpp_method["line_number"]
                self._validate_name(avariant, "variant")

    def _validate_name(self, cpp_object, name_re):
        cpp_object_name = ""
        if isinstance(cpp_object, six.string_types):
            cpp_object_name = cpp_object
            cpp_object = dict()
            cpp_object["name"] = cpp_object_name
            cpp_object["line_number"] = -1
        elif "name" in cpp_object:
            cpp_object_name = cpp_object["name"]
            if ('<' in cpp_object_name) and ("debug" in cpp_object):
                matched = re.match(r".*?(\w+)\W+$", cpp_object["debug"])
                if matched is not None:
                    cpp_object_name = matched.group(1)
        else:
            return

        # Parse union like names
        splitted = cpp_object_name.split()
        if len(splitted) > 1:
            cpp_object_name = splitted[-1]

        if '...' in cpp_object_name:
            # Does not have valid name, we must not check it .
            return

        if len(cpp_object_name) <= 0:
            # Does not have valid name, we must not check it .
            return

        matched = re.match(self._get_config(name_re)["re"], cpp_object_name)
        if matched is None:
            filename = os.path.basename(self.__args.file_path)
            error_message = self._get_config(name_re)["error"]
            if len(error_message) > 0:
                error_message = "%s %s" % (
                    ' '.join([rule_name.capitalize() for rule_name in name_re.split("_")]),
                    error_message)

            if self.__args.debug:
                traceback.print_stack()

            raise SyntaxError("%s:%s:error: Name '%s' isn't matched with rule : %s! %s" % (
                filename,
                cpp_object["line_number"],
                cpp_object_name,
                name_re,
                error_message))

    def _get_class_realname(self, class_name):
        return re.match(r"(\w+).*", class_name).group(1)

    def _validate_cpp_object(self, cpp_object):
        cpp_object_type = type(cpp_object)

        if cpp_object_type == CppDefine:
            if len(cpp_object["parameters"]) <= 0:
                # Normal Define Name
                self._validate_name(cpp_object, "define")
            else:
                # Function Liked Define Name
                self._validate_name(cpp_object, "define_function")
                for aparameter in cpp_object["parameters"]:
                    self._validate_name(aparameter, "define_function_argument")
        elif cpp_object_type == CppHeaderParser.CppClass:
            if "struct" in cpp_object["declaration_method"]:
                class_re = "struct"
                class_method_re = "struct_method"
                class_method_argument_re = "struct_method_argument"
                class_variant_re = "struct_variant"
            else:
                class_re = "class"
                class_method_re = "class_method"
                class_method_argument_re = "class_method_argument"
                class_variant_re = "class_variant"
            self._validate_name(cpp_object, class_re)

            for amethod in cpp_object.get_all_methods():
                matched = re.match(r".*typedef\W[^\(]*\([^\)]*\W(\w+)\W.*\).*", amethod["debug"])
                if matched is None:
                    self._validate_codes_of_cpp_method(amethod)
                    if not self._is_special_method(amethod):
                        if ((amethod["name"] != self._get_class_realname(cpp_object["name"]))
                            and (not amethod.get("constructor", False))
                            and (not amethod.get("destructor", False))):
                            try:
                                self._validate_name(amethod, class_method_re)
                            except SyntaxError:
                                is_need_reraise = True
                                try:
                                    self._validate_name(amethod, "define_function")
                                    is_need_reraise = False
                                except SyntaxError:
                                    pass

                                if is_need_reraise:
                                    raise

                    for aparameter in amethod["parameters"]:
                        an_object = dict()
                        an_object["line_number"] = aparameter["line_number"]
                        if (aparameter["type"].endswith("::*")
                            and (")" in aparameter["name"])):
                            an_object["name"] = re.match(r"(\w+).*", aparameter["name"]).group(1)
                            try:
                                self._validate_name(an_object,
                                                    class_method_re)
                            except SyntaxError:
                                is_need_reraise = True
                                try:
                                    self._validate_name(amethod, "define_function")
                                    is_need_reraise = False
                                except SyntaxError:
                                    pass

                                if is_need_reraise:
                                    raise
                        else:
                            an_object["name"] = self._get_argument_name(aparameter)
                            self._validate_name(an_object,
                                                class_method_argument_re)
                else:
                    self._validate_name(
                        {"name":matched.group(1), "line_number":amethod["line_number"]},
                        "typedef")

            for access_specifier in CppHeaderParser.supportedAccessSpecifier:
                for amember in cpp_object["properties"][access_specifier]:
                    is_skip_validate = False
                    if ("type" in amember) and (amember["type"] is not None):
                        internal_predeclares = ["class", "struct", "union"]
                        if amember["type"] in internal_predeclares:
                            is_skip_validate = True

                    if not is_skip_validate:
                        if amember["static"]:
                            self._validate_name(amember, "static_variant")
                        else:
                            self._validate_name(amember, class_variant_re)

                for amember in cpp_object["structs"][access_specifier]:
                    self._validate_cpp_object(amember)

                for amember in cpp_object["enums"][access_specifier]:
                    self._validate_cpp_object(amember)

        elif cpp_object_type == CppHeaderParser.CppStruct:
            self._validate_name(cpp_object, "struct")

        elif cpp_object_type == CppHeaderParser.CppEnum:
            self._validate_name(cpp_object, "enum")

            line_number = -1
            if "line_number" in cpp_object:
                line_number = cpp_object["line_number"]

            for amember in cpp_object["values"]:
                # Use parent line number if enum value does not have it's line
                # number
                if "line_number" not in amember:
                    amember["line_number"] = line_number
                self._validate_name(amember, "enum_value")

        elif cpp_object_type == CppHeaderParser.CppVariable:
            if cpp_object["type"] != "return":
                if cpp_object["static"]:
                    self._validate_name(cpp_object, "static_variant")
                elif cpp_object["type"] not in ["class", "struct", "union"]:
                    if not cpp_object["type"].endswith("::"):
                        # Don't parse variable that implemented outside of
                        # template class. It's already be parsed when parsing
                        # the class.
                        self._validate_name(cpp_object, "global_variant")

        elif cpp_object_type == CppHeaderParser.CppMethod:
            # Exclude "main" function while parsing global function
            while True:
                # FIXME: Parse special case : "struct RArraySize <T ( & ) [ N ]> {"
                if "debug" in cpp_object:
                    if re.match(r".*\>\s*{$", cpp_object["debug"]) is not None:
                        break

                self._validate_codes_of_cpp_method(cpp_object)
                if cpp_object["name"] == "main":
                    break

                if self._is_special_method(cpp_object):
                    break

                if (cpp_object["class"] is None) or (len(cpp_object["class"]) <= 0):
                    if ">" in cpp_object["name"]:
                        regex = r"^[^<:]*?(?:(\w+)::)?(\w+)\s*<"
                        matched = re.search(regex, cpp_object["debug"])
                        if matched.group(1) is not None:
                            cpp_object["class"] = matched.group(1)

                        cpp_object["name"] = matched.group(2)
                        self._validate_name(cpp_object, "class_method")
                    elif len(cpp_object["returns"]) > 0:
                        # If a function does not have return value(at least
                        # "void"), it maybe macro invokes.

                        # FIXME: We just ignored this situation:
                        # Code Snippets: static RSignal<void(int)> sReceived;
                        if "<" not in cpp_object["name"]:
                            self._validate_name(cpp_object, "function")

                    break

                if self._get_class_realname(cpp_object["class"]) == cpp_object["name"]:
                    # Constructor / Destructor will the same with class name
                    break

                self._validate_name(cpp_object, "class_method")
                break

        elif cpp_object_type == CppHeaderParser.CppUnion:
            self._validate_name(cpp_object, "union")

        elif cpp_object_type == CppNamespace:
            self._validate_name(cpp_object, "namespace")

        elif cpp_object_type == CppFileName:
            self._validate_name(cpp_object, "filename")

    def exec_(self):
        try:

            with open(self.__args.file_path, "r") as source_file:
                # For later parse by _validate_codes_of_cpp_method()
                self._source_lines = source_file.readlines()

            parsed_info = CppHeaderParser.CppHeader(self.__args.file_path)

            # Verify File Names
            filename = os.path.basename(self.__args.file_path)
            cpp_object = CppFileName()
            cpp_object["name"] = filename
            self._validate_cpp_object(cpp_object)

            # Verify Define Names
            for define_text in parsed_info.defines:
                self._validate_cpp_object(self.parse_define(define_text))

            # Verify Function Names
            for cpp_object in parsed_info.functions:
                self._validate_cpp_object(cpp_object)

            # Verify Class Names
            for cpp_object in parsed_info.classes_order:
                self._validate_cpp_object(cpp_object)

            # Verify Struct Names
            for cpp_object in parsed_info.structs_order:
                self._validate_cpp_object(cpp_object)

            # Verify Enum Names
            for cpp_object in parsed_info.enums:
                self._validate_cpp_object(cpp_object)

            # Verify Variable Names
            for cpp_object in parsed_info.variables:
                # Avoid checking member variable inside function body.
                if '{' not in cpp_object['type']:
                    self._validate_cpp_object(cpp_object)

            for namespace in parsed_info.namespaces:
                cpp_object = CppNamespace()
                cpp_object["name"] = namespace
                self._validate_cpp_object(cpp_object)

            # Verify Typdef Names
            for cpp_object in parsed_info.typedefs:
                self._validate_cpp_object(cpp_object)
        except SyntaxError as e:
            print(str(e))
            return 1
        except CppHeaderParser.CppHeaderParser.CppParseError as e:
            # CppHeaderParser can't parse this file, but we should pass it, this
            # is the CppHeaderParser's problem.
            print(str(e))
            return 0
        return 0

def main():
    a = Application()
    sys.exit(a.exec_())

if __name__ == "__main__":
    # Execute only if run as a script
    main()

[((39, 17, 39, 65), 'argparse.ArgumentParser', 'argparse.ArgumentParser', (), '', False, 'import argparse\n'), ((63, 18, 63, 73), 're.match', 're.match', ({(63, 27, 63, 63): '"""[^\\\\w]*(\\\\w+)(?:\\\\(([^\\\\)]*)\\\\)|\\\\s*).*"""', (63, 65, 63, 72): 'adefine'}, {}), "('[^\\\\w]*(\\\\w+)(?:\\\\(([^\\\\)]*)\\\\)|\\\\s*).*', adefine)", False, 'import re\n'), ((84, 18, 84, 73), 're.findall', 're.findall', ({(84, 29, 84, 58): '"""(?:^|[^\\\\w]+)operator[^\\\\w]+"""', (84, 60, 84, 72): 'amethod_name'}, {}), "('(?:^|[^\\\\w]+)operator[^\\\\w]+', amethod_name)", False, 'import re\n'), ((101, 18, 101, 80), 're.match', 're.match', ({(101, 27, 101, 58): '"""^\\\\w+\\\\s*\\\\(\\\\w*::\\\\*(\\\\w+)\\\\)\\\\(.*$"""', (101, 60, 101, 79): "an_argument['type']"}, {}), "('^\\\\w+\\\\s*\\\\(\\\\w*::\\\\*(\\\\w+)\\\\)\\\\(.*$', an_argument['type'])", False, 'import re\n'), ((104, 22, 104, 85), 're.match', 're.match', ({(104, 31, 104, 63): '"""[^\\\\(]*\\\\([^\\\\)]*\\\\W(\\\\w+)\\\\W.*\\\\).*"""', (104, 65, 104, 84): "an_argument['type']"}, {}), "('[^\\\\(]*\\\\([^\\\\)]*\\\\W(\\\\w+)\\\\W.*\\\\).*', an_argument['type'])", False, 'import re\n'), ((162, 19, 162, 34), 're.escape', 're.escape', ({(162, 29, 162, 33): 'name'}, {}), '(name)', False, 'import re\n'), ((236, 22, 236, 65), 're.findall', 're.findall', ({(236, 33, 236, 55): '"""\\\\w+\\\\W+(\\\\w+)\\\\s*=[^=]"""', (236, 57, 236, 64): 'content'}, {}), "('\\\\w+\\\\W+(\\\\w+)\\\\s*=[^=]', content)", False, 'import re\n'), ((474, 26, 474, 74), 'CppHeaderParser.CppHeader', 'CppHeaderParser.CppHeader', ({(474, 52, 474, 73): 'self.__args.file_path'}, {}), '(self.__args.file_path)', False, 'import CppHeaderParser\n'), ((86, 15, 86, 68), 're.match', 're.match', ({(86, 24, 86, 49): '"""(?:^|.*\\\\W)operator\\\\W.*"""', (86, 51, 86, 67): "amethod['debug']"}, {}), "('(?:^|.*\\\\W)operator\\\\W.*', amethod['debug'])", False, 'import re\n'), ((282, 16, 282, 39), 'traceback.print_stack', 'traceback.print_stack', ({}, {}), '()', False, 'import traceback\n'), ((292, 15, 292, 47), 're.match', 're.match', ({(292, 24, 292, 34): '"""(\\\\w+).*"""', (292, 36, 292, 46): 'class_name'}, {}), "('(\\\\w+).*', class_name)", False, 'import re\n'), ((183, 25, 183, 52), 're.search', 're.search', ({(183, 35, 183, 42): 'name_re', (183, 44, 183, 51): 'content'}, {}), '(name_re, content)', False, 'import re\n'), ((253, 26, 253, 72), 're.match', 're.match', ({(253, 35, 253, 50): '""".*?(\\\\w+)\\\\W+$"""', (253, 52, 253, 71): "cpp_object['debug']"}, {}), "('.*?(\\\\w+)\\\\W+$', cpp_object['debug'])", False, 'import re\n'), ((320, 26, 320, 97), 're.match', 're.match', ({(320, 35, 320, 78): '""".*typedef\\\\W[^\\\\(]*\\\\([^\\\\)]*\\\\W(\\\\w+)\\\\W.*\\\\).*"""', (320, 80, 320, 96): "amethod['debug']"}, {}), "('.*typedef\\\\W[^\\\\(]*\\\\([^\\\\)]*\\\\W(\\\\w+)\\\\W.*\\\\).*', amethod['debug'])", False, 'import re\n'), ((345, 48, 345, 88), 're.match', 're.match', ({(345, 57, 345, 67): '"""(\\\\w+).*"""', (345, 69, 345, 87): "aparameter['name']"}, {}), "('(\\\\w+).*', aparameter['name'])", False, 'import re\n'), ((421, 23, 421, 66), 're.match', 're.match', ({(421, 32, 421, 44): '""".*\\\\>\\\\s*{$"""', (421, 46, 421, 65): "cpp_object['debug']"}, {}), "('.*\\\\>\\\\s*{$', cpp_object['debug'])", False, 'import re\n'), ((434, 34, 434, 71), 're.search', 're.search', ({(434, 44, 434, 49): 'regex', (434, 51, 434, 70): "cpp_object['debug']"}, {}), "(regex, cpp_object['debug'])", False, 'import re\n')]