//-----------------------------------------------------------------------------
// Project     : VST SDK
//
// Category    : Examples
// Filename    : public.sdk/samples/vst/hostchecker/source/eventlistcheck.cpp
// Created by  : Steinberg, 12/2012
// Description : Event List check
//
//-----------------------------------------------------------------------------
// LICENSE
// (c) 2020, Steinberg Media Technologies GmbH, All Rights Reserved
//-----------------------------------------------------------------------------
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
// 
//   * Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation 
//     and/or other materials provided with the distribution.
//   * Neither the name of the Steinberg Media Technologies nor the names of its
//     contributors may be used to endorse or promote products derived from this 
//     software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
// OF THE POSSIBILITY OF SUCH DAMAGE.
//-----------------------------------------------------------------------------

#include "eventlistcheck.h"
#include "eventlogger.h"
#include "logevents.h"
#include "pluginterfaces/vst/ivstcomponent.h"
#include "pluginterfaces/vst/ivstevents.h"

//------------------------------------------------------------------------
//    EventListCheck
//------------------------------------------------------------------------
EventListCheck::EventListCheck () : mEventLogger (nullptr), mComponent (nullptr)
{
}

//------------------------------------------------------------------------
void EventListCheck::check (Steinberg::Vst::IEventList* events)
{
	if (events)
	{
		if (!checkEventCount (events))
			mEventLogger->addLogEvent (kLogIdNumInputEventExceedsLimit);

		Steinberg::int32 lastSampleOffset = 0;
		Steinberg::Vst::TQuarterNotes lastPpqPosition = 0;
		Steinberg::int32 eventCount = events->getEventCount ();
		for (Steinberg::int32 eventIdx = 0; eventIdx < eventCount; ++eventIdx)
		{
			Steinberg::Vst::Event event = {};
			Steinberg::tresult tResult = events->getEvent (eventIdx, event);
			if (tResult != Steinberg::kResultOk)
			{
				mEventLogger->addLogEvent (kLogIdCouldNotGetAnInputEvent);
				continue;
			}

			if (event.sampleOffset < lastSampleOffset)
			{
				mEventLogger->addLogEvent (kLogIdEventsAreNotSortedBySampleOffset);
				lastSampleOffset = event.sampleOffset;
			}

			if (event.ppqPosition < lastPpqPosition)
			{
				mEventLogger->addLogEvent (kLogIdEventsAreNotSortedByPpqPosition);
				event.ppqPosition = lastPpqPosition;
			}

			checkEventProperties (event);
		}
	}
}

//------------------------------------------------------------------------
bool EventListCheck::checkEventCount (Steinberg::Vst::IEventList* events)
{
	if (events)
	{
		return events->getEventCount () >= 0 || events->getEventCount () < kMaxEvents;
	}

	return true;
}

//------------------------------------------------------------------------
void EventListCheck::checkEventProperties (const Steinberg::Vst::Event& event)
{
	//! TODO: Make this method smaller
	if (!checkEventBusIndex (event.busIndex))
	{
		mEventLogger->addLogEvent (kLogIdInvalidEventBusIndex);
		return;
	}

	if (!checkEventSampleOffset (event.sampleOffset))
	{
		mEventLogger->addLogEvent (kLogIdInvalidEventSampleOffset);
	}

	switch (event.type)
	{
		case Steinberg::Vst::Event::kNoteOnEvent:
		{
			if (!checkEventChannelIndex (event.busIndex, event.noteOn.channel))
				mEventLogger->addLogEvent (kLogIdInvalidNoteOnChannelIndex);

			if (!isNormalized (event.noteOn.velocity))
				mEventLogger->addLogEvent (kLogIdInvalidEventVelocityValue);

			if (!checkValidPitch (event.noteOn.pitch))
				mEventLogger->addLogEvent (kLogIdInvalidEventPitchValue);

			if (mNotePitches.find (event.noteOn.pitch) != mNotePitches.end ())
			{
				mEventLogger->addLogEvent (kLogIdNoteOnWithPitchAlreadyTriggered);
			}

			mNotePitches.insert (event.noteOn.pitch);

			if (event.noteOn.noteId >= 0)
			{
				if (mNoteIDs.find (event.noteOn.noteId) != mNoteIDs.end ())
					mEventLogger->addLogEvent (kLogIdNoteOnWithIdAlreadyTriggered);
			}

			mNoteIDs.insert (event.noteOn.noteId);

			break;
		}

		case Steinberg::Vst::Event::kNoteOffEvent: ///< is \ref NoteOffEvent
		{
			if (!checkEventChannelIndex (event.busIndex, event.noteOff.channel))
				mEventLogger->addLogEvent (kLogIdInvalidNoteOffChannelIndex);

			if (!isNormalized (event.noteOff.velocity))
				mEventLogger->addLogEvent (kLogIdInvalidEventVelocityValue);

			if (!checkValidPitch (event.noteOff.pitch))
				mEventLogger->addLogEvent (kLogIdInvalidEventPitchValue);

			if (mNotePitches.find (event.noteOff.pitch) == mNotePitches.end ())
			{
				mEventLogger->addLogEvent (kLogIdNoteOffWithPitchNeverTriggered);
			}

			mNotePitches.erase (event.noteOff.pitch);

			if (event.noteOff.noteId >= 0)
			{
				if (mNoteIDs.find (event.noteOff.noteId) == mNoteIDs.end ())
				{
					mEventLogger->addLogEvent (kLogIdNoteOffWithIdNeverTriggered);
				}
			}

			mNoteIDs.erase (event.noteOff.noteId);

			break;
		}

		case Steinberg::Vst::Event::kDataEvent: ///< is \ref DataEvent
		{
			break;
		}

		case Steinberg::Vst::Event::kPolyPressureEvent: ///< is \ref PolyPressureEvent
		{
			if (!checkEventChannelIndex (event.busIndex, event.polyPressure.channel))
				mEventLogger->addLogEvent (kLogIdInvalidPolyPressChannelIndex);

			break;
		}

		case Steinberg::Vst::Event::kNoteExpressionValueEvent: ///< is \ref NoteExpressionValueEvent
		{
			if (!isNormalized (event.noteExpressionValue.value))
				mEventLogger->addLogEvent (kLogIdNoteExpressValNotNormalized);

			checkNoteExpressionValueEvent (event.noteExpressionValue.typeId,
			                               event.noteExpressionValue.noteId,
			                               event.noteExpressionValue.value);
			break;
		}

		default:
		{
			mEventLogger->addLogEvent (kLogIdUnknownEventType);
			break;
		}
	}
}

//------------------------------------------------------------------------
bool EventListCheck::checkEventBusIndex (Steinberg::int32 busIndex)
{
	if (mComponent)
	{
		Steinberg::int32 busCount =
		    mComponent->getBusCount (Steinberg::Vst::kEvent, Steinberg::Vst::kInput);
		return busCount >= 0 && busIndex < busCount;
	}

	return false;
}

//------------------------------------------------------------------------
bool EventListCheck::checkEventSampleOffset (Steinberg::int32 sampleOffset)
{
	return sampleOffset >= 0 && sampleOffset < mSetup.maxSamplesPerBlock;
}

//------------------------------------------------------------------------
bool EventListCheck::checkEventChannelIndex (Steinberg::int32 busIndex,
                                             Steinberg::int32 channelIndex)
{
	if (mComponent)
	{
		Steinberg::int32 busCount =
		    mComponent->getBusCount (Steinberg::Vst::kEvent, Steinberg::Vst::kInput);
		if (busCount >= 0 && busIndex < busCount)
		{
			Steinberg::Vst::BusInfo busInfo = {};
			Steinberg::tresult tResult = mComponent->getBusInfo (
			    Steinberg::Vst::kEvent, Steinberg::Vst::kInput, busIndex, busInfo);
			if (tResult == Steinberg::kResultOk)
			{
				return channelIndex >= 0 && channelIndex < busInfo.channelCount;
			}
		}
	}

	return false;
}

//------------------------------------------------------------------------
bool EventListCheck::checkValidPitch (Steinberg::int16 pitch)
{
	return pitch >= 0 && pitch <= 127;
}

//------------------------------------------------------------------------
bool EventListCheck::isNormalized (float normVal) const
{
	return normVal >= 0. && normVal <= 1.;
}

//------------------------------------------------------------------------
void EventListCheck::checkNoteExpressionValueEvent (
    Steinberg::Vst::NoteExpressionTypeID /*type*/, Steinberg::int32 /*id*/,
    Steinberg::Vst::NoteExpressionValue exprVal) const
{
	if (!isNormalized (exprVal))
	{
		//! Todo
	}
}

//------------------------------------------------------------------------
void EventListCheck::setProcessSetup (Steinberg::Vst::ProcessSetup setup)
{
	mSetup = setup;
}

//------------------------------------------------------------------------
void EventListCheck::setEventLogger (EventLogger* eventLogger)
{
	mEventLogger = eventLogger;
}
