//
//  midisources.h
//  GZDoom
//
//  Created by Christoph Oelckers on 23.02.18.
//

#ifndef midisources_h
#define midisources_h

#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <functional>
#include <vector>
#include "zmusic/mus2midi.h"
#include "zmusic/mididefs.h"

extern char MIDI_EventLengths[7];
extern char MIDI_CommonLengths[15];


// base class for the different MIDI sources --------------------------------------

class MIDISource
{
	int Volume = 0xffff;
	int LoopLimit = 0;
	std::function<bool(int)> TempoCallback = [](int t) { return false; };
	
protected:

	bool isLooping = false;
	bool skipSysex = false;
	int Division = 0;
	int Tempo = 500000;
	int InitialTempo = 500000;
	uint8_t ChannelVolumes[16];
	
	int VolumeControllerChange(int channel, int volume);
	void SetTempo(int new_tempo);
	int ClampLoopCount(int loopcount);

	
public:
	bool Exporting = false;

	// Virtuals for subclasses to override
	virtual ~MIDISource() {}
	virtual void CheckCaps(int tech);
	virtual void DoInitialSetup() = 0;
	virtual void DoRestart() = 0;
	virtual bool CheckDone() = 0;
	virtual std::vector<uint16_t> PrecacheData();
	virtual bool SetMIDISubsong(int subsong);
	virtual uint32_t *MakeEvents(uint32_t *events, uint32_t *max_event_p, uint32_t max_time) = 0;
	
	void StartPlayback(bool looped = true, int looplimit = 0)
	{
		Tempo = InitialTempo;
		LoopLimit = looplimit;
		isLooping = looped;
	}

	void SkipSysex() { skipSysex = true; }
	
	bool isValid() const { return Division > 0; }
	int getDivision() const { return Division; }
	int getInitialTempo() const { return InitialTempo; }
	int getTempo() const { return Tempo; }
	int getChannelVolume(int ch) const { return ChannelVolumes[ch]; }
	void setVolume(int vol) { Volume = vol; }
	void setLoopLimit(int lim) { LoopLimit = lim; }
	void setTempoCallback(std::function<bool(int)> cb)
	{
		TempoCallback = cb;
	}
	
	void CreateSMF(std::vector<uint8_t> &file, int looplimit);

};

// MUS file played with a MIDI stream ---------------------------------------

class MUSSong2 : public MIDISource
{
public:
	MUSSong2(const uint8_t *data, size_t len);
	
protected:
	void DoInitialSetup() override;
	void DoRestart() override;
	bool CheckDone() override;
	std::vector<uint16_t> PrecacheData() override;
	uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
	
private:
	std::vector<uint8_t> MusData;
	uint8_t* MusBuffer;
	uint8_t LastVelocity[16];
	size_t MusP, MaxMusP;
};


// MIDI file played with a MIDI stream --------------------------------------

class MIDISong2 : public MIDISource
{
public:
	MIDISong2(const uint8_t* data, size_t len);
	
protected:
	void CheckCaps(int tech) override;
	void DoInitialSetup() override;
	void DoRestart() override;
	bool CheckDone() override;
	uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
	
private:
	void AdvanceTracks(uint32_t time);
	
	struct TrackInfo;
	
	void ProcessInitialMetaEvents ();
	uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
	TrackInfo *FindNextDue ();
	
	std::vector<uint8_t> MusHeader;
	std::vector<TrackInfo> Tracks;
	TrackInfo *TrackDue;
	int NumTracks;
	int Format;
	uint16_t DesignationMask;
};

// HMI file played with a MIDI stream ---------------------------------------

struct AutoNoteOff
{
	uint32_t Delay;
	uint8_t Channel, Key;
};
// Sorry, std::priority_queue, but I want to be able to modify the contents of the heap.
class NoteOffQueue : public std::vector<AutoNoteOff>
{
public:
	void AddNoteOff(uint32_t delay, uint8_t channel, uint8_t key);
	void AdvanceTime(uint32_t time);
	bool Pop(AutoNoteOff &item);
	
protected:
	void Heapify();
	
	unsigned int Parent(unsigned int i) const { return (i + 1u) / 2u - 1u; }
	unsigned int Left(unsigned int i) const { return (i + 1u) * 2u - 1u; }
	unsigned int Right(unsigned int i) const { return (i + 1u) * 2u; }
};

class HMISong : public MIDISource
{
public:
	HMISong(const uint8_t* data, size_t len);
	
protected:
	
	void DoInitialSetup() override;
	void DoRestart() override;
	bool CheckDone() override;
	void CheckCaps(int tech) override;
	uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
	
private:
	void SetupForHMI(int len);
	void SetupForHMP(int len);
	void AdvanceTracks(uint32_t time);
	
	struct TrackInfo;
	
	void ProcessInitialMetaEvents ();
	uint32_t *SendCommand (uint32_t *event, TrackInfo *track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
	TrackInfo *FindNextDue ();
	
	static uint32_t ReadVarLenHMI(TrackInfo *);
	static uint32_t ReadVarLenHMP(TrackInfo *);
	
	std::vector<uint8_t> MusHeader;
	int NumTracks;
	std::vector<TrackInfo> Tracks;
	TrackInfo *TrackDue;
	TrackInfo *FakeTrack;
	uint32_t (*ReadVarLen)(TrackInfo *);
	NoteOffQueue NoteOffs;
};

// XMI file played with a MIDI stream ---------------------------------------

class XMISong : public MIDISource
{
public:
	XMISong(const uint8_t* data, size_t len);
	
protected:
	bool SetMIDISubsong(int subsong) override;
	void DoInitialSetup() override;
	void DoRestart() override;
	bool CheckDone() override;
	uint32_t *MakeEvents(uint32_t *events, uint32_t *max_events_p, uint32_t max_time) override;
	
private:
	struct TrackInfo;
	enum EventSource { EVENT_None, EVENT_Real, EVENT_Fake };
	
	int FindXMIDforms(const uint8_t *chunk, int len, TrackInfo *songs) const;
	void FoundXMID(const uint8_t *chunk, int len, TrackInfo *song) const;
	void AdvanceSong(uint32_t time);
	
	void ProcessInitialMetaEvents();
	uint32_t *SendCommand (uint32_t *event, EventSource track, uint32_t delay, ptrdiff_t room, bool &sysex_noroom);
	EventSource FindNextDue();
	
	std::vector<uint8_t> MusHeader;
	int NumSongs;
	std::vector<TrackInfo> Songs;
	TrackInfo *CurrSong;
	NoteOffQueue NoteOffs;
	EventSource EventDue;
};



#endif /* midisources_h */