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 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 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"

    # id is reserved on objective-C
    if input_string == "id":
        input_string = "identifier"

    if input_string == "error":
        input_string = "errorReason"

    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 = "/\  +-*^|%$=~@()[].,"
    if input_string == "error":
        input_string = "errorReason"
    return inflection.camelize(replace_chars(input_string, chars_to_replace), True)


def enum_name_syntax(schema_name, prop_name):
    schema_name_modded = class_name_syntax(schema_name)
    prop_name_modded = class_name_syntax(prop_name)
    if prop_name_modded == "AdapterState":
        return "IoT" + schema_name_modded + prop_name_modded
    else:
        return "IoT" + prop_name_modded


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 obj-C type
    :param json_type: the json type
    :return: obj-C 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_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 convert_to_objc_type(prop_data, prop, context="variable"):
    """
    convert the json type to objective-C type
    :param json_type: the json type
    :return: obj-c 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, Obj-c 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"]:
        name = prop_data['name']
        if name == "error":
            name = "errorReason"
        objc_type = 'IoT' + class_name_syntax(name) + 'Attr *'
    else:
        objc_type = json_to_objc_map[json_type]
    logger.debug("convert_to_objc_type(%s) Prop: %s Json Type: %s, Obj-C 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: obj-c 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, Obj-c Type: %s",
                 context, prop, json_type, objc_type)
    if json_type in ["array"]:
        objc_type = "NSArray"
    elif json_type in ["object"]:
        name = prop_data['name']
        if name == "error":
            name = "errorReason"
        objc_type = 'IoT' + class_name_syntax(name) + 'Attr'
    else:
        objc_type = json_to_objc_map[json_type]
    logger.debug("convert_to_objc_alloc_type(%s) Prop: %s Json Type: %s, Obj-C Type: %s",
                 context, prop, json_type, objc_type)
    return objc_type


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"]:
        name = prop_data['name']
        if name == "error":
            name = "errorReason"
        value = 'nil'
        #value = '[[IoT' + class_name_syntax(name) + 'Attr alloc] initWithStateSyncQueue:syncQueue]'
    else:
       value = "0"

    return value
    

def pack_objc_type(prop_data, prop, context="variable"):
    """
    Converts obj-c 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_objc_type(prop_data, prop, context="variable"):
    """
    Converts cpp types to obj-c for 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_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
