/*
 *  Copyright (C) 2002-2019  The DOSBox Team
 *
 *  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 "dosbox.h"
#include "inout.h"
#include "mixer.h"
#include "mem.h"
#include "hardware.h"
#include "setup.h"
#include "support.h"
#include "pic.h"
#include <cstring>
#include <math.h>

#include "mame/emu.h"
#include "mame/saa1099.h"

#define MASTER_CLOCK 7159090

//My mixer channel
static MixerChannel * cms_chan;
//Timer to disable the channel after a while
static Bit32u lastWriteTicks;
static Bit32u cmsBase;
static saa1099_device* device[2];

static void write_cms(Bitu port, Bitu val, Bitu /* iolen */) {
	if(cms_chan && (!cms_chan->enabled)) cms_chan->Enable(true);
	lastWriteTicks = PIC_Ticks;
	switch ( port - cmsBase ) {
	case 1:
		device[0]->control_w(0, 0, val);
		break;
	case 0:
		device[0]->data_w(0, 0, val);
		break;
	case 3:
		device[1]->control_w(0, 0, val);
		break;
	case 2:
		device[1]->data_w(0, 0, val);
		break;
	}
}

static void CMS_CallBack(Bitu len) {
	enum {
		BUFFER_SIZE = 2048
	};

	if ( len > BUFFER_SIZE )
		return;

	if ( cms_chan ) {

		//Have there been 10 seconds of no commands, disable channel
		if ( lastWriteTicks + 10000 < PIC_Ticks ) {
			cms_chan->Enable( false );
			return;
		}
		Bit32s result[BUFFER_SIZE][2];
		Bit16s work[2][BUFFER_SIZE];
		Bit16s* buffers[2] = { work[0], work[1] };
		device_sound_interface::sound_stream stream;
		device[0]->sound_stream_update(stream, 0, buffers, len);
		for (Bitu i = 0; i < len; i++) {
			result[i][0] = work[0][i];
			result[i][1] = work[1][i];
		}
		device[1]->sound_stream_update(stream, 0, buffers, len);
		for (Bitu i = 0; i < len; i++) {
			result[i][0] += work[0][i];
			result[i][1] += work[1][i];
		}
		cms_chan->AddSamples_s32( len, result[0] );
	}
}

// The Gameblaster detection
static Bit8u cms_detect_register = 0xff;

static void write_cms_detect(Bitu port, Bitu val, Bitu /* iolen */) {
	switch ( port - cmsBase ) {
	case 0x6:
	case 0x7:
		cms_detect_register = val;
		break;
	}
}

static Bitu read_cms_detect(Bitu port, Bitu /* iolen */) {
	Bit8u retval = 0xff;
	switch ( port - cmsBase ) {
	case 0x4:
		retval = 0x7f;
		break;
	case 0xa:
	case 0xb:
		retval = cms_detect_register;
		break;
	}
	return retval;
}


class CMS:public Module_base {
private:
	IO_WriteHandleObject WriteHandler;
	IO_WriteHandleObject DetWriteHandler;
	IO_ReadHandleObject DetReadHandler;
	MixerObject MixerChan;

public:
	CMS(Section* configuration):Module_base(configuration) {
		Section_prop * section = static_cast<Section_prop *>(configuration);
		Bitu sampleRate = section->Get_int( "oplrate" );
		cmsBase = section->Get_hex("sbbase");
		WriteHandler.Install( cmsBase, write_cms, IO_MB, 4 );

		// A standalone Gameblaster has a magic chip on it which is
		// sometimes used for detection.
		const char * sbtype=section->Get_string("sbtype");
		if (!strcasecmp(sbtype,"gb")) {
			DetWriteHandler.Install( cmsBase + 4, write_cms_detect, IO_MB, 12 );
			DetReadHandler.Install(cmsBase,read_cms_detect,IO_MB,16);
		}

		/* Register the Mixer CallBack */
		cms_chan = MixerChan.Install(CMS_CallBack,sampleRate,"CMS");

		lastWriteTicks = PIC_Ticks;

		const uint32_t clock = 7159090; // 14318180 isa clock / 2

		machine_config config;
		device[0] = new saa1099_device(config, "", 0, clock);
		device[1] = new saa1099_device(config, "", 0, clock);

		device[0]->device_start();
		device[1]->device_start();
	}

	~CMS() {
		cms_chan = 0;
		delete device[0];
		delete device[1];
	}
};


static CMS* test;
   
void CMS_Init(Section* sec) {
	test = new CMS(sec);
}
void CMS_ShutDown(Section* sec) {
	delete test;	       
}
