/*
Copyright (C) 2003  The Pentagram Team
Copyright (C) 2013-2022  The Exult 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

// Tab Size = 4

#ifndef XMIDINOTESTACK_H_INCLUDED
#define XMIDINOTESTACK_H_INCLUDED

#include "XMidiEvent.h"

class XMidiNoteStack {
	XMidiEvent		*notes = nullptr;		// Top of the stack
	int				polyphony = 0;
	int				max_polyphony = 0;

public:
	// Just clear it. Don't care about what's actually in it
	inline void clear() {
		notes=nullptr;
		polyphony=0;
		max_polyphony=0;
	}

	// Pops the top of the stack if its off_time is <= time (6000th of second)
	inline XMidiEvent *PopTime(uint32 time) {
		if (notes && notes->ex.note_on.note_time <= time)  {
			XMidiEvent *note = notes;
			notes = note->ex.note_on.next_note;
			note->ex.note_on.next_note = nullptr;
			polyphony--;
			return note;
		}

		return nullptr;
	}

	// Pops the top of the stack
	inline XMidiEvent *Pop() {
		if (notes)  {
			XMidiEvent *note = notes;
			notes = note->ex.note_on.next_note;
			note->ex.note_on.next_note = nullptr;
			polyphony--;
			return note;
		}

		return nullptr;
	}

	// Pops the top of the stack
	inline XMidiEvent *Remove(XMidiEvent *event) {
		XMidiEvent *prev = nullptr;
		XMidiEvent *note = notes;
		while (note) {

			if (note == event) {
				if (prev) prev->ex.note_on.next_note = note->ex.note_on.next_note;
				else notes = note->ex.note_on.next_note;
				note->ex.note_on.next_note = nullptr;
				polyphony--;
				return note;
			}
			prev = note;
			note = note->ex.note_on.next_note;
		}
		return nullptr;
	}

	// Finds the note that has same pitch and channel, and pops it
	inline XMidiEvent *FindAndPop(XMidiEvent *event) {

		XMidiEvent *prev = nullptr;
		XMidiEvent *note = notes;
		while (note) {

			if ((note->status & 0xF) == (event->status & 0xF) && note->data[0] == event->data[0]) {
				if (prev) prev->ex.note_on.next_note = note->ex.note_on.next_note;
				else notes = note->ex.note_on.next_note;
				note->ex.note_on.next_note = nullptr;
				polyphony--;
				return note;
			}
			prev = note;
			note = note->ex.note_on.next_note;
		}
		return nullptr;
	}

	// Pushes a note onto the top of the stack
	inline void Push(XMidiEvent *event) {
		event->ex.note_on.next_note = notes;
		notes = event;
		polyphony++;
		if (max_polyphony < polyphony) max_polyphony = polyphony;
	}

	inline void Push(XMidiEvent *event, uint32 time) {
		event->ex.note_on.note_time = time;
		event->ex.note_on.next_note = nullptr;

		polyphony++;
		if (max_polyphony < polyphony) max_polyphony = polyphony;

		if (!notes || time <= notes->ex.note_on.note_time) {
			event->ex.note_on.next_note = notes;
			notes = event;
		}
		else {
			XMidiEvent *prev = notes;
			while (prev) {
				XMidiEvent *note = prev->ex.note_on.next_note;

				if (!note || time <= note->ex.note_on.note_time) {
					event->ex.note_on.next_note = note;
					prev->ex.note_on.next_note = event;
					return;
				}
				prev = note;
			}
		}
	}

	// Finds the note that has same pitch and channel, and sets its after touch to our velocity
	inline void SetAftertouch(XMidiEvent *event) {

		//XMidiEvent *prev = 0;
		XMidiEvent *note = notes;
		while (note) {

			if ((note->status & 0xF) == (event->status & 0xF) && note->data[0] == event->data[0]) {
				note->ex.note_on.actualvel = event->data[1];
				return;
			}
			//prev = note;
			note = note->ex.note_on.next_note;
		}
	}

	inline XMidiEvent* GetNotes() {
		return notes;
		}

	inline int GetPolyphony() {
		return polyphony;
	}

	inline int GetMaxPolyphony() {
		return max_polyphony;
	}
};

#endif //XMIDINOTESTACK_H_INCLUDED
