#include <stdio.h>
#include <stdlib.h>
#include "ibm.h"

#include "device.h"
#include "io.h"
#include "sound.h"
#include "sound_cms.h"

#define MASTER_CLOCK 7159090

typedef struct cms_t
{
        int addrs[2];
        uint8_t regs[2][32];
        uint16_t latch[2][6];
        int freq[2][6];
        float count[2][6];
        int vol[2][6][2];
        int stat[2][6];
        uint16_t noise[2][2];
        uint16_t noisefreq[2][2];
        int noisecount[2][2];
        int noisetype[2][2];
        
        uint8_t latched_data;

        int16_t buffer[MAXSOUNDBUFLEN * 2];

        int pos;
} cms_t;

void cms_update(cms_t *cms)
{
        for (; cms->pos < sound_pos_global; cms->pos++)
        {
                int c, d;
                int16_t out_l = 0, out_r = 0;

                for (c = 0; c < 4; c++)
                {
                        switch (cms->noisetype[c >> 1][c & 1])
                        {
                                case 0: cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK/256; break;
                                case 1: cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK/512; break;
                                case 2: cms->noisefreq[c >> 1][c & 1] = MASTER_CLOCK/1024; break;
                                case 3: cms->noisefreq[c >> 1][c & 1] = cms->freq[c >> 1][(c & 1) * 3]; break;
                        }
                }
                for (c = 0; c < 2; c ++)
                {
                        if (cms->regs[c][0x1C] & 1)
                        {
                                for (d = 0; d < 6; d++)
                                {
                                        if (cms->regs[c][0x14] & (1 << d))
                                        {
                                                if (cms->stat[c][d]) out_l += (cms->vol[c][d][0] * 90);
                                                if (cms->stat[c][d]) out_r += (cms->vol[c][d][1] * 90);
                                                cms->count[c][d] += cms->freq[c][d];
                                                if (cms->count[c][d] >= 24000)
                                                {
                                                        cms->count[c][d] -= 24000;
                                                        cms->stat[c][d] ^= 1;
                                                }
                                        }
                                        else if (cms->regs[c][0x15] & (1 << d))
                                        {
                                                if (cms->noise[c][d / 3] & 1) out_l += (cms->vol[c][d][0] * 90);
                                                if (cms->noise[c][d / 3] & 1) out_r += (cms->vol[c][d][0] * 90);
                                        }
                                }
                                for (d = 0; d < 2; d++)
                                {
                                        cms->noisecount[c][d] += cms->noisefreq[c][d];
                                        while (cms->noisecount[c][d] >= 24000)
                                        {
                                                cms->noisecount[c][d] -= 24000;
                                                cms->noise[c][d] <<= 1;
                                                if (!(((cms->noise[c][d] & 0x4000) >> 8) ^ (cms->noise[c][d] & 0x40))) 
                                                        cms->noise[c][d] |= 1;
                                        }
                                }
                        }
                }
                cms->buffer[(cms->pos << 1)] = out_l;
                cms->buffer[(cms->pos << 1) + 1] = out_r;
        }
}

void cms_get_buffer(int32_t *buffer, int len, void *p)
{
        cms_t *cms = (cms_t *)p;
        
        int c;

        cms_update(cms);
        
        for (c = 0; c < len * 2; c++)
                buffer[c] += cms->buffer[c];

        cms->pos = 0;
}

void cms_write(uint16_t addr, uint8_t val, void *p)
{
        cms_t *cms = (cms_t *)p;
        int voice;
        int chip = (addr & 2) >> 1;
        
//        pclog("cms_write : addr %04X val %02X\n", addr, val);
        
        switch (addr & 0xf)
        {
                case 1:
                cms->addrs[0] = val & 31;
                break;
                case 3:
                cms->addrs[1] = val & 31;
                break;
                
                case 0: case 2:
                cms_update(cms);
                cms->regs[chip][cms->addrs[chip] & 31] = val;
                switch (cms->addrs[chip] & 31)
                {
                        case 0x00: case 0x01: case 0x02: /*Volume*/
                        case 0x03: case 0x04: case 0x05:
                        voice = cms->addrs[chip] & 7;
                        cms->vol[chip][voice][0] = val & 0xf;
                        cms->vol[chip][voice][1] = val >> 4;
                        break;
                        case 0x08: case 0x09: case 0x0A: /*Frequency*/
                        case 0x0B: case 0x0C: case 0x0D:
                        voice = cms->addrs[chip] & 7;
                        cms->latch[chip][voice] = (cms->latch[chip][voice] & 0x700) | val;
                        cms->freq[chip][voice] = (MASTER_CLOCK/512 << (cms->latch[chip][voice] >> 8)) / (511 - (cms->latch[chip][voice] & 255));
                        break;
                        case 0x10: case 0x11: case 0x12: /*Octave*/
                        voice = (cms->addrs[chip] & 3) << 1;
                        cms->latch[chip][voice] = (cms->latch[chip][voice] & 0xFF) | ((val & 7) << 8);
                        cms->latch[chip][voice + 1] = (cms->latch[chip][voice + 1] & 0xFF) | ((val & 0x70) << 4);
                        cms->freq[chip][voice] = (MASTER_CLOCK/512 << (cms->latch[chip][voice] >> 8)) / (511 - (cms->latch[chip][voice] & 255));
                        cms->freq[chip][voice + 1] = (MASTER_CLOCK/512 << (cms->latch[chip][voice + 1] >> 8)) / (511 - (cms->latch[chip][voice + 1] & 255));
                        break;
                        case 0x16: /*Noise*/
                        cms->noisetype[chip][0] = val & 3;
                        cms->noisetype[chip][1] = (val >> 4) & 3;
                        break;
                }
                break;
                case 0x6: case 0x7:
                cms->latched_data = val;
                break;
        }
}

uint8_t cms_read(uint16_t addr, void *p)
{
        cms_t *cms = (cms_t *)p;
        
//        pclog("cms_read : addr %04X\n", addr);
        switch (addr & 0xf)
        {
                case 0x1:
                return cms->addrs[0];
                case 0x3:
                return cms->addrs[1];
                case 0x4:
                return 0x7f;
                case 0xa: case 0xb:
                return cms->latched_data;
        }
        return 0xff;
}

void *cms_init()
{
        cms_t *cms = malloc(sizeof(cms_t));
        memset(cms, 0, sizeof(cms_t));

        pclog("cms_init\n");
        io_sethandler(0x0220, 0x0010, cms_read, NULL, NULL, cms_write, NULL, NULL, cms);
        sound_add_handler(cms_get_buffer, cms);
        return cms;
}

void cms_close(void *p)
{
        cms_t *cms = (cms_t *)p;
        
        free(cms);
}

device_t cms_device =
{
        "Creative Music System / Game Blaster",
        0,
        cms_init,
        cms_close,
        NULL,
        NULL,
        NULL,
        NULL
};
