/*
    Copyright (C) 2000 Paul Barton-Davis 

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

    $Id: mmc.cc,v 1.13 2005/04/18 19:08:06 pauld Exp $
*/

#include <map>

#include <pbd/error.h>
#include <midi++/mmc.h>
#include <midi++/port.h>
#include <midi++/parser.h>

using namespace std;
using namespace MIDI;

static std::map<int,string> mmc_cmd_map;
static void build_mmc_cmd_map ()
{
	pair<int,string> newpair;

	newpair.first = 0x1;
	newpair.second = "Stop";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x2;
	newpair.second = "Play";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x3;
	newpair.second = "DeferredPlay";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4;
	newpair.second = "FastForward";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x5;
	newpair.second = "Rewind";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x6;
	newpair.second = "RecordStrobe";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x7;
	newpair.second = "RecordExit";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x8;
	newpair.second = "RecordPause";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x9;
	newpair.second = "Pause";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0xA;
	newpair.second = "Eject";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0xB;
	newpair.second = "Chase";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0xC;
	newpair.second = "CommandErrorReset";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0xD;
	newpair.second = "MmcReset";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x20;
	newpair.second = "Illegal Mackie Jog Start";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x21;
	newpair.second = "Illegal Mackie Jog Stop";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x40;
	newpair.second = "Write";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x41;
	newpair.second = "MaskedWrite";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x42;
	newpair.second = "Read";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x43;
	newpair.second = "Update";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x44;
	newpair.second = "Locate";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x45;
	newpair.second = "VariablePlay";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x46;
	newpair.second = "Search";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x47;
	newpair.second = "Shuttle";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x48;
	newpair.second = "Step";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x49;
	newpair.second = "AssignSystemMaster";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4A;
	newpair.second = "GeneratorCommand";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4B;
	newpair.second = "MtcCommand";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4C;
	newpair.second = "Move";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4D;
	newpair.second = "Add";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4E;
	newpair.second = "Subtract";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x4F;
	newpair.second = "DropFrameAdjust";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x50;
	newpair.second = "Procedure";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x51;
	newpair.second = "Event";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x52;
	newpair.second = "Group";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x53;
	newpair.second = "CommandSegment";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x54;
	newpair.second = "DeferredVariablePlay";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x55;
	newpair.second = "RecordStrobeVariable";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x7C;
	newpair.second = "Wait";
	mmc_cmd_map.insert (newpair);

	newpair.first = 0x7F;
	newpair.second = "Resume";
	mmc_cmd_map.insert (newpair);
}


MachineControl::MachineControl (Port &p, float version,
				CommandSignature &csig,
				ResponseSignature &rsig)

	: _port (p)
{
	Parser *parser;
	
	build_mmc_cmd_map ();

	_device_id = 1;
	
	if ((parser = _port.input()) != 0) {
		parser->mmc.connect 
			(slot (*this, &MachineControl::process_mmc_message));
	} else {
		warning << "MMC connected to a non-input port: useless!"
			<< endmsg;
	}
}

void
MachineControl::set_device_id (byte id)

{
	_device_id = id & 0x7f;
}

bool
MachineControl::is_mmc (byte *sysex_buf, size_t len)

{
	if (len < 4 || len > 48) {
		return false;
	}

	if (sysex_buf[1] != 0x7f) {
		return false;
	}

	if (sysex_buf[3] != 0x6 && /* MMC Command */
	    sysex_buf[3] != 0x7) { /* MMC Response */
		return false;
	}
	
	return true;
}

void
MachineControl::process_mmc_message (Parser &p, byte *msg, size_t len)

{
	size_t skiplen;
	byte *mmc_msg;
	bool single_byte;

	/* Reject if its not for us. 0x7f is the "all-call" device ID */

	/* msg[0] = 0x7f (MMC sysex ID(
	   msg[1] = device ID
	   msg[2] = 0x6 (MMC command) or 0x7 (MMC response)
	   msg[3] = MMC command code
	   msg[4] = (typically) byte count for following part of command
	*/

#if 0
	cerr << "*** MMC message: len = " << len << "\n\t";
	for (size_t i = 0; i < len; i++) {
		cerr << hex << (int) msg[i] << dec << ' ';
	}
	cerr << endl;
#endif

	if (msg[1] != 0x7f && msg[1] != _device_id) {
		return;
	}

	mmc_msg = &msg[3];
	len -= 3;

	do {

		single_byte = false;

		/* this works for all non-single-byte "counted"
		   commands. we set it to 1 for the exceptions.
		*/

		std::map<int,string>::iterator x = mmc_cmd_map.find ((int)mmc_msg[0]);
		string cmdname = "unknown";

		if (x != mmc_cmd_map.end()) {
			cmdname = (*x).second;
		}

#if 0
		cerr << "+++ MMC type " 
		     << hex
		     << ((int) *mmc_msg)
		     << dec
		     << " \"" << cmdname << "\" "
		     << " len = " << len
		     << endl;
#endif

		switch (*mmc_msg) {

		/* SINGLE-BYTE, UNCOUNTED COMMANDS */

		case cmdStop:
			Stop (*this);
			single_byte = true;
			break;

		case cmdPlay:
			Play (*this);
			single_byte = true;
			break;

		case cmdDeferredPlay:
			DeferredPlay (*this);
			single_byte = true;
			break;

		case cmdFastForward:
			FastForward (*this);
			single_byte = true;
			break;

		case cmdRewind:
			Rewind (*this);
			single_byte = true;
			break;

		case cmdRecordStrobe:
			RecordStrobe (*this);
			single_byte = true;
			break;

		case cmdRecordExit:
			RecordExit (*this);
			single_byte = true;
			break;

		case cmdRecordPause:
			RecordPause (*this);
			single_byte = true;
			break;

		case cmdPause:
			Pause (*this);
			single_byte = true;
			break;

		case cmdEject:
			Eject (*this);
			single_byte = true;
			break;

		case cmdChase:
			Chase (*this);
			single_byte = true;
			break;

		case cmdCommandErrorReset:
			CommandErrorReset (*this);
			single_byte = true;
			break;

		case cmdMmcReset:
			MmcReset (*this);
			single_byte = true;
			break;

		case cmdIllegalMackieJogStart:
			JogStart (*this);
			single_byte = true;
			break;

		case cmdIllegalMackieJogStop:
			JogStop (*this);
			single_byte = true;
			break;

		/* END OF SINGLE-BYTE, UNCOUNTED COMMANDS */

		case cmdMaskedWrite:
			do_masked_write (mmc_msg, len);
			break;

		case cmdLocate:
			do_locate (mmc_msg, len);
			break;

		case cmdShuttle:
			do_shuttle (mmc_msg, len);
			break;

		case cmdStep:
			do_step (mmc_msg, len);
			break;

		case cmdWrite:
		case cmdRead:
		case cmdUpdate:
		case cmdVariablePlay:
		case cmdSearch:
		case cmdAssignSystemMaster:
		case cmdGeneratorCommand:
		case cmdMtcCommand:
		case cmdMove:
		case cmdAdd:
		case cmdSubtract:
		case cmdDropFrameAdjust:
		case cmdProcedure:
		case cmdEvent:
		case cmdGroup:
		case cmdCommandSegment:
		case cmdDeferredVariablePlay:
		case cmdRecordStrobeVariable:
		case cmdWait:
		case cmdResume:
			warning << "MIDI::MachineControl: unimplemented MMC command "
				<< hex << (int) *mmc_msg << dec
				<< endmsg;

			break;

		default:
			warning << "MIDI::MachineControl: unknown MMC command "
				<< hex << (int) *mmc_msg << dec
				<< endmsg;
			
			break;
		}

		/* increase skiplen to cover the command byte and 
		   count byte (if it existed).
		*/

		if (!single_byte) {
			skiplen = mmc_msg[1] + 2;
		} else {
			skiplen = 1;
		}

		if (len <= skiplen) {
			break;
		}

		mmc_msg += skiplen;
		len -= skiplen;

	} while (len > 1); /* skip terminating EOX byte */
}		

int
MachineControl::do_masked_write (byte *msg, size_t len)

{
	/* return the number of bytes "consumed" */

	int retval = msg[1] + 2; /* bytes following + 2 */
	
	switch (msg[2]) {
	case 0x4f:  /* Track Record Ready Status */
		write_track_record_ready (&msg[3], len - 3);
		break;

	default:
		warning << "MIDI::MachineControl: masked write to "
			<< hex << (int) msg[2] << dec
			<< " not implemented"
			<< endmsg;
	}

	return retval;
}

void
MachineControl::write_track_record_ready (byte *msg, size_t len)

{
	size_t n;
	size_t base_track;

	/* Bits 0-4 of the first byte are for special tracks:

	   bit 0: video
	   bit 1: reserved
	   bit 2: time code
	   bit 3: aux track a
	   bit 4: aux track b

	*/

	/* XXX check needed to make sure we don't go outside the
	   support number of tracks.
	*/

	base_track = (msg[0] * 7) - 5;

	for (n = 0; n < 7; n++) {
		if (msg[1] & (1<<n)) {

			/* Only touch tracks that have the "mask"
			   bit set.
			*/

			if (msg[2] & (1<<n)) {
				trackRecordStatus[base_track+n] = true;
				TrackRecordStatusChange (*this, base_track+n,
							 true);
			} else {
				trackRecordStatus[base_track+n] = false;
				TrackRecordStatusChange (*this, base_track+n,
							 false);
			}
		} 

	}
}

int
MachineControl::do_locate (byte *msg, size_t msglen)

{
	if (msg[2] == 0) {
		warning << "MIDI::MMC: locate [I/F] command not supported"
			<< endmsg;
		return 0;
	}

	/* regular "target" locate command */

	Locate (*this, &msg[3]);
	return 0;
}

int
MachineControl::do_step (byte *msg, size_t msglen)
{
	int steps = msg[2] & 0x3f;

	if (msg[2] & 0x40) {
		steps = -steps;
	}

	Step (*this, steps);
	return 0;
}

int
MachineControl::do_shuttle (byte *msg, size_t msglen)

{
	size_t forward;
	byte sh = msg[2];
	byte sm = msg[3];
	byte sl = msg[4];
	size_t left_shift;
	size_t integral;
	size_t fractional;
	float shuttle_speed;

	if (sh & (1<<6)) {
		forward = false;
	} else {
		forward = true;
	}
	
	left_shift = (sh & 0x38) >> 3;

	integral = ((sh & 0x7) << left_shift) | (sm >> (7 - left_shift));
	fractional = (((sm << left_shift) << 7) | (sl << left_shift)) & 0x3FFF;

	shuttle_speed = integral + 
		((float)fractional / (1 << (14)));

	Shuttle (*this, shuttle_speed, forward);

	return 0;
}

