/**
 * 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.
 */

import {
    set, deref, is_structure, make_check, MAX_ARITY, check_nil,
    exec_build, exec_unify, get_ctx
} from "../../nova/core.mjs";
import {
    int32
} from "../misc/hacklib.mjs";
import {
    GROUP_SIZE, device, pipeline
} from "./boot.mjs";

/******************************************************************/
/* Cmd Constructors                                               */
/******************************************************************/

/**
 * test_gpu_data_new(A, B): internal only
 * The predicate succeeds in B with a pi-WAM buffer for the list A.
 */
function test_gpu_data_new(args) {
    let alpha = exec_build(args[0]);
    let res = gpu_list_ints(alpha);
    return exec_unify(args[1], res);
}

function gpu_list_ints(obj) {
    let peek = obj;
    let i = 0;
    while (is_structure(peek) &&
    "." === peek.functor &&
    peek.args.length === 2 &&
    i < MAX_ARITY) {
        i++;
        peek = deref(peek.args[1]);
    }
    check_nil(peek);
    let buf = new ArrayBuffer(i * 4);
    let args = new Int32Array(buf);
    peek = obj;
    i = 0;
    while (is_structure(peek) &&
    "." === peek.functor &&
    peek.args.length === 2) {
        let val = deref(peek.args[0]);
        args[i++] = int32(val);
        peek = deref(peek.args[1]);
    }
    return args;
}

/**
 * test_gpu_comp_new(C, O, S, M): internal only
 * The predicate succeeds in M with a new GPU backed π-WAM for
 * the code buffer C, the offsets O and the state buffer S.
 */
function test_gpu_comp_new(args) {
    let data = new Array(3);
    for (let i = 0; i < 3; i++)
        data[i] = exec_build(args[i]);
    let entries = new Array(3);
    for (let i = 0; i < 3; i++) {
        let buf = device.createBuffer({
            size: data[i].byteLength,
            usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
        });
        entries[i] = {
            binding: i,
            resource: {buffer: buf}
        };
    }
    let bind = device.createBindGroup({
        layout: pipeline.getBindGroupLayout(0),
        entries: entries
    });
    for (let i = 0; i < 3; i++)
        device.queue.writeBuffer(entries[i].resource.buffer, 0, data[i]);
    return exec_unify(args[3], bind);
}

/**
 * test_gpu_comp_work(M, W): internal only
 * The predicate succeeds in W with the current
 * group size of the GPU backed π-WAM M.
 */
function test_gpu_comp_work(args) {
    let alpha = exec_build(args[0]);
    let res = GROUP_SIZE;
    return exec_unify(args[1], res);
}

/******************************************************************/
/* Cmd Interface                                                  */
/******************************************************************/

/**
 * test_gpu_comp_submit(M, K): internal only
 * The predicate succeeds. As a side effect it
 * starts the GPU backed π-WAM M with K groups.
 */
function test_gpu_comp_submit(args) {
    let alpha = exec_build(args[0]);
    let beta = exec_build(args[1]);
    const commandEncoder = device.createCommandEncoder();
    const passEncoder = commandEncoder.beginComputePass();
    passEncoder.setPipeline(pipeline);
    passEncoder.setBindGroup(0, alpha);
    passEncoder.dispatchWorkgroups(int32(beta));
    passEncoder.end();
    device.queue.submit([commandEncoder.finish()]);
    return true;
}

/**
 * test_gpu_comp_done(W, P): internal only
 * The predicate succeeds in P with a new promise
 * that waits for the GPU backed π-WAM W to finish.
 */
function test_gpu_comp_done(args) {
    let alpha = exec_build(args[0]);
    let buf = get_ctx();
    return exec_unify(args[1], join_promise(buf, alpha));
}

async function join_promise(buf, alpha) {
    await device.queue.onSubmittedWorkDone();
}

/******************************************************************/
/* Cmd Init                                                       */
/******************************************************************/

export function main() {
    set("gpu_data_new", 2, make_check(test_gpu_data_new));
    set("gpu_comp_new", 4, make_check(test_gpu_comp_new));
    set("gpu_comp_work", 2, make_check(test_gpu_comp_work));
    set("gpu_comp_submit", 2, make_check(test_gpu_comp_submit));
    set("gpu_comp_done", 2, make_check(test_gpu_comp_done));
}
