import ast


class ModelverseType(object):
    def __init__(self):
        pass

    def isNumber(self):
        return False

    def isNotNumber(self):
        return not self.isNumber()

    def isInteger(self):
        return False

    def isFloat(self):
        return False

    def isBoolean(self):
        return False

    def __str__(self):
        return self.__class__.__name__


class Type(ModelverseType):
    pass


class Integer(ModelverseType):
    def isNumber(self):
        return True

    def isInteger(self):
        return True


class Float(ModelverseType):
    def isNumber(self):
        return True

    def isFloat(self):
        return True


class Boolean(ModelverseType):
    def isNumber(self):
        return True

    def isBoolean(self):
        return True


class Action(ModelverseType):
    pass


class Element(ModelverseType):
    pass


class String(ModelverseType):
    pass


class Void(ModelverseType):
    pass


def generalize_arithmetic(l_type, r_type):
    if l_type.isFloat() or r_type.isFloat():
        return Float()
    if l_type.isInteger() or r_type.isInteger():
        return Integer()
    return Boolean()


def promote_arithmetic(l_type):
    if l_type.isBoolean():
        return Integer()
    elif l_type.isNumber():
        return l_type  # Warning: no copy or new instance is created here
    else:
        raise RuntimeError(
            "You cannot promote {} to a numeric type".format(str(l_type)))


def allow_assign(target, value):
    if target is None or type(target) == Void:
        return False
    if value is None or type(value) == Void:
        return False
    if type(target) == Element:
        return True
    if type(value) == Element:
        #print("Warning: implicit type upcast from Element to " + str(target))
        return True
    return type(target) == type(value)


def string_to_type(t):
    if t == "Integer":
        return Integer()
    elif t == "Boolean":
        return Boolean()
    elif t == "Float":
        return Float()
    elif t == "Action":
        return Action()
    elif t == "String":
        return String()
    elif t == "Type":
        return Type()
    elif t == "Element":
        return Element()
    elif t == "Void":
        return Void()
    else:
        raise Exception("Unknown type used: " + str(t))


def get_type(string):
    if string in ["if", "while", "assign", "call", "break", "continue", "return","resolve","access", "constant", "input", "output", "declare", "global"]:
        return Action()
    elif string in ["Integer", "Float", "String", "Boolean", "Action", "Type"]:
        return Type()
    elif string == "Void":
        return Void()
    try:
        value = ast.literal_eval(string)
        t = type(value)
        if t == bool:
            return Boolean()
        elif t == int:
            return Integer()
        elif t == float:
            return Float()
        elif t == str:
            return String()
    except:
        raise Exception("Value with unknown type: " + str(string))
