# Copyright 2022 The IREE Authors
#
# Licensed under the Apache License v2.0 with LLVM Exceptions.
# See https://llvm.org/LICENSE.txt for license information.
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

import numpy as np
import os
import iree.compiler as compiler
import iree.runtime as rt

TOKEN_TABLE = [
    b"hi",
    b"thanks",
    b"bye",
    b"for",
    b"all",
    b"so",
    b"the",
    b"fish",
    b"the",
    b"end",
    b"long",
    b"and",
    b".",
    b"now",
    b"there",
]


def create_tokenizer_module():
    """Creates a module which defines some custom methods for decoding."""

    class Detokenizer:
        def __init__(self, iface):
            # Any class state here is maintained per-context.
            self.start_of_text = True
            self.start_of_sentence = True

        def reset(self):
            self.start_of_text = True
            self.start_of_sentence = True

        def accumtokens(self, ids_tensor_ref, token_list_ref):
            # TODO: This little dance to turn BufferView refs into real arrays... is not good.
            ids_bv = ids_tensor_ref.deref(rt.HalBufferView)
            ids_array = ids_bv.map().asarray(
                ids_bv.shape, rt.HalElementType.map_to_dtype(ids_bv.element_type)
            )
            token_list = token_list_ref.deref(rt.VmVariantList)
            for index in range(ids_array.shape[0]):
                token_id = ids_array[index]
                token = TOKEN_TABLE[token_id]

                # And this dance to make a buffer... is also not good.
                # A real implementation would just map the constant memory, etc.
                buffer = rt.VmBuffer(len(token))
                buffer_view = memoryview(buffer)
                buffer_view[:] = token
                token_list.push_ref(buffer)
            return ids_array.shape[0]

        def jointokens(self, token_list_ref):
            # The world's dumbest detokenizer. Ideally, the state tracking
            # would be in a module private type that got retained and passed
            # back through.
            token_list = token_list_ref.deref(rt.VmVariantList)
            text = bytearray()
            for i in range(len(token_list)):
                item = bytes(token_list.get_as_object(i, rt.VmBuffer))
                if item == b".":
                    text.extend(b".")
                    self.start_of_sentence = True
                else:
                    if not self.start_of_text:
                        text.extend(b" ")
                    else:
                        self.start_of_text = False
                    if self.start_of_sentence:
                        text.extend(item[0:1].decode("utf-8").upper().encode("utf-8"))
                        text.extend(item[1:])
                        self.start_of_sentence = False
                    else:
                        text.extend(item)

            # TODO: This dance to make a buffer is still bad.
            results = rt.VmBuffer(len(text))
            memoryview(results)[:] = text
            return results.ref

    iface = rt.PyModuleInterface("detokenizer", Detokenizer)
    iface.export("accumtokens", "0rr_i", Detokenizer.accumtokens)
    iface.export("jointokens", "0r_r", Detokenizer.jointokens)
    iface.export("reset", "0v_v", Detokenizer.reset)
    return iface.create()


def compile():
    return compiler.tools.compile_file(
        os.path.join(os.path.dirname(__file__), "main.mlir"), target_backends=["vmvx"]
    )


def main():
    print("Compiling...")
    vmfb_contents = compile()
    print("Decoding secret message...")
    config = rt.Config("local-sync")
    main_module = rt.VmModule.from_flatbuffer(config.vm_instance, vmfb_contents)
    modules = config.default_vm_modules + (
        create_tokenizer_module(),
        main_module,
    )
    context = rt.SystemContext(vm_modules=modules, config=config)

    # First message.
    count = context.modules.main.add_tokens(
        np.asarray([5, 10, 11, 1, 3, 4, 5, 7, 12], dtype=np.int32)
    )
    print(f"ADDED {count} tokens")

    # Second message.
    count = context.modules.main.add_tokens(np.asarray([2, 13], dtype=np.int32))
    print(f"ADDED {count} tokens")

    text = bytes(context.modules.main.get_results().deref(rt.VmBuffer))
    print(f"RESULTS: {text}")

    assert text == b"So long and thanks for all so fish. Bye now"

    # Reset and decode some more.
    context.modules.main.reset()
    count = context.modules.main.add_tokens(np.asarray([0, 14, 12], dtype=np.int32))
    print(f"ADDED {count} tokens")
    text = bytes(context.modules.main.get_results().deref(rt.VmBuffer))
    print(f"RESULTS: {text}")
    assert text == b"Hi there."


if __name__ == "__main__":
    main()
