#include <stdint.h>
#include <string.h>
#include "podules.h"
#include "aka31.h"
#include "d71071l.h"
#include "wd33c93a.h"

#define DMA_MODE_AUTOINIT (1 << 4)
#define DMA_MODE_DIR      (1 << 5)

void d71071l_init(d71071l_t *dma, podule_t *podule)
{
	memset(dma, 0, sizeof(d71071l_t));
	dma->podule = podule;
}

void d71071l_write(d71071l_t *dma, uint32_t addr, uint8_t val)
{
	switch (addr & 0x21c)
	{
		case 0x200:
		dma->base = val & 4;
		dma->selch = val & 3;
		break;

		case 0x004:
		dma->channel[dma->selch].count_base = (dma->channel[dma->selch].count_base & 0xff00) | val;
		if (!dma->base)
			dma->channel[dma->selch].count_cur = (dma->channel[dma->selch].count_cur & 0xff00) | val;
		break;
		case 0x204:
		dma->channel[dma->selch].count_base = (dma->channel[dma->selch].count_base & 0xff) | (val << 8);
		if (!dma->base)
			dma->channel[dma->selch].count_cur = (dma->channel[dma->selch].count_cur & 0xff) | (val << 8);
		break;

		case 0x008:
		dma->channel[dma->selch].addr_base = (dma->channel[dma->selch].addr_base & 0xffff00) | val;
		if (!dma->base)
			dma->channel[dma->selch].addr_cur = (dma->channel[dma->selch].addr_cur & 0xffff00) | val;
		break;
		case 0x208:
		dma->channel[dma->selch].addr_base = (dma->channel[dma->selch].addr_base & 0xff00ff) | (val << 8);
		if (!dma->base)
			dma->channel[dma->selch].addr_cur = (dma->channel[dma->selch].addr_cur & 0xff00ff) | (val << 8);
		break;
		case 0x00c:
		dma->channel[dma->selch].addr_base = (dma->channel[dma->selch].addr_base & 0x00ffff) | (val << 16);
		if (!dma->base)
			dma->channel[dma->selch].addr_cur = (dma->channel[dma->selch].addr_cur & 0x00ffff) | (val << 16);
		break;

		case 0x014:
		dma->channel[dma->selch].mode = val;
		break;

		case 0x21c:
		dma->mask = val;
		break;
	}
}

uint8_t d71071l_read(d71071l_t *dma, uint32_t addr)
{
	switch (addr & 0x21c)
	{
		case 0x200:
		return dma->base | dma->selch;

		case 0x004:
		if (!dma->base)
			return dma->channel[dma->selch].count_cur & 0xff;
		return dma->channel[dma->selch].count_base & 0xff;
		case 0x204:
		if (!dma->base)
			return (dma->channel[dma->selch].count_cur >> 8) & 0xff;
		return (dma->channel[dma->selch].count_base >> 8) & 0xff;

		case 0x008:
		if (!dma->base)
			return dma->channel[dma->selch].addr_cur & 0xff;
		return dma->channel[dma->selch].addr_base & 0xff;
		case 0x208:
		if (!dma->base)
			return (dma->channel[dma->selch].addr_cur >> 8) & 0xff;
		return (dma->channel[dma->selch].addr_base >> 8) & 0xff;
		case 0x00c:
		if (!dma->base)
			return (dma->channel[dma->selch].addr_cur >> 16) & 0xff;
		return (dma->channel[dma->selch].addr_base >> 16) & 0xff;

		case 0x014:
		return dma->channel[dma->selch].mode;

		case 0x21c:
		return dma->mask;
	}
	return 0;
}

int dma_read(d71071l_t *dma, int channel)
{
	uint8_t val;

	if (dma->mask & (1 << channel))
		return -1;
	if (!(dma->channel[channel].mode & (1 << 3)))
		return -1;
	val = aka31_read_ram(dma->podule, dma->channel[channel].addr_cur & 0xffff);
//aka31_log("dma_read: channel=%i addr=%04x count=%04x val=%02x\n", channel, dma->channel[channel].addr_cur, dma->channel[channel].count_cur, val);
	if (dma->channel[channel].mode & DMA_MODE_DIR)
		dma->channel[channel].addr_cur--;
	else
		dma->channel[channel].addr_cur++;

	dma->channel[channel].count_cur--;

	if (dma->channel[channel].count_cur < 0)
	{
		if (dma->channel[channel].mode & DMA_MODE_AUTOINIT)
		{
			dma->channel[channel].addr_cur  = dma->channel[channel].addr_base;
			dma->channel[channel].count_cur = dma->channel[channel].count_base;
		}
		else
		{
			aka31_tc_int(dma->podule);
			dma->mask |= (1 << channel);
//                        aka31_log("dma_tc\n");
		}
	}

	return val;
}

int dma_write(d71071l_t *dma, int channel, uint8_t val)
{
	if (dma->mask & (1 << channel))
		return -1;
	if (dma->channel[channel].mode & (1 << 3))
		return -1;
//aka31_log("dma_write: channel=%i val=%02x addr=%04x count=%04x\n", channel, val, dma->channel[channel].addr_cur, dma->channel[channel].count_cur);
	aka31_write_ram(dma->podule, dma->channel[channel].addr_cur & 0xffff, val);
	if (dma->channel[channel].mode & DMA_MODE_DIR)
		dma->channel[channel].addr_cur--;
	else
		dma->channel[channel].addr_cur++;

	dma->channel[channel].count_cur--;

	if (dma->channel[channel].count_cur < 0)
	{
		if (dma->channel[channel].mode & DMA_MODE_AUTOINIT)
		{
			dma->channel[channel].addr_cur  = dma->channel[channel].addr_base;
			dma->channel[channel].count_cur = dma->channel[channel].count_base;
		}
		else
		{
			aka31_tc_int(dma->podule);
			dma->mask |= (1 << channel);
//                        aka31_log("dma_tc\n");
		}
	}

	return 0;
}

