/**
 * FILE: virtio-demo.c
 * --------------------
 * VirtIO demo device backend.
 */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu/iov.h"
#include "qemu/module.h"
#include "qemu/timer.h"
#include "hw/virtio/virtio.h"
#include "hw/qdev-properties.h"
#include "hw/virtio/virtio-demo.h"
#include "sysemu/rng.h"
#include "sysemu/runstate.h"
#include "qom/object_interfaces.h"
#include "trace.h"

// "host features": Every possible feature
// result of negotiation is stored in guest and backend features
static Property virtio_demo_properties[] = {
    DEFINE_PROP_BIT64("in-order-buf", VirtIODemo, host_features,
                      VIRTIO_F_IN_ORDER, true),
    DEFINE_PROP_BIT64("test-feature", VirtIODemo, host_features,
                      VIRTIO_F_DEMO_TEST, true),
    DEFINE_PROP_END_OF_LIST(),
};

// Multiply via a device.
struct mult_val12_t
{
    uint8_t val_1;
    uint8_t val_2;
};

typedef struct multiplicands
{
    uint8_t val_1;
    uint8_t val_2;
} multiplicands_t;

typedef uint16_t prod_t;

typedef struct demoreq
{
    multiplicands_t m;
    prod_t res;
} demoreq_t;

static void
demo_read_outbuf(VirtIODevice *vdev, VirtQueue *vq)
{
    // VirtIODemo *s = VIRTIO_DEMO(vdev);
    // print a message to console
    printf("[%s] %s: Got a virtqueue message from guest\n", __FILE__, __func__);

    VirtQueueElement *vqe;
    // VirtQueueElement *vqe_rx;/

    while (!virtio_queue_ready(vq))
    {
        printf("vq not ready\n");
        return;
    }

    if (!runstate_check(RUN_STATE_RUNNING))
    {
        printf("not synced");
        return;
    }
    
    vqe = virtqueue_pop(vq, sizeof(VirtQueueElement)); // 2 multiplicands
    if (!vqe)
    {
        printf("vqe not present\n");
        return;
    }

    // printf("vqe index: %d\n", vqe_tx->index);
    demoreq_t req;
    iov_to_buf(vqe->out_sg, vqe->out_num, 0, &req, vqe->out_sg->iov_len); // iov len must be >= sizeof(m)
    
    // multiply!
    uint16_t res = req.m.val_1 * req.m.val_2;
    printf("mult res: %d\n", res);
    
    iov_from_buf(vqe->in_sg, vqe->in_num, 0, &res, vqe->in_sg->iov_len);

    virtqueue_push(vq, vqe, vqe->in_sg->iov_len);
    virtio_notify(vdev, vq);
}

// device realize can register custom actions based on
// the host features
static void virtio_demo_device_realize(DeviceState *dev, Error **errp)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
    VirtIODemo *s = VIRTIO_DEMO(dev);

    virtio_init(vdev, VIRTIO_ID_DEMO, 0); // config_size = 0, not sure of significance

    // setup the virtqueues.
    // register callback for a virtqueue ready event
    // max 1 pending req/response in the queue.
    // 64: # of entries in each vring (desc, avail, used)
    // before wraparound
    // must be a power of two (virtio spec)
    // find_vqs() does mapping in this init order.
    s->vq = virtio_add_queue(vdev, 64, demo_read_outbuf); // for driver outbufs

    // callback is generally used when driver wants to retrieve
    // data stored on device. The sequence is:
    // 1. driver adds inbufs to queue and kicks.
    // 2. the handler in device retrieves this data and copies to the inbuf
    // 3. handler kicks inbuf queue, which is read by the driver.
    // s->rx_vq = virtio_add_queue(vdev, 64, demo_fill_inbuf); // for driver inbufs
}

static void virtio_demo_device_unrealize(DeviceState *dev)
{
    VirtIODevice *vdev = VIRTIO_DEVICE(dev); // parent class

    VirtIODemo *s = VIRTIO_DEMO(dev); // our device
    (void)s;

    // seem like standard funcs.
    virtio_del_queue(vdev, 0);
    virtio_cleanup(vdev);
}

// initilialize attributes of the virtio-demo-device.
// (non-virtqueue ops)
static void virtio_demo_instance_init(Object *obj)
{
    VirtIODemo *s = VIRTIO_DEMO(obj);
    (void)s;

    // lowkey think instance init can be empty
}

static uint64_t virtio_demo_get_features(VirtIODevice *vdev, uint64_t features,
                                          Error **errp)
{
    printf("requesting features\n");
    VirtIODemo *dev = VIRTIO_DEMO(vdev);
    features |= dev->host_features;
    // virtio_add_feature(&features, VIRTIO_F_IN_ORDER);

    return features;
}

// GOAL: Setup the methods of the parent (VirtioDeviceClass)
// of this virtio-demo-device class. All these methods will
// be inherited by the child.
static void virtio_demo_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass);

    // can add additional class props as well.
    // props must be part of your device struct.
    device_class_set_props(dc, virtio_demo_properties);

    set_bit(DEVICE_CATEGORY_MISC, dc->categories);

    vdc->realize = virtio_demo_device_realize;
    vdc->unrealize = virtio_demo_device_unrealize;
    vdc->get_features = virtio_demo_get_features; // needed, else assert fail (virtio-bus.c)
}

static const TypeInfo virtio_demo_info = {
    .name = TYPE_VIRTIO_DEMO,

    // reg as virtio-mmio (virtio-bus) device.
    // The device backend object will only inherit attributes
    // and methods of the DEVICE_CLASS. (no pci stuff)
    .parent = TYPE_VIRTIO_DEVICE,
    .instance_size = sizeof(VirtIODemo),
    .instance_init = virtio_demo_instance_init,
    .class_init = virtio_demo_class_init,
};

// QEMU device boilerplate to register type
static void virtio_register_types(void)
{
    type_register_static(&virtio_demo_info);
}

type_init(virtio_register_types)