/*
 * Copyright (C) 1998-2018 Paul Davis <paul@linuxaudiosystems.com>
 * Copyright (C) 2005-2006 Taybin Rutkin <taybin@taybin.com>
 * Copyright (C) 2009 David Robillard <d@drobilla.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 <string>
#include <iostream>

#include "pbd/signals.h"

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

namespace MIDI {

class Port;
class Parser;

typedef PBD::Signal<void(Parser&)>                   ZeroByteSignal;
typedef PBD::Signal<void(Parser&,unsigned short)>    BankSignal;
typedef PBD::Signal<void(Parser&,samplecnt_t)>       TimestampedSignal;
typedef PBD::Signal<void(Parser&, byte)>             OneByteSignal;
typedef PBD::Signal<void(Parser &, EventTwoBytes *)> TwoByteSignal;
typedef PBD::Signal<void(Parser &, pitchbend_t)>     PitchBendSignal;
typedef PBD::Signal<void(Parser &, uint16_t, int)>   RPNSignal;
typedef PBD::Signal<void(Parser &, uint16_t, float)> RPNValueSignal;
typedef PBD::Signal<void(Parser &, byte *, size_t)>  Signal;
typedef PBD::Signal<void(Parser &, byte *, size_t, samplecnt_t)> AnySignal;

class LIBMIDIPP_API Parser {
 public:
	Parser ();
	~Parser ();

	/* sets the time that will be reported for any MTC or MIDI Clock
	   message the next time ::scanner() parses such a message. It should
	   therefore be set before every byte passed into ::scanner().
	*/

	samplecnt_t get_timestamp() const { return _timestamp; }
	void set_timestamp (const samplecnt_t timestamp) { _timestamp = timestamp; }

	/* signals that anyone can connect to */

	BankSignal            bank_change;
	TwoByteSignal         note_on;
	TwoByteSignal         note_off;
	TwoByteSignal         poly_pressure;
	OneByteSignal         pressure;
	OneByteSignal         program_change;
	PitchBendSignal       pitchbend;
	TwoByteSignal         controller;

	BankSignal            channel_bank_change[16];
	TwoByteSignal         channel_note_on[16];
	TwoByteSignal         channel_note_off[16];
	TwoByteSignal         channel_poly_pressure[16];
	OneByteSignal         channel_pressure[16];
	OneByteSignal         channel_program_change[16];
	PitchBendSignal       channel_pitchbend[16];
	TwoByteSignal         channel_controller[16];
	ZeroByteSignal        channel_active_preparse[16];
	ZeroByteSignal        channel_active_postparse[16];
	RPNValueSignal        channel_rpn[16];
	RPNValueSignal        channel_nrpn[16];
	RPNSignal             channel_rpn_change[16];
	RPNSignal             channel_nrpn_change[16];

	OneByteSignal         mtc_quarter_frame; /* see below for more useful signals */
	Signal                mtc;
	Signal                raw_preparse;
	Signal                raw_postparse;
	AnySignal             any;
	Signal                sysex;
	Signal                mmc;
	AnySignal             position;
	Signal                song;

	ZeroByteSignal        all_notes_off;
	ZeroByteSignal        tune;
	ZeroByteSignal        active_sense;
	ZeroByteSignal        reset;
	ZeroByteSignal        eox;

	TimestampedSignal     timing;
	TimestampedSignal     tick; /* rarely seen in the wild */
	TimestampedSignal     start;
	TimestampedSignal     stop;
	TimestampedSignal     contineu;  /* note spelling */

	/* This should really be protected, but then derivatives of Port
	   can't access it.
	*/

	void scanner (byte c);

	size_t *message_counts() { return message_counter; }
	const char *midi_event_type_name (MIDI::eventType);
	void trace (bool onoff, std::ostream *o, const std::string &prefix = "");
	bool tracing() { return trace_stream != 0; }

	void set_offline (bool);
	bool offline() const { return _offline; }
	PBD::Signal<void()> OfflineStatusChanged;

	PBD::Signal<int(byte *, size_t)> edit;

	void set_mmc_forwarding (bool yn) {
		_mmc_forward = yn;
	}

	/* MTC */

	MTC_FPS mtc_fps() const { return _mtc_fps; }
	MTC_Status  mtc_running() const { return _mtc_running; }
	const byte *mtc_current() const { return _mtc_time; }
	bool        mtc_locked() const  { return _mtc_locked; }

	PBD::Signal<void(Parser &, int, samplecnt_t)>      mtc_qtr;
	PBD::Signal<void(const byte *, bool, samplecnt_t)> mtc_time;
	PBD::Signal<void(MTC_Status)>                     mtc_status;
	PBD::Signal<bool()>                                 mtc_skipped;

	void set_mtc_forwarding (bool yn) {
		_mtc_forward = yn;
	}

	void reset_mtc_state ();

  private:
	/* tracing */

	std::ostream *trace_stream;
	std::string trace_prefix;
	void trace_event (Parser &p, byte *msg, size_t len, samplecnt_t);
	PBD::ScopedConnection trace_connection;

	size_t message_counter[256];

	enum ParseState {
		NEEDSTATUS,
		NEEDONEBYTE,
		NEEDTWOBYTES,
		VARIABLELENGTH
	};
	ParseState state;
	unsigned char *msgbuf;
	int msglen;
	int msgindex;
	MIDI::eventType msgtype;
	channel_t channel;
	bool _offline;
	bool runnable;
	bool was_runnable;
	bool _mmc_forward;
	bool _mtc_forward;
	int   expected_mtc_quarter_frame_code;
	byte _mtc_time[5];
	byte _qtr_mtc_time[5];
	unsigned long consecutive_qtr_frame_cnt;
	MTC_FPS _mtc_fps;
	MTC_Status _mtc_running;
	bool       _mtc_locked;
	byte last_qtr_frame;

	samplecnt_t _timestamp;

	ParseState pre_variable_state;
	MIDI::eventType pre_variable_msgtype;
	byte last_status_byte;

	void channel_msg (byte);
	void realtime_msg (byte);
	void system_msg (byte);
	void signal (byte *msg, size_t msglen);
	bool possible_mmc (byte *msg, size_t msglen);
	bool possible_mtc (byte *msg, size_t msglen);
	void process_mtc_quarter_frame (byte *msg);
};

} // namespace MIDI


