/*
 * Copyright (C) 2000-2017 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
 * Copyright (C) 2009-2010 David Robillard <d@drobilla.net>
 * Copyright (C) 2010-2012 Carl Hetherington <carl@carlh.net>
 * Copyright (C) 2015-2019 Robin Gareus <robin@gareus.org>
 *
 * 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.
 */

#pragma once

#include "temporal/time.h"

#include "pbd/signals.h"
#include "pbd/ringbuffer.h"

#include "midi++/libmidi_visibility.h"
#include "midi++/types.h"
#include "midi++/parser.h"

namespace ARDOUR {
	class PortEngine;
}

namespace MIDI {

class Port;
class Parser;
class MachineControlCommand;

/** Class to handle incoming and outgoing MIDI machine control messages */
class LIBMIDIPP_API MachineControl
{
  public:
	typedef PBD::Signal<void(MachineControl&)> MMCSignal;

	enum Command {
		cmdStop = 0x1,
		cmdPlay = 0x2,
		cmdDeferredPlay = 0x3,
		cmdFastForward = 0x4,
		cmdRewind = 0x5,
		cmdRecordStrobe = 0x6,

		cmdRecordExit = 0x7,
		cmdRecordPause = 0x8,
		cmdPause = 0x9,
		cmdEject = 0xA,
		cmdChase = 0xB,
		cmdCommandErrorReset = 0xC,
		cmdMmcReset = 0xD,

		cmdIllegalMackieJogStart = 0x20,
		cmdIllegalMackieJogStop = 0x21,

		cmdWrite = 0x40,
		cmdMaskedWrite = 0x41,
		cmdRead = 0x42,
		cmdUpdate = 0x43,
		cmdLocate = 0x44,
		cmdVariablePlay = 0x45,
		cmdSearch = 0x46,

		cmdShuttle = 0x47,
		cmdStep = 0x48,
		cmdAssignSystemMaster = 0x49,
		cmdGeneratorCommand = 0x4A,
		cmdMtcCommand = 0x4B,
		cmdMove = 0x4C,
		cmdAdd = 0x4D,

		cmdSubtract = 0x4E,
		cmdDropFrameAdjust = 0x4F,
		cmdProcedure = 0x50,
		cmdEvent = 0x51,
		cmdGroup = 0x52,
		cmdCommandSegment = 0x53,
		cmdDeferredVariablePlay = 0x54,

		cmdRecordStrobeVariable = 0x55,

		cmdWait = 0x7C,
		cmdResume = 0x7F
	};

        MachineControl ();

        void set_ports (MIDI::Port* input, MIDI::Port* output);

	Port* input_port() { return _input_port; }
	Port* output_port() { return _output_port; }

	void set_receive_device_id (byte id);
	void set_send_device_id (byte id);
	byte receive_device_id () const { return _receive_device_id; }
	byte send_device_id () const { return _send_device_id; }
	void enable_send (bool);
	bool send_enabled () const { return _enable_send; }
	void send (MachineControlCommand const &, timestamp_t when);

	static bool is_mmc (byte *sysex_buf, size_t len);

	/* Signals to connect to if you want to run "callbacks"
	   when certain MMC commands are received.
	*/

	MMCSignal Stop;
	MMCSignal Play;
	MMCSignal DeferredPlay;
	MMCSignal FastForward;
	MMCSignal Rewind;
	MMCSignal RecordStrobe;
	MMCSignal RecordExit;
	MMCSignal RecordPause;
	MMCSignal Pause;
	MMCSignal Eject;
	MMCSignal Chase;
	MMCSignal CommandErrorReset;
	MMCSignal MmcReset;
	MMCSignal JogStart;
	MMCSignal JogStop;
	MMCSignal Write;
	MMCSignal MaskedWrite;
	MMCSignal Read;
	MMCSignal Update;
	MMCSignal VariablePlay;
	MMCSignal Search;
	MMCSignal AssignSystemMaster;
	MMCSignal GeneratorCommand;
	MMCSignal MidiTimeCodeCommand;
	MMCSignal Move;
	MMCSignal Add;
	MMCSignal Subtract;
	MMCSignal DropFrameAdjust;
	MMCSignal Procedure;
	MMCSignal Event;
	MMCSignal Group;
	MMCSignal CommandSegment;
	MMCSignal DeferredVariablePlay;
	MMCSignal RecordStrobeVariable;
	MMCSignal Wait;
	MMCSignal Resume;

	PBD::Signal<void()> SPPStart;
	PBD::Signal<void()> SPPContinue;
	PBD::Signal<void()> SPPStop;

	/* The second argument is the shuttle speed, the third is
	   true if the direction is "forwards", false for "reverse"
	*/

	PBD::Signal<void(MachineControl&,float,bool)> Shuttle;

	/* The second argument specifies the desired track record enabled
	   status.
	*/

	PBD::Signal<void(MachineControl &,size_t,bool)>
		                             TrackRecordStatusChange;

	/* The second argument specifies the desired track record enabled
	   status.
	*/

	PBD::Signal<void(MachineControl &,size_t,bool)>
		                             TrackMuteChange;

	/* The second argument points to a byte array containing
	   the locate target value in MMC Standard Time Code
	   format (5 bytes, roughly: hrs/mins/secs/frames/subframes)
	*/

	PBD::Signal<void(MachineControl &, const byte *)> Locate;

	/* The second argument is the number of steps to jump */

	PBD::Signal<void(MachineControl &, int)> Step;

#define MMC_NTRACKS 48

	/* note: these are not currently in use */

	byte updateRate;
	byte responseError;
	byte commandError;
	byte commandErrorLevel;

	byte motionControlTally;
	byte velocityTally;
	byte stopMode;
	byte fastMode;
	byte recordMode;
	byte recordStatus;
	bool trackRecordStatus[MMC_NTRACKS];
	bool trackRecordReady[MMC_NTRACKS];
	byte globalMonitor;
	byte recordMonitor;
	byte trackSyncMonitor;
	byte trackInputMonitor;
	byte stepLength;
	byte playSpeedReference;
	byte fixedSpeed;
	byte lifterDefeat;
	byte controlDisable;
	byte trackMute[MMC_NTRACKS];
	byte failure;
	byte selectedTimeCode;
	byte shortSelectedTimeCode;
	byte timeStandard;
	byte selectedTimeCodeSource;
	byte selectedTimeCodeUserbits;
	byte selectedMasterCode;
	byte requestedOffset;
	byte actualOffset;
	byte lockDeviation;
	byte shortSelectedMasterCode;
	byte shortRequestedOffset;
	byte shortActualOffset;
	byte shortLockDeviation;
	byte resolvedPlayMode;
	byte chaseMode;
	byte generatorTimeCode;
	byte shortGeneratorTimeCode;
	byte generatorCommandTally;
	byte generatorSetUp;
	byte generatorUserbits;
	byte vitcInsertEnable;
	byte midiTimeCodeInput;
	byte shortMidiTimeCodeInput;
	byte midiTimeCodeCommandTally;
	byte midiTimeCodeSetUp;
	byte gp0;
	byte gp1;
	byte gp2;
	byte gp3;
	byte gp4;
	byte gp5;
	byte gp6;
	byte gp7;
	byte shortGp0;
	byte shortGp1;
	byte shortGp2;
	byte shortGp3;
	byte shortGp4;
	byte shortGp5;
	byte shortGp6;
	byte shortGp7;
	byte procedureResponse;
	byte eventResponse;
	byte responseSegment;
	byte wait;
	byte resume;

  private:
	byte _receive_device_id;
	byte _send_device_id;
	Port* _input_port;
	Port* _output_port;
	bool _enable_send; ///< true if MMC sending is enabled

	void process_mmc_message (Parser &p, byte *, size_t len);
	PBD::ScopedConnectionList port_connections; ///< connections to our parser for incoming data

	int  do_masked_write (byte *, size_t len);
	int  do_locate (byte *, size_t len);
	int  do_step (byte *, size_t len);
	int  do_shuttle (byte *, size_t len);

	void write_track_status (byte *, size_t len, byte reg);
	void spp_start ();
	void spp_continue ();
	void spp_stop ();
};

/** Class to describe a MIDI machine control command to be sent.
 *  In an ideal world we might use a class hierarchy for this, but objects of this type
 *  have to be allocated off the stack for RT safety.
 */
class LIBMIDIPP_API MachineControlCommand
{
public:
	MachineControlCommand () : _command (MachineControl::Command (0)) {}
	MachineControlCommand (MachineControl::Command);
	MachineControlCommand (Timecode::Time);

	MIDI::byte* fill_buffer (MachineControl *mmc, MIDI::byte *) const;

private:
	MachineControl::Command _command;
	Timecode::Time _time;
};

} // namespace MIDI

