# Copyright (C) 2014-2017 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.

from __future__ import print_function
import os, sys, re
from gen_common import MakoTemplateWriter, ArgumentParser
from argparse import FileType

'''
'''
def gen_llvm_type(type, name, is_pointer, is_pointer_pointer, is_array, is_array_array, array_count, array_count1, is_llvm_struct, is_llvm_enum, is_llvm_pfn, output_file):

    llvm_type = ''

    if is_llvm_struct:
        if is_pointer or is_pointer_pointer:
            llvm_type = 'Type::getInt32Ty(ctx)'
        else:
            llvm_type = 'ArrayType::get(Type::getInt8Ty(ctx), sizeof(%s))' % type
    elif is_llvm_enum:
        llvm_type = 'Type::getInt32Ty(ctx)'
    elif is_llvm_pfn:
        llvm_type = 'PointerType::get(Type::getInt8Ty(ctx), 0)'
    else:
        if type == 'BYTE' or type == 'char' or type == 'uint8_t' or type == 'int8_t' or type == 'bool':
            llvm_type = 'Type::getInt8Ty(ctx)'
        elif type == 'UINT64' or type == 'INT64' or type == 'uint64_t' or type == 'int64_t':
            llvm_type = 'Type::getInt64Ty(ctx)'
        elif type == 'UINT16' or type == 'int16_t' or type == 'uint16_t':
            llvm_type = 'Type::getInt16Ty(ctx)'
        elif type == 'UINT' or type == 'INT' or type == 'int' or type == 'BOOL' or type == 'uint32_t' or type == 'int32_t':
            llvm_type = 'Type::getInt32Ty(ctx)'
        elif type == 'float' or type == 'FLOAT':
            llvm_type = 'Type::getFloatTy(ctx)'
        elif type == 'double' or type == 'DOUBLE':
            llvm_type = 'Type::getDoubleTy(ctx)'
        elif type == 'void' or type == 'VOID':
            llvm_type = 'Type::getInt32Ty(ctx)'
        elif type == 'HANDLE':
            llvm_type = 'PointerType::get(Type::getInt32Ty(ctx), 0)'
        elif type == 'simdscalar':
            llvm_type = 'VectorType::get(Type::getFloatTy(ctx), pJitMgr->mVWidth)'
        elif type == 'simdscalari':
            llvm_type = 'VectorType::get(Type::getInt32Ty(ctx), pJitMgr->mVWidth)'
        elif type == '__m128i':
            llvm_type = 'VectorType::get(Type::getInt32Ty(ctx), 4)'
        elif type == 'SIMD256::Float':
            llvm_type = 'VectorType::get(Type::getFloatTy(ctx), 8)'
        elif type == 'SIMD256::Integer':
            llvm_type = 'VectorType::get(Type::getInt32Ty(ctx), 8)'
        elif type == 'SIMD512::Float':
            llvm_type = 'VectorType::get(Type::getFloatTy(ctx), 16)'
        elif type == 'SIMD512::Integer':
            llvm_type = 'VectorType::get(Type::getInt32Ty(ctx), 16)'
        elif type == 'simdvector':
            llvm_type = 'ArrayType::get(VectorType::get(Type::getFloatTy(ctx), 8), 4)'
        elif type == 'simd16vector':
            llvm_type = 'ArrayType::get(VectorType::get(Type::getFloatTy(ctx), 16), 4)'
        elif type == 'SIMD256::Vec4':
            llvm_type = 'ArrayType::get(VectorType::get(Type::getFloatTy(ctx), 8), 4)'
        elif type == 'SIMD512::Vec4':
            llvm_type = 'ArrayType::get(VectorType::get(Type::getFloatTy(ctx), 16), 4)'
        else:
            llvm_type = 'Gen_%s(pJitMgr)' % type

    if is_pointer:
        llvm_type = 'PointerType::get(%s, 0)' % llvm_type

    if is_pointer_pointer:
        llvm_type = 'PointerType::get(%s, 0)' % llvm_type

    if is_array_array:
        llvm_type = 'ArrayType::get(ArrayType::get(%s, %s), %s)' % (llvm_type, array_count1, array_count)
    elif is_array:
        llvm_type = 'ArrayType::get(%s, %s)' % (llvm_type, array_count)

    return {
        'name'  : name,
        'type'  : llvm_type,
    }

'''
'''
def gen_llvm_types(input_file, output_file):

    lines = input_file.readlines()

    types = []

    for idx in range(len(lines)):
        line = lines[idx].rstrip()

        if 'gen_llvm_types FINI' in line:
            break

        match = re.match(r'(\s*)struct(\s*)(\w+)', line)
        if match:
            llvm_args = []

             # Detect start of structure
            is_fwd_decl = re.search(r';', line)

            if not is_fwd_decl:

                # Extract the command name
                struct_name = match.group(3).strip()

                type_entry = {
                    'name'      : struct_name,
                    'members'   : [],
                }

                end_of_struct = False

                while not end_of_struct and idx < len(lines)-1:
                    idx += 1
                    line = lines[idx].rstrip()

                    is_llvm_typedef = re.search(r'@llvm_typedef', line)
                    if is_llvm_typedef is not None:
                        is_llvm_typedef = True
                    else:
                        is_llvm_typedef = False

                    ###########################################
                    # Is field a llvm struct? Tells script to treat type as array of bytes that is size of structure.
                    is_llvm_struct = re.search(r'@llvm_struct', line)

                    if is_llvm_struct is not None:
                        is_llvm_struct = True
                    else:
                        is_llvm_struct = False

                    ###########################################
                    # Is field the start of a function? Tells script to ignore it
                    is_llvm_func_start = re.search(r'@llvm_func_start', line)

                    if is_llvm_func_start is not None:
                        while not end_of_struct and idx < len(lines)-1:
                            idx += 1
                            line = lines[idx].rstrip()
                            is_llvm_func_end = re.search(r'@llvm_func_end', line)
                            if is_llvm_func_end is not None:
                                break;
                        continue

                    ###########################################
                    # Is field a function? Tells script to ignore it
                    is_llvm_func = re.search(r'@llvm_func', line)

                    if is_llvm_func is not None:
                        continue

                    ###########################################
                    # Is field a llvm enum? Tells script to treat type as an enum and replaced with uint32 type.
                    is_llvm_enum = re.search(r'@llvm_enum', line)

                    if is_llvm_enum is not None:
                        is_llvm_enum = True
                    else:
                        is_llvm_enum = False

                    ###########################################
                    # Is field a llvm function pointer? Tells script to treat type as an enum and replaced with uint32 type.
                    is_llvm_pfn = re.search(r'@llvm_pfn', line)

                    if is_llvm_pfn is not None:
                        is_llvm_pfn = True
                    else:
                        is_llvm_pfn = False

                    ###########################################
                    # Is field const?
                    is_const = re.search(r'\s+const\s+', line)

                    if is_const is not None:
                        is_const = True
                    else:
                        is_const = False

                    ###########################################
                    # Is field a pointer?
                    is_pointer_pointer = re.search('\*\*', line)

                    if is_pointer_pointer is not None:
                        is_pointer_pointer = True
                    else:
                        is_pointer_pointer = False

                    ###########################################
                    # Is field a pointer?
                    is_pointer = re.search('\*', line)

                    if is_pointer is not None:
                        is_pointer = True
                    else:
                        is_pointer = False

                    ###########################################
                    # Is field an array of arrays?
                    # TODO: Can add this to a list.
                    is_array_array = re.search('\[(\w*)\]\[(\w*)\]', line)
                    array_count = '0'
                    array_count1 = '0'

                    if is_array_array is not None:
                        array_count = is_array_array.group(1)
                        array_count1 = is_array_array.group(2)
                        is_array_array = True
                    else:
                        is_array_array = False

                    ###########################################
                    # Is field an array?
                    is_array = re.search('\[(\w*)\]', line)

                    if is_array is not None:
                        array_count = is_array.group(1)
                        is_array = True
                    else:
                        is_array = False

                    is_scoped = re.search('::', line)

                    if is_scoped is not None:
                        is_scoped = True
                    else:
                        is_scoped = False

                    type = None
                    name = None
                    if is_const and is_pointer:

                        if is_scoped:
                            field_match = re.match(r'(\s*)(\w+\<*\w*\>*)(\s+)(\w+::)(\w+)(\s*\**\s*)(\w+)', line)

                            type = '%s%s' % (field_match.group(4), field_match.group(5))
                            name = field_match.group(7)
                        else:
                            field_match = re.match(r'(\s*)(\w+\<*\w*\>*)(\s+)(\w+)(\s*\**\s*)(\w+)', line)

                            type = field_match.group(4)
                            name = field_match.group(6)

                    elif is_pointer:
                        field_match = re.match(r'(\s*)(\s+)(\w+\<*\w*\>*)(\s*\**\s*)(\w+)', line)

                        if field_match:
                            type = field_match.group(3)
                            name = field_match.group(5)
                    elif is_const:
                        field_match = re.match(r'(\s*)(\w+\<*\w*\>*)(\s+)(\w+)(\s*)(\w+)', line)

                        if field_match:
                            type = field_match.group(4)
                            name = field_match.group(6)
                    else:
                        if is_scoped:
                            field_match = re.match(r'\s*(\w+\<*\w*\>*)\s*::\s*(\w+\<*\w*\>*)\s+(\w+)', line)

                            if field_match:
                                type = field_match.group(1) + '::' + field_match.group(2)
                                name = field_match.group(3)
                        else:
                            field_match = re.match(r'(\s*)(\w+\<*\w*\>*)(\s+)(\w+)', line)

                            if field_match:
                                type = field_match.group(2)
                                name = field_match.group(4)

                    if is_llvm_typedef is False:
                        if type is not None:
                            type_entry['members'].append(
                                gen_llvm_type(
                                    type, name, is_pointer, is_pointer_pointer, is_array, is_array_array,
                                    array_count, array_count1, is_llvm_struct, is_llvm_enum, is_llvm_pfn, output_file))

                    # Detect end of structure
                    end_of_struct = re.match(r'(\s*)};', line)

                    if end_of_struct:
                        types.append(type_entry)

    cur_dir = os.path.dirname(os.path.abspath(__file__))
    template = os.path.join(cur_dir, 'templates', 'gen_llvm.hpp')

    MakoTemplateWriter.to_file(
        template,
        output_file,
        cmdline=sys.argv,
        filename=os.path.basename(output_file),
        types=types)

'''
    Function which is invoked when this script is started from a command line.
    Will present and consume a set of arguments which will tell this script how
    to behave
'''
def main():

    # Parse args...
    parser = ArgumentParser()
    parser.add_argument('--input', '-i', type=FileType('r'),
            help='Path to input file containing structs', required=True)
    parser.add_argument('--output', '-o', action='store',
            help='Path to output file', required=True)
    args = parser.parse_args()

    gen_llvm_types(args.input, args.output)

if __name__ == '__main__':
    main()
# END OF FILE
