import logging
import inflection
from .helper import *
logger = logging.getLogger()


def replace_chars(a, chars):
    """
    function that replaces the occurrences of chars in the string
    :param a: input string
    :param chars: chars to be replaced with "" (nothing)
    :return:
    """
    string = a
    for char in chars:
        copy_string = string.replace(char, '_')
        string = copy_string
    return string


#
#  jinga custom functions: filter
#
def trim_path_name(input_string):
    return strip_right(strip_left(input_string, '/'), "ResURI")


def all_caps(input_string):
    return input_string.upper()


def trim_schema_name(input_string):
    """
    Removes the resource type prefix
    :param input_string:
    :return:
    """
    prefix = "qti.allplay.r."
    return input_string[input_string.startswith(prefix) and len(prefix):]


def java_string_constant(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = inflection.camelize("" + replace_chars(input_string, chars_to_replace), True)
    return "RESOURCE_ATTR_" + text.upper()


def java_prop_name(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = replace_chars(input_string, chars_to_replace)
    return "Prop_" + text


def string_constant(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = inflection.camelize("" + replace_chars(input_string, chars_to_replace), True)
    return "k" + text


def class_data_member(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = inflection.underscore("" + replace_chars(input_string, chars_to_replace))
    return text + "_"

def class_data_member_with_prefix_suffix(input_string, prefix, suffix):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = inflection.underscore("" + replace_chars(input_string, chars_to_replace))
    prefix = inflection.underscore("" + replace_chars(prefix, chars_to_replace))
    suffix = inflection.underscore("" + replace_chars(suffix, chars_to_replace))
    return prefix + "_" + text + "_" + suffix + "_"


def local_variable(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    return inflection.underscore("" + replace_chars(input_string, chars_to_replace))

def java_instance_variable_syntax(input_string):
    """
        replace chars so that it can be used as an variable
        :param input_string: string to be adjusted
        :return: adjusted string
        """
    chars_to_replace = "/\  +-*^|%$=~@()[].,"

    return "m" + inflection.camelize("" + replace_chars(input_string, chars_to_replace), True)


def variable_syntax(input_string, camelize=False, caps_first_letter=False):
    """
    replace chars so that it can be used as an variable
    :param input_string: string to be adjusted
    :return: adjusted string
    """
    chars_to_replace = "/\  +-*^|%$=~@()[].,"

    # Objective-C doesn't allow variables starting with new.
    if input_string.startswith("new"):
        input_string = input_string[3:]

    # description is reserved on objective-C
    if input_string == "description":
        input_string = "itemDescription"

    if camelize:
        return inflection.camelize(""+replace_chars(input_string, chars_to_replace), caps_first_letter)
    else:
        return inflection.underscore("" + replace_chars(input_string, chars_to_replace))


def class_name_syntax(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    return inflection.camelize(replace_chars(input_string, chars_to_replace), True)


def convert_to_cpp_array_type(json_data):
    """
    convert the json type to c++ type
    :param json_type: the json type
    :return: c++ type.
    """
    try:
        t = "std::vector<" + convert_to_cpp_type(json_data["items"]["type"]) + ">"
    except:
        t = "void *"
        pass

    logger.debug("convert_to_cpp_array_type %s", t)
    return t


def convert_to_objc_array_type(json_data):
    """
    convert the json type to java type
    :param json_type: the json type
    :return: java type.
    """
    try:
        t = "NSArray <" + convert_to_cpp_type(json_data["items"]["type"]) + "> *"
    except:
        t = "void *"
        pass

    logger.debug("convert_to_objc_array_type %s", t)
    return t


def get_json_type(prop_data):
    json_type = ""

    if "type" in prop_data:
        json_type = prop_data["type"]
    elif "enum" in prop_data:
        json_type = "string"
    return json_type


def props_have_object_array(props):
    for prop, prop_data in props.items():
        if is_prop_an_object_array(prop_data):
            return True
    return False

def props_have_writable_object_array(props):
    for prop, prop_data in props.items():
        if is_prop_an_object_array(prop_data):
            if not 'readOnly' in prop_data or not prop_data['readOnly']:
                return True
    return False

def get_object_name_from_obj_array(prop_data):
    """
    Fetches the name of the object from the array type.
    :param prop_data:
    :return:
    """
    return 'IoT' + class_name_syntax(prop_data['items']['name']) + 'Attr'


def get_java_object_name_from_obj_array(prop_data):
    """
    Fetches the name of the object from the array type.
    :param prop_data:
    :return:
    """
    return class_name_syntax(prop_data['items']['name']) + 'Attr'


def get_objc_object_name_from_obj_array(prop_data):
    """
    Fetches the name of the object from the array type.
    :param prop_data:
    :return:
    """
    return 'IoT' + class_name_syntax(prop_data['items']['name']) + 'Attr *'


def get_objc_alloc_object_name_from_obj_array(prop_data):
    """
    Fetches the name of the object from the array type.
    :param prop_data:
    :return:
    """
    return 'IoT' + class_name_syntax(prop_data['items']['name']) + 'Attr'


def convert_to_cpp_type(prop_data, prop, context="variable"):
    """
    convert the json type to c++ type
    :param json_type: the json type
    :return: c++ type.
    """
    json_to_cpp_map = {"boolean": "bool",
                       "number": "double",
                       "integer": "int",
                       "string": "std::string",
                       "null": "void *",
                       "": "void *"}
    cpp_type = ""
    json_type = get_json_type(prop_data)

    logger.debug("convert_to_cpp_type(%s) Prop: %s Json Type: %s, CPP Type: %s",
                 context, prop, json_type, cpp_type)
    if json_type in ["array"]:
        cpp_type = "std::vector<"
        if prop_data['items']['type'] == 'object':
            cpp_type += 'IoT' + class_name_syntax(prop_data['items']['name']) + 'Attr'
        else:
            cpp_type += json_to_cpp_map[prop_data["items"]["type"]]
        cpp_type += ">"

        if context is "return":
            cpp_type += "&"
    elif json_type in ["object"]:
        cpp_type = 'IoT' + class_name_syntax(prop_data['name']) + 'Attr'
        if context is "return":
            cpp_type += "&"
    else:
        cpp_type = json_to_cpp_map[json_type]
    logger.debug("convert_to_cpp_type(%s) Prop: %s Json Type: %s, CPP Type: %s",
                 context, prop, json_type, cpp_type)
    return cpp_type


def get_java_class_name(prop_data):
    return class_name_syntax(prop_data['items']['name']) + "Attr"


def get_java_attribute_class_name(prop_data):
    return class_name_syntax(prop_data['name']) + 'Attr'


def convert_to_java_type(prop_data, prop, context="variable"):
    """
        convert the json type to Java type
        :param json_type: the json type
        :return: Java data type.
        """
    json_to_java_map = {"boolean": "boolean",
                        "number": "double",
                        "integer": "int",
                        "string": "String",
                        "null": "null",
                        "": "Object"}
    java_type = ""
    json_type = get_json_type(prop_data)

    logger.debug("convert_to_java_type(%s) Prop: %s Json Type: %s, Java Type: %s",
                 context, prop, json_type, java_type)
    if json_type in ["array"]:
        java_type = "List<"
        if prop_data['items']['type'] == 'object':
            java_type += class_name_syntax(prop_data['items']['name']) + "Attr"
        elif prop_data['items']['type'] == 'string':
            java_type += 'String'
        elif prop_data['items']['type'] == 'integer':
            java_type += 'Integer'
        elif prop_data['items']['type'] == 'number':
            java_type += 'Double'
        else:
            java_type += json_to_objc_map[prop_data["items"]["type"]]
        java_type += "> "

    elif json_type in ["object"]:
        java_type = class_name_syntax(prop_data['name']) + 'Attr'
    else:
        if 'enum' in prop_data:
            java_type = class_name_syntax(prop)
        else:
            java_type = json_to_java_map[json_type]
    logger.debug("convert_to_java_type(%s) Prop: %s Json Type: %s, Java Type: %s",
                 context, prop, json_type, java_type)
    return java_type

def convert_to_objc_type(prop_data, prop, context="variable"):
    """
    convert the json type to objective-C type
    :param json_type: the json type
    :return: java type.
    """
    json_to_objc_map = {"boolean": "BOOL",
                       "number": "double",
                       "integer": "int",
                       "string": "NSString *",
                       "null": "void *",
                       "": "void *"}
    objc_type = ""
    json_type = get_json_type(prop_data)

    logger.debug("convert_to_objc_type(%s) Prop: %s Json Type: %s, java type: %s",
                 context, prop, json_type, objc_type)
    if json_type in ["array"]:
        objc_type = "NSArray <"
        if prop_data['items']['type'] == 'object':
            objc_type += 'IoT' + class_name_syntax(prop_data['items']['name']) + 'Attr *'
        elif prop_data['items']['type'] == 'number' or prop_data['items']['type'] == 'integer':
            objc_type += 'NSNumber *'
        else:
            objc_type += json_to_objc_map[prop_data["items"]["type"]]
        objc_type += "> *"

    elif json_type in ["object"]:
        objc_type = 'IoT' + class_name_syntax(prop_data['name']) + 'Attr *'
    else:
        objc_type = json_to_objc_map[json_type]
    logger.debug("convert_to_objc_type(%s) Prop: %s Json Type: %s, java type: %s",
                 context, prop, json_type, objc_type)
    return objc_type


def convert_to_objc_alloc_type(prop_data, prop, context="variable"):
    """
    convert the json type to objective-C type used for alloc-init.
    :param json_type: the json type
    :return: java type.
    """
    json_to_objc_map = {"boolean": "BOOL",
                       "number": "double",
                       "integer": "int",
                       "string": "NSString",
                       "null": "void *",
                       "": "void *"}
    objc_type = ""
    json_type = get_json_type(prop_data)

    logger.debug("convert_to_objc_alloc_type(%s) Prop: %s Json Type: %s, java type: %s",
                 context, prop, json_type, objc_type)
    if json_type in ["array"]:
        objc_type = "NSArray"
    elif json_type in ["object"]:
        objc_type = 'IoT' + class_name_syntax(prop_data['name']) + 'Attr'
    else:
        objc_type = json_to_objc_map[json_type]
    logger.debug("convert_to_objc_alloc_type(%s) Prop: %s Json Type: %s, java type: %s",
                 context, prop, json_type, objc_type)
    return objc_type

def default_value_java_type(prop_data, prop, context="variable"):
    """
        Used to assign a first value to a Java type
        """
    value = ""
    json_type = get_json_type(prop_data)

    if json_type in ["boolean"]:
        value = "false"
    elif json_type in ["string"]:
        value = "\"\""
    elif json_type in ["array"]:
        value = "new ArrayList<>()"
    elif json_type in ["object"]:
        value = 'new ' + class_name_syntax(prop_data['name']) + 'Attr()'
    elif json_type in ["integer"]:
        value = "0"
    elif json_type in ["number"]:
        value = "0f"
    else:
        value = "null"

    return value

def default_value_objc_type(prop_data, prop, context="variable"):
    """
    Used to assign a first value to a obj type
    """
    value = ""
    json_type = get_json_type(prop_data)

    if json_type in ["boolean"]:
        value = "NO"
    elif json_type in ["string"]:
        value = "@\"\""
    elif json_type in ["array"]:
        value = "@[]"
    elif json_type in ["object"]:
        value = '[[IoT' + class_name_syntax(prop_data['name']) + 'Attr alloc] initWithStateSyncQueue:syncQueue]'
    else:
       value = "0"

    return value

def java_enum_class(name):
    return "public enum " + class_name_syntax(name)

def java_enum_constant(input_string):
    chars_to_replace = "/\  +-*^|%$=~@()[].,"
    text = inflection.camelize("" + replace_chars(input_string, chars_to_replace), True)
    return "k" + text

def from_java_string_func(prop):
    return variable_syntax(prop, True, False) + 'FromString'

def to_java_string_func(prop):
    return variable_syntax(prop, True, False) + 'ToString'


def pack_java_type(prop_data, prop, context="varible"):
    """
    Converts Java types
    """
    packed = ""
    json_type = get_json_type(prop_data)

    prop_name = variable_syntax(prop, True)
    if json_type in ["array"] and prop_data['items']['type'] == "string":
        packed = "ResourceAttrUtils.streamFromStringArray(" + java_instance_variable_syntax(prop_name) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "integer":
        packed = "ResourceAttrUtils.streamFromIntArray(" + java_instance_variable_syntax(prop_name) + ")"
    else:
        packed = java_instance_variable_syntax(prop)

    return packed


def pack_objc_type(prop_data, prop, context="variable"):
    """
    Converts java types to cpp for pack/unpack methods
    """
    packed = ""
    json_type = get_json_type(prop_data)

    prop_name = variable_syntax(prop, True)
    if json_type in ["boolean"]:
        packed = "(bool)self." + prop_name
    elif json_type in ["string"]:
        packed = "std::string([self." + prop_name + " UTF8String])"
    elif json_type in ["array"] and prop_data['items']['type'] == "string":
        packed = "vectorFromStringArray(self." + prop_name + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "integer":
        packed = "vectorFromIntArray(self." + prop_name + ")"
    else:
        packed = "self." + prop_name

    return packed


def unpack_java_type(prop_data, prop, context="variable"):
    """
    Converts cpp types to java for pack/unpack methods
    """

    unpacked = ""
    json_type = get_json_type(prop_data)
    if json_type in ["boolean"]:
        unpacked = "ResourceAttrUtils.boolValueFromRepresentation(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    elif json_type in ["string"]:
        unpacked = "ResourceAttrUtils.stringValueFromRepresentation(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    elif json_type in ["number"]:
        unpacked = "ResourceAttrUtils.doubleValueFromRepresentation(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    elif json_type in ["integer"]:
        unpacked = "ResourceAttrUtils.intValueFromRepresentation(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "string":
        unpacked = "ResourceAttrUtils.stringArrayFromStream(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "integer":
        unpacked = "ResourceAttrUtils.intArrayFromStream(rep, ResourceAttributes." + java_prop_name(prop) + ")"
    else:
        unpacked = "ResourceAttributes." + java_prop_name(prop)

    return unpacked


def diff_java_type(prop_data, prop, context="variable"):
    """
    Converts cpp types to java for pack/unpack methods
    """

    check = ""
    json_type = get_json_type(prop_data)
    if json_type in ["boolean"]:
        check = java_instance_variable_syntax(prop)+" != ResourceAttrUtils.boolValueFromRepresentation(rep, ResourceAttributes." \
                + java_prop_name(prop) + ")"
    elif json_type in ["string"]:
        check = "!" + java_instance_variable_syntax(prop)+".equalsIgnoreCase(ResourceAttrUtils.stringValueFromRepresentation(rep, ResourceAttributes." \
                + java_prop_name(prop) + "))"
    elif json_type in ["number"]:
        check = java_instance_variable_syntax(prop)+" != ResourceAttrUtils.doubleValueFromRepresentation(rep, ResourceAttributes." \
                + java_prop_name(prop) + ")"
    elif json_type in ["integer"]:
        check = java_instance_variable_syntax(prop)+" != ResourceAttrUtils.intValueFromRepresentation(rep, ResourceAttributes." \
                + java_prop_name(prop) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "string":
        check = "!"+java_instance_variable_syntax(prop) +".equals(ResourceAttrUtils.stringArrayFromStream(rep, ResourceAttributes." \
                + java_prop_name(prop) + "))"
    elif json_type in ["array"] and prop_data['items']['type'] == "integer":
        check = "!"+java_instance_variable_syntax(prop) +".equals(ResourceAttrUtils.intArrayFromStream(rep, ResourceAttributes." \
                + java_prop_name(prop) + "))"
    else:
        check = java_instance_variable_syntax(prop)+" != ResourceAttributes." + java_prop_name(prop)

    return check


def unpack_objc_type(prop_data, prop, context="variable"):
    """
    Converts cpp types to javafor pack/unpack methods
    """

    unpacked = ""
    json_type = get_json_type(prop_data)
    if json_type in ["boolean"]:
        unpacked = "boolValueFromRepresentation(rep, " + string_constant(prop) + ")"
    elif json_type in ["string"]:
        unpacked = "stringValueFromRepresentation(rep, " + string_constant(prop) + ")"
    elif json_type in ["number"]:
        unpacked = "doubleValueFromRepresentation(rep, " + string_constant(prop) + ")"
    elif json_type in ["integer"]:
        unpacked = "intValueFromRepresentation(rep, " + string_constant(prop) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "string":
        unpacked = "stringArrayFromVector(rep, " + string_constant(prop) + ")"
    elif json_type in ["array"] and prop_data['items']['type'] == "integer":
        unpacked = "intArrayFromVector(rep, " + string_constant(prop) + ")"
    else:
        unpacked = "rep[" + string_constant(prop) + "]"

    return unpacked


def objc_property_qualifier(prop_data, prop, context="variable"):
    """
    Generates appropriate memory management qualifiers for properties
    """

    qualifier = ""
    json_type = get_json_type(prop_data)
    if json_type in ["string"]:
        qualifier = "copy"
    elif json_type in ["array"]:
        qualifier = "copy"
    elif json_type in ["object"]:
        qualifier = "strong"
    else:
        qualifier = "assign"

    return qualifier


def convert_to_cpp_return_type(prop_data, prop):
    return convert_to_cpp_type(prop_data, prop, "return")

def convert_to_java_return_type(prop_data, prop):
    return convert_to_java_type(prop_data, prop, "return")

def convert_to_objc_return_type(prop_data, prop):
    return convert_to_objc_type(prop_data, prop, "return")

def is_prop_array_type(prop_data):
    json_type = get_json_type(prop_data)
    if json_type in ["array"]:
        return True
    else:
        return False


def is_prop_string_type(prop_data):
    json_type = get_json_type(prop_data)
    if json_type in ["string"]:
        return True
    else:
        return False


def is_prop_an_object(prop_data):
    """

    :param prop_data:
    :return:
    """
    json_type = get_json_type(prop_data)

    if json_type in ["object"]:
        return True
    else:
        return False


def is_prop_an_object_array(prop_data):
    """

    :param prop_data:
    :return:
    """
    json_type = get_json_type(prop_data)

    if json_type in ["array"] and prop_data['items']['type'] == 'object':
        return True
    else:
        return False
