/////////////////////////////////////////////////////////////////////////////
// Name:
// Purpose:		scoreplayer.cpp
// Author:      Cesar Mauri Loba (cesar at crea-si dot com)
// Modified by:
// Created:
// Copyright:   (C) 2010-11 Cesar Mauri Loba - CREA Software Systems
//
//  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 3 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, see <http://www.gnu.org/licenses/>.
/////////////////////////////////////////////////////////////////////////////

#include "scoreplayer.h"
#include "spcore/conversion.h"

#include <boost/static_assert.hpp>
#include <boost/algorithm/string.hpp>
#ifdef WIN32
// TODO: remove the following line when boost gets updated
// See: https://svn.boost.org/trac/boost/ticket/4649
#pragma warning (disable:4127)
#endif
#include <boost/tokenizer.hpp>
#include <boost/foreach.hpp>
#include <sys/timeb.h>
#include <sys/types.h>

using namespace spcore;
using namespace boost;
using namespace std;

namespace mod_score_player {

static unsigned long long GetTStamp (void)
{
	struct timeb now;
	ftime(&now);
	return (unsigned long long) now.time * 1000 + now.millitm;
}

ScorePlayerComponent::ScorePlayerComponent(const char * name, int argc, const char * argv[])
: CComponentAdapter(name, argc, argv)
, m_volume(127)		// Maximum volume default
, m_instrument(0)	// Piano default
, m_newInstrument(0)
, m_channel(0)		// Channel 0 default
, m_newChannel(0)
, m_originalChannel(0)
, m_scoreWrap(false)
, m_duration(500)
, m_runningChord(-1)
, m_lastPlayedChord(-1)
, m_timeStamp(0)
, m_chordPointer(-1.0f)
, m_progressPointer(0.0f)
, m_progressFactor(1.0f)
{
	//
	// Pins
	//

	// Input pins
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinPointer(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinProgress(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinVolume(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinInstrument(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinDuration(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinScore(*this), false));
	RegisterInputPin (*SmartPtr<IInputPin>(new InputPinWrap(*this), false));

	// Output pins
	m_oPinNote= CTypeMIDIMessage::CreateOutputPin("note");
	if (m_oPinNote.get()== NULL) throw std::runtime_error("output pin creation failed.");
	RegisterOutputPin (*m_oPinNote);
	m_oPinNotesPlayed= CTypeInt::CreateOutputPin("notes_played");
	RegisterOutputPin (*m_oPinNotesPlayed);

	// Instances
	m_note= CTypeMIDIMessage::CreateInstance();
	m_notesPlayed= CTypeInt::CreateInstance();

	//
	// Parse arguments
	//
	if (argc) {
		for (int i= 0; i< argc; ++i) {
			if (argv[i] && strcmp ("-c", argv[i])== 0) {
				// Channel
				unsigned int channel;

				++i;
				if (i< argc && argv[i] && StrToUint(argv[i], &channel) && channel<= 15) 
					m_channel= m_originalChannel= m_newChannel= (unsigned char) channel;
				else throw std::runtime_error("score_player. Wrong value for option -c");
			}			
			else if (argv[i] && strlen(argv[i])) {
				string error_msg("score_player. Unknown option:");
				error_msg+= argv[i];
				throw std::runtime_error(error_msg);
			}
		}
	}

	//
	// Add a chromatic scale for testing purposes
	for (unsigned char i= 40; i<= 50; ++i) {
		struct Chord c;
		c.notes[0]= i;
		m_score.push_back(c);
	}
	m_progressFactor= 1.0f / (float) m_score.size();
}

void ScorePlayerComponent::StopAllNotes()
{
	assert (IsInitialized());

	m_note->SetStatus(0xB, m_channel);
	m_note->SetData1(123);
	m_note->SetData2(0);
	m_oPinNote->Send(m_note);
	m_runningChord= m_lastPlayedChord= -1;
}

// Given the value of the score pointer in the range 0..1 returns
// the appropiate index for m_score vector
int ScorePlayerComponent::Pointer2Index (float pointer)
{
	// Convert to int to index the appropiate note
	int ichord= (int) ((float) (m_score.size() + 1) * pointer);
	if (ichord>= (int) m_score.size()) ichord= (int) m_score.size() - 1;
	assert (ichord>= 0 && ichord< (int) m_score.size());

	return ichord;
}

void ScorePlayerComponent::ProcessPointerUpdate (float pointer)
{
	assert (pointer>= 0.0f && pointer<= 1.0f);

	// Convert to int to index the appropiate note
	int ichord= Pointer2Index (pointer);

	// Need to stop previous chord?
	unsigned long long now= GetTStamp ();
	if (m_runningChord>= 0 && (ichord!= m_runningChord || (now - m_timeStamp)>= m_duration)) {
		for (int i= 0; i< POLYPHONY && m_score[m_runningChord].notes[i]!= -1; ++i) {
			m_note->SetNoteOff (m_channel, m_score[m_runningChord].notes[i], 127);
			m_oPinNote->Send(m_note);
		}

		m_runningChord= -1;	// Note stopped
	}

	// Score change?
	m_mutex.lock();
	if (!m_newScore.empty()) {
		StopAllNotes();
		m_score= m_newScore;
		m_newScore.clear();

		m_progressFactor= 1.0f / (float) m_score.size();
		ichord= Pointer2Index (pointer);
	}

	// Channel change
	m_channel= m_newChannel;
	m_mutex.unlock();

	// Program change?
	if (m_newInstrument!= m_instrument && m_channel!= 9) {
		m_instrument= m_newInstrument;
		m_note->SetProgramChange (m_channel, m_instrument);
		m_oPinNote->Send(m_note);
	}

	// Need to start a new note?
	if (ichord!= m_lastPlayedChord) {
		m_runningChord= m_lastPlayedChord= ichord;
		for (int i= 0; i< POLYPHONY && m_score[m_runningChord].notes[i]!= -1; ++i) {
			m_note->SetNoteOn (m_channel, m_score[m_runningChord].notes[i], m_volume);
			m_oPinNote->Send(m_note);
		}
		m_timeStamp= now;
		m_notesPlayed->setValue(1);
	}
	else 
		m_notesPlayed->setValue(0);

	// Send number of chords played
	m_oPinNotesPlayed->Send(m_notesPlayed);
}

void ScorePlayerComponent::OnPinPointer (const CTypeFloat & msg)
{
	assert (this->IsInitialized());
	if (!this->IsInitialized()) return;

	// In principle pointer should be between 0 and 1.0f, so in debug
	// mode we assert to help developers to detect the error
//	assert (msg.getValue()>= 0.0f && msg.getValue()<= 1.0f);

	// In release mode we make sure this limit is enforced
	float newPointer= fabsf (msg.getValue());
	if (newPointer> 1.0f) 
		newPointer= modff(newPointer, &newPointer);

	ProcessPointerUpdate (newPointer);
}


void ScorePlayerComponent::OnPinProgress (const CTypeFloat & msg)
{
	assert (IsInitialized());
	if (!IsInitialized()) return;

	if (!m_scoreWrap) {
		m_progressPointer+= msg.getValue() * m_progressFactor;
		if (m_progressPointer> 1.0f) m_progressPointer= 1.0f;
		else if (m_progressPointer< 0.0f) m_progressPointer= 0.0f;
	}
	else {
		m_progressPointer+= msg.getValue() * m_progressFactor;
		if (m_progressPointer< 0.0f) {
			float foo;
			m_progressPointer= 1.0f + modff(m_progressPointer, &foo);
		}
		else if (m_progressPointer> 1.0f) {
			float foo;
			m_progressPointer= modff(m_progressPointer, &foo);
		}		
	}

	ProcessPointerUpdate (m_progressPointer);
}

void ScorePlayerComponent::OnPinVolume (const CTypeInt & msg)
{
	int newVol= msg.getValue();
	if (newVol>= 0 && newVol<= 127)
		m_volume= (unsigned char) newVol;
	else
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_WARNING, "Volume out of range. Ignored.", GetName());
}

void ScorePlayerComponent::OnPinInstrument (const CTypeInt & msg)
{
	unsigned char newInstrument= (unsigned char) msg.getValue();

	if (newInstrument<= 127)
		m_newInstrument= newInstrument;
	else
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_WARNING, "Instrument out of range. Ignored.", GetName());
	
}

void ScorePlayerComponent::OnPinDuration (const CTypeInt & msg)
{
	int newDuration= msg.getValue();
	if (newDuration>= 20 && newDuration<= 10000)
		m_duration= (unsigned int) newDuration;		
	else
		getSpCoreRuntime()->LogMessage(ICoreRuntime::LOG_WARNING, "Duration out of range. Ignored.", GetName());
}


/* Parse score line. Return values:
	[0 POLYPHONY] -> number of notes found (of 0 means blank/comment line)
	-1			  -> percussion set selected
	-2			  -> line contains syntax errors
	-3			  -> line contains note values out of range
*/
static
int ParseScoreLine (string& line, std::vector<ScorePlayerComponent::Chord>& score)
{
	boost::trim(line);

	// Skip blank and comments
	if (line.empty() || line[0]== '#') return 0;

	// Tokenize
	ScorePlayerComponent::Chord chord;
	int nnotes= 0;
	char_separator<char> sep("\t ,");
	tokenizer<char_separator<char> > tok(line, sep);	
	BOOST_FOREACH(string t, tok) {
		if (t== "percussion") return -1;

		// Try to extract note
		unsigned int note;
		if (!StrToUint(t.c_str(), &note)) return -2;
		if (note> 127) return -3;

		// Store note
		chord.notes[nnotes++]= (unsigned char) note;

		if (nnotes== ScorePlayerComponent::POLYPHONY) break;
	}

	if (nnotes) score.push_back(chord);
		
	return nnotes;
}

/* Parse score. Return:
	0	-> parsing successful
	1   -> parsing successful and percussive set has been selected
	-1	-> an error has ocurred
*/
static
int ParseScore (const char* contents, std::vector<ScorePlayerComponent::Chord>& score)
{
	if (!contents) return -1;

	int result= 0;

	const char* line_beg= contents;
	const char* line_end= NULL;

	for(;;) {
		// Search line begining (skip LF, VT, FF & CR)
		while (*line_beg>= 0xA && *line_beg<= 0xD) ++line_beg;
		if (!*line_beg) break;

		// Search line end
		line_end= line_beg;
		while (*line_end && (*line_end< 0xA || *line_end> 0xD)) ++line_end;

		// Line lenght
		int len= line_end - line_beg;
		assert (len> 0);

		// Extract line
		std::string line(line_beg, len);

		// Parse line
		int retval= ParseScoreLine (line, score);
		if (retval== -1) result= 1;
		else if (retval== -2) {
			// Syntax error
			getSpCoreRuntime()->LogMessage(
				ICoreRuntime::LOG_ERROR,  
				"Syntax error while parsing score",
				ScorePlayerComponent::getTypeName());
			return -1;
		}
		else if (retval== -3) {
			getSpCoreRuntime()->LogMessage(
				ICoreRuntime::LOG_WARNING,  
				"Some notes are out of range while parsing score",
				ScorePlayerComponent::getTypeName());
		}

		line_beg= line_end;
	}

	return result;
}

void ScorePlayerComponent::OnPinScore (const CTypeString & msg)
{
	std::vector<Chord> newScore;

	int retval= ParseScore (msg.get(), newScore);

	// Check errors
	if (retval== -1) return;

	// Check that the new score provides, at least, 2 chords
	if (newScore.size()< 2) {
		getSpCoreRuntime()->LogMessage(
			ICoreRuntime::LOG_ERROR,  
			"New score has not enough chords (minimum 2)",
			ScorePlayerComponent::getTypeName());
		return;
	}

	m_mutex.lock();

	// Need to change channel?
	if (retval== 1) m_newChannel= 9;
	else m_newChannel= m_originalChannel;

	// Set new score
	m_newScore= newScore;

	m_mutex.unlock();
}

void ScorePlayerComponent::OnPinWrap (const CTypeBool & msg)
{
	m_scoreWrap= msg.getValue();
}

};	// namespace end
