#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H

#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/mca.h>
#include <86box/sound.h>
#include <86box/timer.h>
#include <86box/snd_opl.h>
#include <86box/plat_unused.h>

#ifdef ENABLE_ADLIB_LOG
int adlib_do_log = ENABLE_ADLIB_LOG;

static void
adlib_log(const char *fmt, ...)
{
    va_list ap;

    if (adlib_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define adlib_log(fmt, ...)
#endif

typedef struct adlib_t {
    fm_drv_t opl;

    uint8_t pos_regs[8];
} adlib_t;

static void
adlib_get_buffer(int32_t *buffer, int len, void *priv)
{
    adlib_t *adlib = (adlib_t *) priv;

    const int32_t *opl_buf = adlib->opl.update(adlib->opl.priv);

    for (int c = 0; c < len * 2; c++)
        buffer[c] += opl_buf[c];

    adlib->opl.reset_buffer(adlib->opl.priv);
}

uint8_t
adlib_mca_read(int port, void *priv)
{
    const adlib_t *adlib = (adlib_t *) priv;

    adlib_log("adlib_mca_read: port=%04x\n", port);

    return adlib->pos_regs[port & 7];
}

void
adlib_mca_write(int port, uint8_t val, void *priv)
{
    adlib_t *adlib = (adlib_t *) priv;

    if (port < 0x102)
        return;

    adlib_log("adlib_mca_write: port=%04x val=%02x\n", port, val);

    switch (port) {
        case 0x102:
            if ((adlib->pos_regs[2] & 1) && !(val & 1))
                io_removehandler(0x0388, 0x0002,
                                 adlib->opl.read, NULL, NULL,
                                 adlib->opl.write, NULL, NULL,
                                 adlib->opl.priv);
            if (!(adlib->pos_regs[2] & 1) && (val & 1))
                io_sethandler(0x0388, 0x0002,
                              adlib->opl.read, NULL, NULL,
                              adlib->opl.write, NULL, NULL,
                              adlib->opl.priv);
            break;

        default:
            break;
    }
    adlib->pos_regs[port & 7] = val;
}

uint8_t
adlib_mca_feedb(void *priv)
{
    const adlib_t *adlib = (adlib_t *) priv;

    return (adlib->pos_regs[2] & 1);
}

void *
adlib_init(UNUSED(const device_t *info))
{
    adlib_t *adlib = malloc(sizeof(adlib_t));
    memset(adlib, 0, sizeof(adlib_t));

    adlib_log("adlib_init\n");
    fm_driver_get(FM_YM3812, &adlib->opl);
    io_sethandler(0x0388, 0x0002,
                  adlib->opl.read, NULL, NULL,
                  adlib->opl.write, NULL, NULL,
                  adlib->opl.priv);
    music_add_handler(adlib_get_buffer, adlib);
    return adlib;
}

void *
adlib_mca_init(const device_t *info)
{
    adlib_t *adlib = adlib_init(info);

    io_removehandler(0x0388, 0x0002,
                     adlib->opl.read, NULL, NULL,
                     adlib->opl.write, NULL, NULL,
                     adlib->opl.priv);
    mca_add(adlib_mca_read,
            adlib_mca_write,
            adlib_mca_feedb,
            NULL,
            adlib);
    adlib->pos_regs[0] = 0xd7;
    adlib->pos_regs[1] = 0x70;

    return adlib;
}

void
adlib_close(void *priv)
{
    adlib_t *adlib = (adlib_t *) priv;
    free(adlib);
}

const device_t adlib_device = {
    .name          = "AdLib",
    .internal_name = "adlib",
    .flags         = DEVICE_ISA,
    .local         = 0,
    .init          = adlib_init,
    .close         = adlib_close,
    .reset         = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};

const device_t adlib_mca_device = {
    .name          = "AdLib (MCA)",
    .internal_name = "adlib_mca",
    .flags         = DEVICE_MCA,
    .local         = 0,
    .init          = adlib_mca_init,
    .close         = adlib_close,
    .reset         = NULL,
    { .available = NULL },
    .speed_changed = NULL,
    .force_redraw  = NULL,
    .config        = NULL
};
