/*
 * sid.c - MOS6581 (SID) emulation, hooks to actual implementation.
 *
 * Written by
 *  Teemu Rantanen <tvr@cs.hut.fi>
 *  Michael Schwendt <sidplay@geocities.com>
 *  Ettore Perazzoli <ettore@comm2000.it>
 *  Dag Lem <resid@nimrod.no>
 *
 * This file is part of VICE, the Versatile Commodore Emulator.
 * See README for copyright notice.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307  USA.
 *
 */

#include "vice.h"

#include <stdio.h>
#include <string.h>

#include "../c64/c64io.h"
#include "catweaselmkiii.h"
#include "fastsid.h"
#include "hardsid.h"
#include "machine.h"
#include "maincpu.h"
#ifdef HAVE_PARSID
#include "parsid.h"
#endif
#include "resources.h"
#include "sid-resources.h"
#include "sid-snapshot.h"
#include "sid.h"
#include "sound.h"
#include "types.h"

#ifdef HAVE_MOUSE
#include "mouse.h"
#include "lightpen.h"
#endif

#ifdef HAVE_RESID
#include "resid.h"
#endif

#ifdef HAVE_RESID_FP
#include "resid-fp.h"
#endif

/* SID engine hooks. */
static sid_engine_t sid_engine;

/* read register value from sid */
static BYTE lastsidread;

/* register data */
static BYTE siddata[SOUND_CHANNELS_MAX][32];

static int (*sid_read_func)(WORD addr, int chipno);
static void (*sid_store_func)(WORD addr, BYTE val, int chipno);

static int sid_enable, sid_engine_type = -1;

BYTE *sid_get_siddata(unsigned int channel)
{
    return siddata[channel];
}

/* ------------------------------------------------------------------------- */

static int sid_read_off(WORD addr, int chipno)
{
    BYTE val;

    if (addr == 0x19 || addr == 0x1a)
        val = 0xff;
    else {
        if (addr == 0x1b || addr == 0x1c)
            val = (BYTE)(maincpu_clk % 256);
        else
            val = 0;
    }

    /* FIXME: Change API, return BYTE! */
    return (int)val;
}

static void sid_write_off(WORD addr, BYTE val, int chipno)
{
}

/* ------------------------------------------------------------------------- */

static BYTE REGPARM2 sid_read_chip(WORD addr, int chipno)
{
    int	val;

    addr &= 0x1f;

    machine_handle_pending_alarms(0);

#ifdef HAVE_MOUSE
    if (addr == 0x19 && _mouse_enabled && chipno == 0) {
        val = mouse_get_x();
    } else if (addr == 0x1a && _mouse_enabled && chipno == 0) {
        val = mouse_get_y();
    } else if (addr == 0x19 && lightpen_enabled && chipno == 0) {
        val = lightpen_read_button_x();
    } else if (addr == 0x1a && lightpen_enabled && chipno == 0) {
        val = lightpen_read_button_y();
    } else
#endif
    {
        /* Account for that read functions in VICE are called _before_
           incrementing the clock. */
        maincpu_clk++;
        val = sid_read_func(addr, chipno);
        maincpu_clk--;
    }

    /* Fallback when sound is switched off. */
    if (val < 0) {
        if (addr == 0x19 || addr == 0x1a)
	    val = 0xff;
	else {
	    if (addr == 0x1b || addr == 0x1c)
		val = maincpu_clk % 256;
	    else
		val = 0;
	}
    }

    lastsidread = val;
    return val;
}

/* write register value to sid */
static void REGPARM3 sid_store_chip(WORD addr, BYTE byte, int chipno)
{
    addr &= 0x1f;

    siddata[chipno][addr] = byte;

    /* WARNING: assumes `maincpu_rmw_flag' is 0 or 1.  */
    machine_handle_pending_alarms(maincpu_rmw_flag + 1);

    if (maincpu_rmw_flag) {
	maincpu_clk--;
	sid_store_func(addr, lastsidread, chipno);
	maincpu_clk++;
    }

    sid_store_func(addr, byte, chipno);
}

/* ------------------------------------------------------------------------- */

BYTE REGPARM1 sid_read(WORD addr)
{
    if (sid_stereo
        && addr >= sid_stereo_address_start
        && addr < sid_stereo_address_end)
        sid_read_chip(addr, 1);

    return sid_read_chip(addr, 0);
}

BYTE REGPARM1 sid2_read(WORD addr)
{
    io_source=IO_SOURCE_STEREO_SID;
    return sid_read_chip(addr, 1);
}

void REGPARM2 sid_store(WORD addr, BYTE byte)
{
    if (sid_stereo
        && addr >= sid_stereo_address_start
        && addr < sid_stereo_address_end) {
        sid_store_chip(addr, byte, 1);
        return;
    }

    sid_store_chip(addr, byte, 0);
}

void REGPARM2 sid2_store(WORD addr, BYTE byte)
{
    sid_store_chip(addr, byte, 1);
}

/* ------------------------------------------------------------------------- */

void sid_reset(void)
{
    sound_reset();

    memset(siddata, 0, sizeof(siddata));
}

static int sidengine;

sound_t *sid_sound_machine_open(int chipno)
{
    sidengine = 0;

    if (resources_get_int("SidEngine", &sidengine) < 0)
        return NULL;

    sid_engine = fastsid_hooks;

#ifdef HAVE_RESID
    if (sidengine == SID_ENGINE_RESID)
        sid_engine = resid_hooks;
#endif

#ifdef HAVE_RESID_FP
    if (sidengine == SID_ENGINE_RESID_FP)
        sid_engine = residfp_hooks;
#endif

    return sid_engine.open(siddata[chipno]);
}

int sid_sound_machine_init(sound_t *psid, int speed, int cycles_per_sec)
{
    return sid_engine.init(psid, speed, cycles_per_sec);
}

void sid_sound_machine_close(sound_t *psid)
{
    sid_engine.close(psid);
}

BYTE sid_sound_machine_read(sound_t *psid, WORD addr)
{
    return sid_engine.read(psid, addr);
}

void sid_sound_machine_store(sound_t *psid, WORD addr, BYTE byte)
{
    sid_engine.store(psid, addr, byte);
}

void sid_sound_machine_reset(sound_t *psid, CLOCK cpu_clk)
{
    sid_engine.reset(psid, cpu_clk);
}

int sid_sound_machine_calculate_samples(sound_t *psid, SWORD *pbuf, int nr,
                                        int interleave, int *delta_t)
{
    return sid_engine.calculate_samples(psid, pbuf, nr, interleave, delta_t);
}

void sid_sound_machine_prevent_clk_overflow(sound_t *psid, CLOCK sub)
{
    sid_engine.prevent_clk_overflow(psid, sub);
}

char *sid_sound_machine_dump_state(sound_t *psid)
{
    return sid_engine.dump_state(psid);
}

int sid_sound_machine_cycle_based(void)
{
    switch (sidengine) {
      case SID_ENGINE_FASTSID:
        return 0;
#ifdef HAVE_RESID
      case SID_ENGINE_RESID:
        return 1;
#endif
#ifdef HAVE_RESID_FP
      case SID_ENGINE_RESID_FP:
        return 1;
#endif
#ifdef HAVE_CATWEASELMKIII
      case SID_ENGINE_CATWEASELMKIII:
        return 0;
#endif
#ifdef HAVE_HARDSID
      case SID_ENGINE_HARDSID:
        return 0;
#endif
#ifdef HAVE_PARSID
      case SID_ENGINE_PARSID_PORT1:
      case SID_ENGINE_PARSID_PORT2:
      case SID_ENGINE_PARSID_PORT3:
        return 0;
#endif
    }

    return 0;
}

int sid_sound_machine_channels(void)
{
    int stereo = 0;
    resources_get_int("SidStereo", &stereo);
    return stereo ? 2 : 1;
}

static void set_sound_func(void)
{
    if (sid_enable) {
        if (sid_engine_type == SID_ENGINE_FASTSID) {
            sid_read_func = sound_read;
            sid_store_func = sound_store;
        }
#ifdef HAVE_RESID
        if (sid_engine_type == SID_ENGINE_RESID) {
            sid_read_func = sound_read;
            sid_store_func = sound_store;
        }
#endif
#ifdef HAVE_RESID_FP
        if (sid_engine_type == SID_ENGINE_RESID_FP) {
            sid_read_func = sound_read;
            sid_store_func = sound_store;
        }
#endif
#ifdef HAVE_CATWEASELMKIII
        if (sid_engine_type == SID_ENGINE_CATWEASELMKIII) {
            sid_read_func = catweaselmkiii_read;
            sid_store_func = catweaselmkiii_store;
        }
#endif
#ifdef HAVE_HARDSID
        if (sid_engine_type == SID_ENGINE_HARDSID) {
            sid_read_func = hardsid_read;
            sid_store_func = hardsid_store;
        }
#endif
#ifdef HAVE_PARSID
        if (sid_engine_type == SID_ENGINE_PARSID_PORT1 ||
            sid_engine_type == SID_ENGINE_PARSID_PORT2 ||
            sid_engine_type == SID_ENGINE_PARSID_PORT3) {
            sid_read_func = parsid_read;
            sid_store_func = parsid_store;
        }
#endif
    } else {
        sid_read_func = sid_read_off;
        sid_store_func = sid_write_off;
    }
}

void sid_sound_machine_enable(int enable)
{
    sid_enable = enable;

    set_sound_func();
}

int sid_engine_set(int engine)
{
#ifdef HAVE_CATWEASELMKIII
    if (engine == SID_ENGINE_CATWEASELMKIII
        && sid_engine_type != SID_ENGINE_CATWEASELMKIII) {
        if (catweaselmkiii_open() < 0)
            return -1;
    }
    if (engine != SID_ENGINE_CATWEASELMKIII
        && sid_engine_type == SID_ENGINE_CATWEASELMKIII)
        catweaselmkiii_close();
#endif
#ifdef HAVE_HARDSID
    if (engine == SID_ENGINE_HARDSID
        && sid_engine_type != SID_ENGINE_HARDSID) {
        if (hardsid_open() < 0)
            return -1;
    }
    if (engine != SID_ENGINE_HARDSID
        && sid_engine_type == SID_ENGINE_HARDSID)
        hardsid_close();
#endif
#ifdef HAVE_PARSID
    if ((engine == SID_ENGINE_PARSID_PORT1 || engine == SID_ENGINE_PARSID_PORT2 || engine == SID_ENGINE_PARSID_PORT3)
        && sid_engine_type != engine) {
        if (engine == SID_ENGINE_PARSID_PORT1) {
          if (parsid_open(1) < 0)
            return -1;
        }
        if (engine == SID_ENGINE_PARSID_PORT2) {
          if (parsid_open(2) < 0)
            return -1;
        }
        if (engine == SID_ENGINE_PARSID_PORT3) {
          if (parsid_open(3) < 0)
            return -1;
        }
    }
    if (engine != SID_ENGINE_PARSID_PORT1 && engine != SID_ENGINE_PARSID_PORT2 && engine != SID_ENGINE_PARSID_PORT3
        && (sid_engine_type == SID_ENGINE_PARSID_PORT1 || sid_engine_type == SID_ENGINE_PARSID_PORT2 || sid_engine_type == SID_ENGINE_PARSID_PORT3))
        parsid_close();
#endif

    sid_engine_type = engine;

    set_sound_func();

    return 0;
}

void sid_state_read(unsigned int channel, sid_snapshot_state_t *sid_state)
{
    sid_engine.state_read(sound_get_psid(channel), sid_state);
}

void sid_state_write(unsigned int channel, sid_snapshot_state_t *sid_state)
{
    sid_engine.state_write(sound_get_psid(channel), sid_state);
}

void sid_set_machine_parameter(long clock_rate)
{
#ifdef HAVE_CATWEASELMKIII
    catweaselmkiii_set_machine_parameter(clock_rate);
#endif
#ifdef HAVE_HARDSID
    hardsid_set_machine_parameter(clock_rate);
#endif
}
