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

# Python source
import os
import sys
import re
from gen_common import *

def parse_event_fields(lines, idx, event_dict):
    """
        Parses lines from a proto file that contain an event definition and stores it in event_dict
    """
    fields = []
    end_of_event = False

    # record all fields in event definition.
    # note: we don't check if there's a leading brace.
    while not end_of_event and idx < len(lines):
        line = lines[idx].rstrip()
        idx += 1

        # ex 1: uint32_t    numSampleCLZExecuted; // number of sample_cl_z instructions executed
        # ex 2: char        reason[256]; // size of reason
        match = re.match(r'^(\s*)([\w\*]+)(\s+)([\w]+)(\[\d+\])*;\s*(\/\/.*)*$', line)
        # group 1 -
        # group 2 type
        # group 3 -
        # group 4 name
        # group 5 [array size]
        # group 6 //comment

        if match:
            field = {
                "type": match.group(2),
                "name": match.group(4),
                "size": int(match.group(5)[1:-1]) if match.group(5) else 1,
                "desc": match.group(6)[2:].strip() if match.group(6) else "",
            }
            fields.append(field)

        end_of_event = re.match(r'(\s*)};', line)

    event_dict['fields'] = fields
    event_dict['num_fields'] = len(fields)

    return idx

def parse_enums(lines, idx, event_dict):
    """
        Parses lines from a proto file that contain an enum definition and stores it in event_dict
    """
    enum_names = []
    end_of_enum = False

    # record all enum values in enumeration
    # note: we don't check if there's a leading brace.
    while not end_of_enum and idx < len(lines):
        line = lines[idx].rstrip()
        idx += 1

        preprocessor = re.search(r'#if|#endif', line)

        if not preprocessor:
            enum = re.match(r'(\s*)(\w+)(\s*)', line)

            if enum:
                enum_names.append(line)

            end_of_enum = re.match(r'(\s*)};', line)

    event_dict['names'] = enum_names
    return idx

def parse_protos(files, verbose=False):
    """
        Parses a proto file and returns a dictionary of event definitions
    """

    # Protos structure:
    #
    # {
    #   "events": {
    #     "defs": {     // dict of event definitions where keys are 'group_name::event_name"
    #       ...,
    #       "ApiStat::DrawInfoEvent": {
    #         "id": 3,
    #         "group": "ApiStat",
    #         "name": "DrawInfoEvent",  // name of event without 'group_name::' prefix
    #         "desc": "",
    #         "fields": [
    #           {
    #             "type": "uint32_t",
    #             "name": "drawId",
    #             "size": 1,
    #             "desc": "",
    #           },
    #           ...
    #         ]
    #       },
    #       ...
    #     },
    #     "groups": {   // dict of groups with lists of event keys
    #       "ApiStat": [
    #         "ApiStat::DispatchEvent",
    #         "ApiStat::DrawInfoEvent",
    #         ...
    #       ],
    #       "Framework": [
    #         "Framework::ThreadStartApiEvent",
    #         "Framework::ThreadStartWorkerEvent",
    #         ...
    #       ],
    #       ...
    #     },
    #     "map": {  // map of event ids to match archrast output to event key
    #       "1": "Framework::ThreadStartApiEvent",
    #       "2": "Framework::ThreadStartWorkerEvent",
    #       "3": "ApiStat::DrawInfoEvent",
    #       ...
    #     }
    #   },
    #   "enums": { ... }    // enums follow similar defs, map (groups?) structure
    # }

    protos = {
        'events': {
            'defs': {},             # event dictionary containing events with their fields
            'map': {},              # dictionary to map event ids to event names
            'groups': {}            # event keys stored by groups
        },
        'enums': {
            'defs': {},
            'map': {}
        }
    }

    event_id = 0
    enum_id = 0

    if type(files) is not list:
        files = [files]

    for filename in files:
        if verbose:
            print("Parsing proto file: %s" % os.path.normpath(filename))

        with open(filename, 'r') as f:
            lines = f.readlines()
            in_brief = False
            brief = []
            idx = 0
            while idx < len(lines):
                line = lines[idx].strip()
                idx += 1

                # If currently processing a brief, keep processing or change state
                if in_brief:
                    match = re.match(r'^\s*\/\/\/\s*(.*)$', line)                   # i.e. "/// more event desc..."
                    if match:
                        brief.append(match.group(1).strip())
                        continue
                    else:
                        in_brief = False

                # Match event/enum brief
                match = re.match(r'^\s*\/\/\/\s*@(brief|breif)\s*(.*)$', line)       # i.e. "///@brief My event desc..."
                if match:
                    in_brief = True
                    brief.append(match.group(2).strip())
                    continue

                # Match event definition
                match = re.match(r'event(\s*)(((\w*)::){0,1}(\w+))', line)          # i.e. "event SWTag::CounterEvent"
                if match:
                    event_id += 1

                    # Parse event attributes
                    event_key = match.group(2)                                      # i.e. SWTag::CounterEvent
                    event_group = match.group(4) if match.group(4) else ""          # i.e. SWTag
                    event_name = match.group(5)                                     # i.e. CounterEvent

                    # Define event attributes
                    event = {
                        'id': event_id,
                        'group': event_group,
                        'name': event_name,
                        'desc': ' '.join(brief)
                    }
                    # Add period at end of event desc if necessary
                    if event["desc"] and event["desc"][-1] != '.':
                        event["desc"] += '.'

                    # Reset brief
                    brief = []

                    # Now add event fields
                    idx = parse_event_fields(lines, idx, event)

                    # Register event and mapping
                    protos['events']['defs'][event_key] = event
                    protos['events']['map'][event_id] = event_key

                    continue

                # Match enum definition
                match = re.match(r'enum(\s*)(\w+)', line)
                if match:
                    enum_id += 1

                    # Parse enum attributes
                    enum_name = match.group(2)

                    # Define enum attr
                    enum = {
                        'name': enum_name,
                        'desc': ' '.join(brief)
                    }
                    # Add period at end of event desc if necessary
                    if enum["desc"] and enum["desc"][-1] != '.':
                        enum["desc"] += '.'

                    # Reset brief
                    brief = []

                    # Now add enum fields
                    idx = parse_enums(lines, idx, enum)

                    # Register enum and mapping
                    protos['enums']['defs'][enum_name] = enum
                    protos['enums']['map'][enum_id] = enum_name

                    continue

    # Sort and group events
    event_groups = protos['events']['groups']
    for key in sorted(protos['events']['defs']):
        group = protos['events']['defs'][key]['group']
        if group not in event_groups:
            event_groups[group] = []
        event_groups[group].append(key)

    return protos


def main():

    # Parse args...
    parser = ArgumentParser()
    parser.add_argument("--proto", "-p", dest="protos", nargs='+', help="Path to all proto file(s) to process. Accepts one or more paths (i.e. events.proto and events_private.proto)", required=True)
    parser.add_argument("--output-dir", help="Output dir (defaults to ./codegen). Will create folder if it does not exist.", required=False, default="codegen")
    parser.add_argument("--verbose", "-v", help="Verbose", action="store_true")
    args = parser.parse_args()

    if not os.path.exists(args.output_dir):
        MakeDir(args.output_dir)

    for f in args.protos:
        if not os.path.exists(f):
            print('Error: Could not find proto file %s' % f, file=sys.stderr)
            return 1

    # Parse each proto file and add to protos container
    protos = parse_protos(args.protos, args.verbose)

    files = [
        ["gen_ar_event.hpp", ""],
        ["gen_ar_event.cpp", ""],
        ["gen_ar_eventhandler.hpp", "gen_ar_event.hpp"],
        ["gen_ar_eventhandlerfile.hpp", "gen_ar_eventhandler.hpp"]
    ]

    rval = 0

    try:
        # Delete existing files
        for f in files:
            filename = f[0]
            output_fullpath = os.path.join(args.output_dir, filename)
            if os.path.exists(output_fullpath):
                if args.verbose:
                    print("Deleting existing file: %s" % output_fullpath)
                os.remove(output_fullpath)

        # Generate files from templates
        print("Generating c++ from proto files...")
        for f in files:
            filename = f[0]
            event_header = f[1]
            curdir = os.path.dirname(os.path.abspath(__file__))
            template_file = os.path.join(curdir, 'templates', filename)
            output_fullpath = os.path.join(args.output_dir, filename)

            if args.verbose:
                print("Generating: %s" % output_fullpath)
            MakoTemplateWriter.to_file(template_file, output_fullpath,
                    cmdline=sys.argv,
                    filename=filename,
                    protos=protos,
                    event_header=event_header)

    except Exception as e:
        print(e)
        rval = 1

    return rval

if __name__ == '__main__':
    sys.exit(main())
