// 1MHz bus Paula emulations for B-Em
// Copyright (C) 2020 Dominic Beesley
//
// This program is free software; you can redistribute it and / or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110 - 1301 USA.

#include <stdio.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <string.h>

#include "b-em.h"
#include <allegro5/allegro_audio.h>
#include "sound.h"
#include "savestate.h"

#include "paula.h"

#define RAM_SIZE				(512*1024)
#define NUM_CHANNELS			4
#define JIM_DEV					0xD0
#define JIM_PAGE				0xFEFC
#define REG_BASE				0x80

static uint8_t jimDev;
static uint16_t jimPage;		//fcfe,fcfd  - big endian

typedef struct
{

    //user facing registers
    int8_t	    data;
    uint8_t	    addr_bank;
    uint16_t	addr;

    uint8_t     period_h_latch;
    uint16_t	period;
    uint16_t	len;
    bool	    act;
    bool	    repeat;
    uint8_t	    vol;
    uint16_t	repoff;
    uint8_t	    peak;

    //internal registers
    bool	    act_prev;
    uint16_t	samper_ctr;
    int8_t	    data_next;
    uint16_t	sam_ctr;


} CHANNELREGS;

static uint8_t	ChipRam[RAM_SIZE];
CHANNELREGS		ChannelRegs[NUM_CHANNELS];
uint8_t			ChannelSel;
uint8_t			Volume;

#define H1M_STREAM_RATE 31250

#define H1M_PAULA_CLK 3547672
#define H1M_PCLK_LIM  65536
#define H1M_PCLK_A    (int)((((long long)H1M_PCLK_LIM) * ((long long)H1M_PAULA_CLK)) / (long long)H1M_STREAM_RATE)

uint32_t paula_clock_acc = 0; //paula clock acumulator - when this overflows do a Paula period


void paula_reset()
{
    jimDev = 0;
    jimPage = 0;
    ChannelSel = 0;
    Volume = 0x3F;
    for (int i = 0; i < NUM_CHANNELS; i++) {
        ChannelRegs[i].data = 0;
        ChannelRegs[i].addr = 0;
        ChannelRegs[i].addr_bank = 0;

        ChannelRegs[i].act = false;
        ChannelRegs[i].act_prev = false;
        ChannelRegs[i].repeat = false;

        ChannelRegs[i].len = 0;
        ChannelRegs[i].period_h_latch = 0;
        ChannelRegs[i].period = 0;
        ChannelRegs[i].vol = 0;
        ChannelRegs[i].peak = 0;

        ChannelRegs[i].repoff = 0;

    }

}


void paula_loadstate(FILE *f) {
}


void paula_savestate(FILE *f) {
}

void paula_init()
{
    memset(ChipRam, 0, sizeof(ChipRam));

}

sound_rec_t paula_rec = {
    NULL,   // fp
    false,  // rec_started
    "Record Paula to file",
    1,      // WAVE type
    1,      // channels
    31250,  // sample rate
    16      // bits/sample
};

void paula_close(void)
{
    if (paula_rec.fp)
        sound_stop_rec(&paula_rec);
}


void paula_write(uint16_t addr, uint8_t val)
{
    if (addr == 0xfcff)
        jimDev = val;
    else {
        if (jimDev == DEVNO_PAULA)
        {
            if (addr == 0xfcfe)
                jimPage = (jimPage & 0xFF00) | val;
            else if (addr == 0xfcfd)
                jimPage = (jimPage & 0x00FF) | ((uint16_t)val << 8);
            else if ((addr & 0xff00) == 0xfd00) {
                //jim
                if ((jimPage == JIM_PAGE) && ((addr & 0x00f0) == REG_BASE))
                {
                    // sound registers
                    //note registers are all exposed big-endian style
                    switch (addr & 0x0F)
                    {
                    case 0:
                        ChannelRegs[ChannelSel].data = val;
                        break;
                    case 1:
                        ChannelRegs[ChannelSel].addr_bank = val;
                        break;
                    case 2:
                        ChannelRegs[ChannelSel].addr = (ChannelRegs[ChannelSel].addr & 0x00FF) | (val << 8);
                        break;
                    case 3:
                        ChannelRegs[ChannelSel].addr = (ChannelRegs[ChannelSel].addr & 0xFF00) | val;
                        break;
                    case 4:
                        ChannelRegs[ChannelSel].period_h_latch = val;
                        break;
                    case 5:
                        ChannelRegs[ChannelSel].period = (ChannelRegs[ChannelSel].period_h_latch << 8) | val;
                        break;
                    case 6:
                        ChannelRegs[ChannelSel].len = (ChannelRegs[ChannelSel].len & 0x00FF) | (val << 8);
                        break;
                    case 7:
                        ChannelRegs[ChannelSel].len = (ChannelRegs[ChannelSel].len & 0xFF00) | val;
                        break;
                    case 8:
                        ChannelRegs[ChannelSel].act = (val & 0x80) != 0;
                        ChannelRegs[ChannelSel].repeat = (val & 0x01) != 0;
                        ChannelRegs[ChannelSel].sam_ctr = 0;
                        break;
                    case 9:
                        ChannelRegs[ChannelSel].vol = val & 0xFC;
                        break;
                    case 10:
                        ChannelRegs[ChannelSel].repoff = (ChannelRegs[ChannelSel].repoff & 0x00FF) | (val << 8);
                        break;
                    case 11:
                        ChannelRegs[ChannelSel].repoff = (ChannelRegs[ChannelSel].repoff & 0xFF00) | val;
                        break;
                    case 12:
                        ChannelRegs[ChannelSel].peak = 0;
                        break;
                    case 14:
                        Volume = val & 0xFC;
                        break;
                    case 15:
                        ChannelSel = val % NUM_CHANNELS;
                        break;
                    }
                }
                else
                {
                    // everything else write chip ram!
                    int chipaddr = (((int)jimPage) << 8) + (((int)addr) & 0xFF);
                    ChipRam[chipaddr % RAM_SIZE] = val;
                }
            }

        }
    }
}

bool paula_read(uint16_t addr, uint8_t *val)
{
    if (jimDev == DEVNO_PAULA)
    {
        if (addr == 0xfcff) {
            *val = ~jimDev;
            return true;
        }
        else if (addr == 0xfcfe) {
            *val = (uint8_t)jimPage;
            return true;
        }
        else if (addr == 0xfcfd) {
            *val = jimPage >> 8;
            return true;
        }
        else if ((addr & 0xff00) == 0xfd00) {
            //jim
            if ((jimPage == JIM_PAGE) && ((addr & 0x00f0) == REG_BASE))
            {
                //note registers are all exposed big-endian style
                switch (addr & 0x0F)
                {
                case 0:
                    *val = ChannelRegs[ChannelSel].data;
                    break;
                case 1:
                    *val = ChannelRegs[ChannelSel].addr_bank;
                    break;
                case 2:
                    *val = ChannelRegs[ChannelSel].addr >> 8;
                    break;
                case 3:
                    *val = (uint8_t)ChannelRegs[ChannelSel].addr;
                    break;
                case 4:
                    *val = ChannelRegs[ChannelSel].period >> 8;
                    break;
                case 5:
                    *val = (uint8_t)ChannelRegs[ChannelSel].period;
                    break;
                case 6:
                    *val = ChannelRegs[ChannelSel].len >> 8;
                    break;
                case 7:
                    *val = (uint8_t)ChannelRegs[ChannelSel].len;
                    break;
                case 8:
                    *val = (ChannelRegs[ChannelSel].act ? 0x80 : 0x00) | (ChannelRegs[ChannelSel].repeat ? 0x01 : 0x00);
                    break;
                case 9:
                    *val = ChannelRegs[ChannelSel].vol;
                    break;
                case 10:
                    *val = ChannelRegs[ChannelSel].repoff >> 8;
                    break;
                case 11:
                    *val = (uint8_t)ChannelRegs[ChannelSel].repoff;
                    break;
                case 12:
                    *val = ChannelRegs[ChannelSel].peak;
                    break;
                case 14:
                    *val = Volume; //as of 2019/11/23 real paula returns sel here - is a bug to be fixed
                    break;
                case 15:
                    *val = ChannelSel;
                    break;
                default:
                    *val = 255;
                    break;
                }
            }
            else
            {
                // everything else write chip ram!
                int chipaddr = (((int)jimPage) << 8) + (((int)addr) & 0xFF);
                *val = ChipRam[chipaddr % RAM_SIZE];
            }
            return true;
        }
        else
            return false;
    }
    else
        return false;
}

static void fetch_mem(CHANNELREGS *curchan) {


    //this is a very rough approximation of what really happens in terms of prioritisation
    //but should be close enough - normally data accesses that clash will be queued up
    //by intcon and could take many 8M cycles but here we just always deliver the data!
    int addr = (((int)curchan->addr_bank) << 16) + (int)curchan->addr + (int)curchan->sam_ctr;
    curchan->data_next = ChipRam[addr % RAM_SIZE];

    if (curchan->sam_ctr == curchan->len)
    {
        if (curchan->repeat)
        {
            curchan->sam_ctr = curchan->repoff;
        }
        else
        {
            curchan->act = false;
            curchan->sam_ctr = 0;
        }
    }
    else
    {
        curchan->sam_ctr++;
    }

}



static void paula_update_3_5MHz()
{

    //do this once every 3.5MhZ

    for (int i = 0; i < NUM_CHANNELS; i++)
    {
        CHANNELREGS *curchan = &ChannelRegs[i];

        if (!curchan->act) {
            curchan->samper_ctr = 0;
        }
        else {
            if (!curchan->act_prev)
            {
                fetch_mem(curchan);
                curchan->samper_ctr = curchan->period;
            }
            else if (curchan->samper_ctr == 0)
            {
                curchan->data = curchan->data_next;
                fetch_mem(curchan);
                curchan->samper_ctr = curchan->period;
            }
            else
                curchan->samper_ctr--;
        }

        curchan->act_prev = curchan->act;
    }

}

static void fput_samples(FILE *fp, int16_t s)
{
    char bytes[2];
    bytes[0] = s;
    bytes[1] = s >> 8;
    fwrite(bytes, 2, 1, fp);
    paula_rec.rec_started = true;
}


static int16_t paula_get_sample()
{
    int16_t sample = 0;

    for (int i = 0; i < NUM_CHANNELS; i++)
    {
        int snd_dat = (int)ChannelRegs[i].data * (int)(ChannelRegs[i].vol >> 2);
        sample += snd_dat;
        int snd_mag = ((snd_dat > 0) ? snd_dat : -snd_dat) >> 6;
        if (snd_mag > ChannelRegs[i].peak)
            ChannelRegs[i].peak = snd_mag;
    }

    return sample;
}

// use sound rate of 31250
void paula_fillbuf(int16_t *buffer, int len) {
    int sample;
    int16_t *bufptr = buffer;
    for (sample = 0; sample < len; sample++) {
        paula_clock_acc += H1M_PCLK_A;
        while (paula_clock_acc > H1M_PCLK_LIM) {
            paula_update_3_5MHz();
            paula_clock_acc -= H1M_PCLK_LIM;
        }
        int16_t s = paula_get_sample();

        if (paula_rec.fp)
            fput_samples(paula_rec.fp, s);
        *bufptr++ += s;
    }
}

