import time as python_time
import json
import sys

class PrimitiveFinished(Exception):
    """Exception to indicate the result value of a primitive, as a return cannot be used."""
    def __init__(self, value):
        Exception.__init__(self)
        self.result = value

class InterpretedFunctionFinished(Exception):
    """Exception to indicate the result value of an interpreted function, as a return
       cannot be used."""
    def __init__(self, value):
        Exception.__init__(self)
        self.result = value

class SleepKernel(Exception):
    """Exception to indicate the kernel to sleep for some time."""
    def __init__(self, timeout, interruptable):
        Exception.__init__(self)
        self.timeout = timeout
        self.interruptable = interruptable

# Functions annotated with __exception_return use the JIT's calling convention instead of
# the kernel's: returns are handled by throwing a PrimitiveFinished exception; the caller's
# returnvalue is not modified.
#
# ### Rationale for __exception_return
#
# __exception_return is a useful mechanism because it allows us to have a __call_function
# implementation that has O(1) state read overhead. A previous implementation of
# __call_function checked if the caller's frame had been popped whenever
# ModelverseKernel.execute_yield threw a StopIteration exception. However, that incurs O(n) overhead
# _per call,_ where n is the number of StopIteration exceptions that are thrown during the call.
# O(n) is pretty bad, but this actually becomes O(n * m) when m calls to __call_function are
# nested. And that's just not acceptable.
# __exception_return requires kernel support, but I think the complexity gains are well worth it;
# I reckon JIT-to-interpreter switches aren't going to get a whole lot cheaper than this.
EXCEPTION_RETURN_KEY = "__exception_return"
"""A dictionary key for functions which request that the kernel throw a InterpretedFunctionFinished
   exception with the return value instead of injecting the return value in the caller's frame."""

def integer_subtraction(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value - b_value])]
    raise PrimitiveFinished(result)

def integer_addition(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value + b_value])]
    raise PrimitiveFinished(result)

def integer_multiplication(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value * b_value])]
    raise PrimitiveFinished(result)

def integer_division(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [int(a_value) // b_value])]
    raise PrimitiveFinished(result)

def integer_lt(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value < b_value])]
    raise PrimitiveFinished(result)

def bool_and(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value and b_value])]
    raise PrimitiveFinished(result)

def bool_or(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value or b_value])]
    raise PrimitiveFinished(result)

def bool_not(a, **remainder):
    a_value, =          yield [("RV", [a])]
    result, = yield [("CNV", [not a_value])]
    raise PrimitiveFinished(result)

def float_subtraction(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value - b_value])]
    raise PrimitiveFinished(result)

def float_addition(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value + b_value])]
    raise PrimitiveFinished(result)

def float_multiplication(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value * b_value])]
    raise PrimitiveFinished(result)

def float_division(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [float(a_value) / float(b_value)])]
    raise PrimitiveFinished(result)

def float_lt(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value < b_value])]
    raise PrimitiveFinished(result)

def string_join(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [str(a_value) + str(b_value)])]
    raise PrimitiveFinished(result)

def string_split(a, b, **remainder):
    # TODO make non-primitive, though compiled
    a_value, b_value = yield [("RV", [a]), ("RV", [b])]
    result = a_value.split(b_value)
    elems = yield [("CN", [])] + [("CNV", [v]) for v in result]
    new_val = elems[0]
    yield [("CD", [new_val, i, v]) for i, v in enumerate(elems[1:])]
    raise PrimitiveFinished(new_val)

def string_get(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value[b_value]])]
    raise PrimitiveFinished(result)

def string_len(a, **remainder):
    a_value, = yield [("RV", [a])]
    result, = yield [("CNV", [len(a_value)])]
    raise PrimitiveFinished(result)

def value_eq(a, b, **remainder):
    a_value, b_value =  yield [("RV", [a]), ("RV", [b])]
    result, = yield [("CNV", [a_value == b_value])]
    raise PrimitiveFinished(result)

def element_eq(a, b, **remainder):
    result, = yield [("CNV", [a == b])]
    raise PrimitiveFinished(result)

def cast_string(a, **remainder):
    a_value, = yield [("RV", [a])]
    if isinstance(a_value, dict):
        result, = yield [("CNV", [str(a_value["value"])])]
    else:
        result, = yield [("CNV", [str(a_value)])]
    raise PrimitiveFinished(result)

def cast_float(a, **remainder):
    a_value, = yield [("RV", [a])]
    result, = yield [("CNV", [float(a_value)])]
    raise PrimitiveFinished(result)

def cast_boolean(a, **remainder):
    a_value, = yield [("RV", [a])]
    result, = yield [("CNV", [bool(a_value)])]
    raise PrimitiveFinished(result)

def cast_integer(a, **remainder):
    a_value, = yield [("RV", [a])]
    result, = yield [("CNV", [int(a_value)])]
    raise PrimitiveFinished(result)

def cast_value(a, **remainder):
    a_value, = yield [("RV", [a])]
    if isinstance(a_value, dict):
        # Action or type
        value = a_value["value"]
    else:
        value = json.dumps(a_value)
    result, = yield [("CNV", [value])]
    raise PrimitiveFinished(result)

def cast_id(a, **remainder):
    result, = yield [("CNV", ["%s" % (a)])]
    raise PrimitiveFinished(result)

def list_insert(a, b, c, **remainder):
    # TODO make non-primitive, though compiled
    a_outgoing, c_value = yield [("RO", [a]), ("RV", [c])]
    links = yield [("RD", [a, i]) for i in range(c_value, len(a_outgoing))] + \
                  [("RDE", [a, i]) for i in range(c_value, len(a_outgoing))]

    if sys.version_info[0] < 3:
        values = links[:len(links)/2]
        edges = links[len(links)/2:]
    else:
        values = links[:len(links) // 2]
        edges = links[len(links) // 2:]

    yield [("CD", [a, c_value, b])] + \
          [("CD", [a, c_value + 1 + index, value]) for index, value in enumerate(values)] + \
          [("DE", [i]) for i in edges]
    raise PrimitiveFinished(a)

def list_delete(a, b, **remainder):
    # TODO make non-primitive, though compiled
    a_outgoing, b_value = yield [("RO", [a]), ("RV", [b])]
    links = yield [("RD", [a, i]) for i in range(b_value, len(a_outgoing))] + \
                  [("RDE", [a, i]) for i in range(b_value, len(a_outgoing))]

    if sys.version_info[0] < 3:
        values = links[:len(links) / 2]
        edges = links[len(links) / 2:]
    else:
        values = links[:len(links) // 2]
        edges = links[len(links) // 2:]

    yield [("CD", [a, b_value + index, value]) for index, value in enumerate(values[1:])] + \
          [("DE", [i]) for i in edges]
    raise PrimitiveFinished(a)

def dict_add_fast(a, b, c, **remainder):
    # TODO deprecate, as dict_add is now also efficient
    v, = yield [("RV", [b])]
    yield [("CD", [a, v, c])]
    raise PrimitiveFinished(a)

def dict_delete(a, b, **remainder):
    b_value, = yield [("RV", [b])]
    edge, = yield [("RDE", [a, b_value])]
    if edge is None:
        print("Failed dict_delete for value '%s'!" % b_value)
        keys, = yield [("RDK", [a])]
        keys = yield [("RV", [i]) for i in keys]
        print("Keys: " + str(keys))
        raise Exception()
    yield [("DE", [edge])]
    raise PrimitiveFinished(a)

def dict_delete_node(a, b, **remainder):
    edge, = yield [("RDNE", [a, b])]
    if edge is None:
        print("Failed dict_delete_node!")
    yield [("DE", [edge])]
    raise PrimitiveFinished(a)

def dict_read(a, b, **remainder):
    b_value, = yield [("RV", [b])]
    result, = yield [("RD", [a, b_value])]
    raise PrimitiveFinished(result)

def dict_read_edge(a, b, **remainder):
    b_value, = yield [("RV", [b])]
    result, = yield [("RDE", [a, b_value])]
    raise PrimitiveFinished(result)

def dict_read_node(a, b, **remainder):
    result, = yield [("RDN", [a, b])]
    raise PrimitiveFinished(result)

def dict_in(a, b, **remainder):
    b_value, = yield [("RV", [b])]
    value, = yield [("RD", [a, b_value])]
    is_in = value is not None
    result, = yield [("CNV", [is_in])]
    raise PrimitiveFinished(result)

def dict_in_node(a, b, **remainder):
    value, = yield [("RDN", [a, b])]
    result, = yield [("CNV", [value is not None])]
    raise PrimitiveFinished(result)

def dict_keys(a, **remainder):
    keys, result = yield [("RDK", [a]), ("CN", [])]
    edges = yield [("CE", [result, result]) for _ in keys]
    _ = yield [("CE", [edge, key]) for edge, key in zip(edges, keys)]
    raise PrimitiveFinished(result)

def is_physical_int(a, **remainder):
    t, = yield [("RV", [a])]
    try:
        result, = yield [("CNV", [isinstance(t, int) or isinstance(t, long)])]
    except NameError:
        result, = yield [("CNV", [isinstance(t, int)])]

    raise PrimitiveFinished(result)

def is_physical_string(a, **remainder):
    t, = yield [("RV", [a])]
    try:
        result, = yield [("CNV", [isinstance(t, str) or isinstance(t, unicode)])]
    except NameError:
        result, = yield [("CNV", [isinstance(t, str)])]

    raise PrimitiveFinished(result)

def is_physical_float(a, **remainder):
    t, = yield [("RV", [a])]
    result, = yield [("CNV", [isinstance(t, float)])]
    raise PrimitiveFinished(result)

def is_physical_boolean(a, **remainder):
    t, = yield [("RV", [a])]
    result, = yield [("CNV", [isinstance(t, bool)])]
    raise PrimitiveFinished(result)

def is_physical_action(a, **remainder):
    t, = yield [("RV", [a])]
    result, = yield [("CNV", [isinstance(t, dict) and t["value"] in ["if", "while", "assign", "call", "break", "continue", "return", "resolve", "access", "constant", "global", "declare"]])]
    raise PrimitiveFinished(result)

def is_physical_none(a, **remainder):
    t, = yield [("RV", [a])]
    result, = yield [("CNV", [isinstance(t, dict) and t["value"] == "none"])]
    raise PrimitiveFinished(result)

def create_node(**remainder):
    result, = yield [("CN", [])]
    raise PrimitiveFinished(result)

def create_edge(a, b, **remainder):
    result, = yield [("CE", [a, b])]
    raise PrimitiveFinished(result)

def create_value(a, **remainder):
    a_value, = yield [("RV", [a])]
    result, = yield [("CNV", [a_value])]
    raise PrimitiveFinished(result)

def read_nr_out(a, **remainder):
    outgoing, = yield [("RO", [a])]
    result, = yield [("CNV", [len(outgoing)])]
    raise PrimitiveFinished(result)

def read_out(a, b, root, **remainder):
    outgoing, b_value = yield [("RO", [a]), ("RV", [b])]
    raise PrimitiveFinished(sorted(outgoing)[b_value] if len(outgoing) > b_value else root)

def read_nr_in(a, **remainder):
    incoming, = yield [("RI", [a])]
    result, = yield [("CNV", [len(incoming)])]
    raise PrimitiveFinished(result)

def read_in(a, b, root, **remainder):
    incoming, b_value = yield [("RI", [a]), ("RV", [b])]
    raise PrimitiveFinished(sorted(incoming)[b_value] if len(incoming) > b_value else root)

def read_edge_src(a, **remainder):
    result, = yield [("RE", [a])]
    raise PrimitiveFinished(result[0])

def read_edge_dst(a, **remainder):
    result, = yield [("RE", [a])]
    raise PrimitiveFinished(result[1])

def delete_element(a, **remainder):
    edge, = yield [("RE", [a])]
    if edge[0] is None:
        # Not an edge:
        yield [("DN", [a])]
        result, = yield [("CNV", [False])]
        raise PrimitiveFinished(result)
    else:
        yield [("DE", [a])]
        result, = yield [("CNV", [True])]
        raise PrimitiveFinished(result)

def read_root(root, **remainder):
    raise PrimitiveFinished(root)

def is_edge(a, **remainder):
    edge, = yield [("RE", [a])]
    result, = yield [("CNV", [edge[0] is not None])]
    raise PrimitiveFinished(result)

def log(a, **remainder):
    a_value, = yield [("RV", [a])]
    print("== LOG == " + str(a_value))
    raise PrimitiveFinished(a)

def read_taskroot(task_root, **remainder):
    raise PrimitiveFinished(task_root)

def time(**remainder):
    a, = yield [("CNV", [python_time.time()])]
    raise PrimitiveFinished(a)

def hash(a, **remainder):
    a_value, = yield [("RV", [a])]
    import hashlib
    try:
        b_value = hashlib.sha512(a_value).hexdigest()
    except TypeError:
        b_value = hashlib.sha512(a_value.encode()).hexdigest()
    b, = yield [("CNV", [b_value])]
    raise PrimitiveFinished(b)

def __sleep(a, b, **remainder):
    timeout, interruptable = yield [("RV", [a]), ("RV", [b])]
    yield [("SLEEP", [timeout, interruptable])]
    raise PrimitiveFinished(a)

def is_error(a, **remainder):
    if a is None:
        result, = yield [("CNV", [True])]
    else:
        result, = yield [("CNV", [False])]
    raise PrimitiveFinished(result)
