###
# Modern Albufeira Prolog Interpreter
#
# Warranty & Liability
# To the extent permitted by applicable law and unless explicitly
# otherwise agreed upon, XLOG Technologies AG makes no warranties
# regarding the provided information. XLOG Technologies AG assumes
# no liability that any problems might be solved with the information
# provided by XLOG Technologies AG.
#
# Rights & License
# All industrial property rights regarding the information - copyright
# and patent rights in particular - are the sole property of XLOG
# Technologies AG. If the company was not the originator of some
# excerpts, XLOG Technologies AG has at least obtained the right to
# reproduce, change and translate the information.
#
# Reproduction is restricted to the whole unaltered document. Reproduction
# of the information is only allowed for non-commercial uses. Selling,
# giving away or letting of the execution of the library is prohibited.
# The library can be distributed as part of your applications and libraries
# for execution provided this comment remains unchanged.
#
# Restrictions
# Only to be distributed with programs that add significant and primary
# functionality to the library. Not to be distributed with additional
# software intended to replace any components of the library.
#
# Trademarks
# Jekejeke is a registered trademark of XLOG Technologies AG.
##

from nova.core import (
    set, deref, is_structure, make_check, MAX_ARITY, check_nil,
    exec_build, exec_unify, int32, Compound, get_ctx)
import asyncio
import threading


MAX_TILT = 64


class PiWam:
    def __init__(self):
        self.code = None
        self.offset = None
        self.state = None


class PiWarp:
    def __init__(self):
        super().__init__()
        self.map = None
        self.mfrom = 0
        self.to = 0
        self.id = 0
        self.tilt = 0
        self.cmd = False
        self.buf = None
        self.thread = None


def run(warp):
    i = warp.mfrom
    while i < warp.to:
        warp.map.state[warp.map.offset[i] - 3] = i
        i += 1
    run_warp(warp, warp.mfrom, warp.to)
    while warp.cmd:
        warp.buf = read_ints()
        run_warp(warp, warp.mfrom, warp.to)


def run_warp(warp, mfrom, to):
    active = True
    while active:
        active = False
        i = warp.id if warp.cmd else mfrom
        while i < to:
            if run_id(warp, i):
                if warp.cmd:
                    warp.id = i
                    return
                else:
                    active = True
            i += 1


def run_id(warp, mid):
    pi = warp.map
    offset = pi.offset[mid]
    pc = pi.state[offset-2]
    accu = pi.state[offset-1]
    k = warp.tilt if warp.cmd else 0
    while k < MAX_TILT:
        if pc < len(pi.code):
            instr = pi.code[pc]
            value = run_get(warp, pi, instr, offset)
            if warp.cmd:
                pi.state[offset-2] = pc
                pi.state[offset-1] = accu
                warp.tilt = k
                return False               
            else:
                accu = run_fun(instr, accu, value)
                run_set(pi, instr, accu, offset)
                pc = pc + 1 + run_jump(instr, accu)
        else:
            pi.state[offset-2] = pc
            pi.state[offset-1] = accu
            return False
        k += 1
    pi.state[offset-2] = pc
    pi.state[offset-1] = accu
    return True


def run_get(warp, pi, instr, offset):
    act = instr >> 20
    obj = ((instr >> 10) & 0x03FF) - 512
    mode = (act >> 4) & 0x0F
    if mode == 0:  # get
        return pi.state[offset+obj]
    elif mode == 1:  # cst
        return obj
    elif mode == 2:  # set
        return 0
    elif mode == 4:  # out
        return 0
    elif mode == 5:  # in
        extra_outline(warp, pi, obj, offset)
        return 0
    elif mode == 6:  # tbl
        return pi.code[obj]
    else:
        raise ValueError("illegal mode")


def run_fun(instr, accu, value):
    act = instr >> 20
    fun = act >> 8
    if fun == 0:  # val
        return value
    elif fun == 1:  # acc
        return accu
    elif fun == 2:  # add
        return int32(value + accu)
    elif fun == 3:  # sub
        return int32(value - accu)
    elif fun == 4:  # mul
        return int32(value * accu)
    elif fun == 5:  # zdiv
        return int32(value // accu)
    elif fun == 6:  # rem
        return int32(value % accu)
    else:
        raise ValueError("illegal fun")


def run_set(pi, instr, accu, offset):
    act = instr >> 20
    obj = ((instr >> 10) & 0x03FF) - 512
    mode = (act >> 4) & 0x0F
    if mode == 0:  # get
        return
    elif mode == 1:  # cst
        return
    elif mode == 2:  # set
        pi.state[offset+obj] = accu
        return
    elif mode == 4:  # out
        write_ints(load_ints(obj, pi.state, offset))
        return
    elif mode == 5:  # in
        return
    elif mode == 6:  # tbl
        return
    else:
        raise ValueError("illegal mode")


def run_jump(instr, accu):
    act = instr >> 20
    rel = (instr & 0x03FF) - 512
    cond = act & 0x0F
    if cond == 0:  # eq
        return rel if accu == 0 else 0
    elif cond == 1:  # nq
        return rel if accu != 0 else 0
    elif cond == 2:  # ls
        return rel if accu < 0 else 0
    elif cond == 3:  # gr
        return rel if accu > 0 else 0
    elif cond == 4:  # lq
        return rel if accu <= 0 else 0
    elif cond == 5:  # gq
        return rel if accu >= 0 else 0
    elif cond == 15:  # true
        return rel
    else:
        raise ValueError("illegal cond")


#################################################################
# π-WAM Input                                                   #
#################################################################

def read_ints():
    print(end=": ")
    return list(map(lambda y: int32(int(y)), 
                filter(lambda x: x != "", input().split())))


def write_ints(res):
    i = 0
    while i < len(res):
        print(res[i], end=" ")
        i += 1
    print()


#################################################################
# π-WAM Store/Load                                              #
#################################################################

def store_ints(res, mmax, state, offset):
    i = 0
    while i < mmax:
        if i < len(res):
            state[offset + i] = res[i]
        else:
            state[offset + i] = 0
        i += 1


def load_ints(mmax, state, offset):
    res = [0] * mmax
    i = 0
    while i < mmax:
        res[i] = state[offset + i]
        i += 1
    return res


def extra_outline(warp, pi, obj, offset):
    if warp.cmd:
        store_ints(warp.buf, obj, pi.state, offset)
        warp.buf = None
        warp.cmd = False
    else:
        warp.cmd = True


#################################################################
# π-WAM Constructors                                            #
#################################################################

###
# test_ir_buf_new(A, B): internal only
# The predicate succeeds in B with a pi-WAM buffer for the list A.
##
def test_ir_buf_new(args):
    alpha = exec_build(args[0])
    res = list_ints(alpha)
    return exec_unify(args[1], res)


def list_ints(obj):
    peek = obj
    i = 0
    while (is_structure(peek) and
           peek.functor == "." and
           len(peek.args) == 2 and
           i < MAX_ARITY):
        i += 1
        peek = deref(peek.args[1])
    check_nil(peek)
    args = [NotImplemented] * i
    peek = obj
    i = 0
    while (is_structure(peek) and
           peek.functor == "." and
           len(peek.args) == 2):
        val = deref(peek.args[0])
        args[i] = int32(val)
        i += 1
        peek = deref(peek.args[1])
    return args


###
# test_ir_buf_get(A, B): internal only
# The predicate succeeds in B with a list for the pi-WAM buffer A.
##
def test_ir_buf_get(args):
    alpha = exec_build(args[0])
    res = ints_list(alpha, 0, len(alpha))
    return exec_unify(args[1], res)


def ints_list(alpha, off, count):
    res = "[]"
    i = off + count - 1
    while i >= off:
        res = Compound(".", [alpha[i], res])
        i -= 1
    return res


###
# test_ir_map_new(C, O, S, M): internal only
# The predicate succeeds in M with a CPU backed π-WAM for
# the code buffer C, the offsets O and the state buffer S,
##
def test_ir_map_new(args):
    res = PiWam()
    res.code = exec_build(args[0])
    res.offset = exec_build(args[1])
    res.state = exec_build(args[2])
    return exec_unify(args[3], res)


#################################################################
# π-WAM Interface                                               #
#################################################################

###
# test_ir_warp_new(M, W): internal only
# The predicate succeeds in W with a new
# warp for the π-WAM M.
##
def test_ir_warp_new(args):
    alpha = exec_build(args[0])
    res = PiWarp()
    res.map = alpha
    res.thread = threading.Thread(target=run, args=(res,))
    return exec_unify(args[1], res)


###
# test_ir_warp_start(W, S, L): internal only
# The predicate succeeds. As a side effect it
# starts the warp W at start S of length L.
##
def test_ir_warp_start(args):
    alpha = exec_build(args[0])
    beta = exec_build(args[1])
    gamma = exec_build(args[2])
    at = int32(beta)
    mlen = min(len(alpha.map.offset) - at, int32(gamma))
    alpha.mfrom = at
    alpha.to = at+mlen
    alpha.thread.start()
    return True


###
# test_ir_warp_join(W, P): internal only
# The predicate succeeds in P with a new promise
# that waits for the warp W to finish.
##
def test_ir_warp_join(args):
    alpha = exec_build(args[0])
    buf = get_ctx()
    return exec_unify(args[1], lambda: join_promise(buf, alpha.thread))


async def join_promise(buf, alpha):
    await asyncio.to_thread(alpha.join)


###
# test_ir_warp_free(W): internal only
# The predicate succeeds. As a side effect it frees the warp W.
##
def test_ir_warp_free(args):
    return True


#################################################################
# π-WAM Lib Init                                                #
#################################################################

def main():
    set("ir_buf_new", 2, make_check(test_ir_buf_new))
    set("ir_buf_get", 2, make_check(test_ir_buf_get))
    set("ir_map_new", 4, make_check(test_ir_map_new))
    set("ir_warp_new", 2, make_check(test_ir_warp_new))
    set("ir_warp_start", 3, make_check(test_ir_warp_start))
    set("ir_warp_join", 2, make_check(test_ir_warp_join))
    set("ir_warp_free", 1, make_check(test_ir_warp_free))
