/*-------------------------------------------------------*/
/* ProjectOmega                 									*/
/* Written by T.Miles												*/
/* ID: 289175															*/
/* Module:	Coordinator Class									   */
/* Handles inter process communtication and              */
/* plugin configuration                                  */
/*-------------------------------------------------------*/
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <clib/asl_protos.h>
#include <clib/intuition_protos.h>
#include <clib/cybergraphics_protos.h>
#include <clib/graphics_protos.h>
#include <libraries/dos.h>
#include <libraries/dosextens.h>
#include <libraries/asl.h>
#include <pragmas/cybergraphics_pragmas.h>
#include <exec/ports.h>
#include <workbench/startup.h>
#include <dos/dostags.h>
#include <intuition/intuition.h>
#include <cybergraphx/cybergraphics.h>

#include <iostream.h>
#include <stdio.h>
#include <stdlib.h> 

#include "misc/systemdefs.h"
#include "coordinator/coordinator.h"
#include "base/plugin.h"
#include "misc/array.h"
#include "misc/omegamsg.h"
#include "output/output.h"

Coordinator::Coordinator(){
	debug=FALSE;
	noplugins=FALSE;
	rendPort=NULL;
	scr=NULL;
	myScreen=NULL;
	oa_SetName("ProjectOmega Coordinator");
	oa_Author="T.Miles";
	oa_Version="0.01";
	scrModeID=0;	
	inputlist=NULL;
	needRenderer=FALSE;
	curTempoPlugin=-1;
}

void Coordinator::SetScreenMode(ULONG id){
	ScreenModeRequester *requester;
	ULONG oldScreenMode=scrModeID;
	scrModeID=id;
	
	if (id==0){
		requester=(ScreenModeRequester *)
					AllocAslRequestTags(ASL_ScreenModeRequest,
											  ASLSM_Screen,myScreen, 
											  TAG_DONE);
		if (requester){
	  		if(AslRequest(requester,NULL))
   			scrModeID=requester->sm_DisplayID;
  			else
   			scrModeID=oldScreenMode;
			FreeAslRequest(requester);
		}
	}
}


BOOL Coordinator::InitInput(ULONG in, ULONG i_trk, ULONG out, ULONG o_trk){
	PluginTrack *pOutTrack, *pTrack;
	CoordinatorTrack *cTrack;
	UINT n;
	UINT size=outputPlugins.GetSize();
	
	// We can only use this track if...
	// Output doesn't use the renderer or,
	// Any outputs currently used don't use the renderer
	pOutTrack=outputPlugins.GetItemAddress((UINT)out);	
	for (n=0;n<size;n++){
		if (n!=out){
			pTrack=outputPlugins.GetItemAddress(n);		
			if (pTrack->pt_Used && pTrack->pt_useRenderer && pOutTrack->pt_useRenderer){
				Error("Sorry, Can't Use More Than One","Output That Needs Renderer");
				return FALSE;
			}
		}
	}
	
 	// Check to see we haven't used the track before
 	size=coordTracks.GetSize();
 	for (n=0;n<size;n++){
 		cTrack=coordTracks.GetItemAddress(n);
 		if (cTrack->ct_Input==in && cTrack->ct_InputTrk==i_trk
 			&& cTrack->ct_Output==out && cTrack->ct_OutputTrk==o_trk){
 			Error("This Track Configuration","Is Already Setup");
 			return FALSE;
 		}
	}
			
	// Tell the appropriate input it's being used
	pTrack=inputPlugins.GetItemAddress((UINT)in);
	if (pTrack)
		pTrack->pt_Used++;
	else{
		Error("Internal Error","Invalid Input");
		return FALSE;
	}
	// If we're running then we need to tell the plugin to start
	// but only if it hasn't been started before		
	if (IsRunning() && pTrack->pt_Used==1){
		dataMessage->om_Class=OM_START;
		SendToPort(inputPlugins[in].pt_MsgPort);
	}
		
	// Do the same for the output 
	pTrack=outputPlugins.GetItemAddress((UINT)out);
	if (pTrack)
		pTrack->pt_Used++;
	else{
		Error("Internal Error","Invalid Output");
		return FALSE;
	}
	// We shouldn't get to the point where we need to tell a 2nd output to start
	// as we're only allowing 1 at a time
											
	// Add a new config track
	CoordinatorTrack newTrack={in,0,i_trk,out,0,o_trk};
	newTrack.ct_InputID=inputPlugins[in].pt_ID;
	newTrack.ct_OutputID=outputPlugins[out].pt_ID;
	coordTracks.AddItem(newTrack);
	dataMessage->om_Class=OM_INIT;
	dataMessage->om_Track=i_trk;
	dataMessage->om_EffectNo=o_trk;
	dataMessage->om_Value=(UINT)in;
	dataMessage->om_Misc=(UBYTE *)(pTrack->pt_MsgPort);
	SendToPort(inputPlugins[in].pt_MsgPort);
	return TRUE;
}

void Coordinator::InitOutputs(){
	UINT numOuts;

	numOuts=outputPlugins.GetSize();
	dataMessage->om_Class=OM_INIT;

	// Init the renderer
	if (rendPort && needRenderer){
		dataMessage->om_Scr=(Screen *)scrModeID;
		dataMessage->om_Track=numOuts;
		SendToPort(rendPort,TRUE);		// Signal the renderer and wait
		scr=dataMessage->om_Scr;		// The renderer returns the screen pointer within the msg
	}
	else{
		if (needRenderer)
			Error("Can't Initialise Renderer","Program not present?");
	}
	if (scr || !needRenderer){
		dataMessage->om_Misc=(UBYTE *)rendPort;	// Tell the outputs where the renderer is!
		for (UINT n=0;n<numOuts;n++){
			dataMessage->om_ID=n;
			// Intialise the output only if it's used
			if (outputPlugins[n].pt_Used!=0)
				SendToPort(outputPlugins[n].pt_MsgPort);
		}
	}
}

BOOL Coordinator::Start(){
	UINT n;
	UINT inSize=inputPlugins.GetSize();
	UINT outSize=outputPlugins.GetSize();
	BOOL result=TRUE;
	
	needRenderer=FALSE;
	// Check to see if we need to use the renderer by scanning through the used
	// outputs
	for (n=0;n<outSize;n++){
		if (outputPlugins[n].pt_Used!=0 && outputPlugins[n].pt_useRenderer==TRUE)
			needRenderer=TRUE;
	}
	
	if (!IsRunning()){
		if (coordTracks.GetSize()!=0){
			InitOutputs();		// Also initialises the renderer
			if (scr || !needRenderer){			// The screen might have failed
				dataMessage->om_Class=OM_START;
		
				// Tell the outputs to start (if they are being utilised)
				dataMessage->om_Tempo=oa_Tempo;
				for (n=0;n<outSize;n++){
					cout << "output " << n;
					if (outputPlugins[n].pt_Used!=0){
						cout << " started";
						SendToPort(outputPlugins[n].pt_MsgPort);
					}
					cout << endl;
				}			
				// Tell the inputs to start (if they are being utilised)
				for (n=0;n<inSize;n++){
					cout << "input " << n;
					if (inputPlugins[n].pt_Used!=0){
						cout << " started";
						SendToPort(inputPlugins[n].pt_MsgPort);
					}
					cout << endl;
				}
				oa_SetStatus(APP_STATUS_RUNNING,TRUE);
			}
			else{
				if (needRenderer){
					Error("Can't Startup!","Screen Not Available");
					result=FALSE;
				}
			}
		}
		else{
			Error("Can't StartUp!","Configure some tracks");
			result=FALSE;
		} 
	}
	return result;
}

// Send stop msg to renderer,inputs & outputs
// Only notify those plugins that are being used
BOOL Coordinator::Stop(){
	UINT n;
	
	if (IsRunning()){
		dataMessage->om_Class=OM_STOP;
		for (n=0;n<inputPlugins.GetSize();n++){
			cout << "Input " << n;
			if (inputPlugins[n].pt_Used!=0){
				SendToPort(inputPlugins[n].pt_MsgPort);	// Tell inputs to stop
				cout << " stopped";
			}
			cout << endl;
		}
		for (n=0;n<outputPlugins.GetSize();n++){
			cout << "Output " << n;
			if(outputPlugins[n].pt_Used!=0){
				cout << " stopped";
				SendToPort(outputPlugins[n].pt_MsgPort);	// Tell outputs to stop
			}
			cout << endl;
		}
		// Need to stop the renderer after the outputs so they can deallocate
		// screen buffers before screen closes
		SendToPort(rendPort);			
		oa_SetStatus(APP_STATUS_RUNNING,FALSE);	
		return TRUE;
	}
	return FALSE;
}



BOOL Coordinator::Initialise(){
	if (FindPort("ProjectOmega")){
		Error("Coordinator is already running",NULL);
		return FALSE;
	}
	else
		return OmegaApp::Initialise("ProjectOmega");
}
 
void Coordinator::UpdateInputList(){
	UINT size=inputPlugins.GetSize();
	PluginTrack *pTrk;
	
	if (inputlist)
		delete[]inputlist;
	
	inputlist=new char *[size+1];
	for (UINT n=0;n<size;n++){
		pTrk=inputPlugins.GetItemAddress(n);
		inputlist[n]=pTrk->pt_Name;
	}
	inputlist[size]=NULL;
}
	 
void Coordinator::DealWithMessages(){
	OmegaMessage *msg;
	PluginTrack newTrack;
	PluginTrack *pTrack;
	
	UINT tempo=0;
	BOOL replied=FALSE;
	
	while (msg=(OmegaMessage *)GetMsg(basePort)){
		replied=FALSE;
		switch (msg->om_Class){
		case OM_INIT:
			newTrack.pt_Name=(char *)msg->om_Misc;
			newTrack.pt_MsgPort=msg->om_Port;
			newTrack.pt_Used=FALSE;
			newTrack.pt_Tracks=msg->om_Track;
			newTrack.pt_ID=msg->om_ID;
			newTrack.pt_EffectList=msg->om_Effects;
			newTrack.pt_useRenderer=TRUE;		// Default is true
			msg->om_Scr=myScreen;
			switch(msg->om_Type){
			case OMEGAINPUT:
				inputPlugins.AddItem(newTrack);
				UpdateInputList();
				break;
			case OMEGAOUTPUT:
				newTrack.pt_useRenderer=(BOOL)msg->om_EffectNo;
				outputPlugins.AddItem(newTrack);
				break;
			case OMEGARENDERER:
				rendPort=msg->om_Port;
				break;
			}
			break;
		case OM_TEMPO:
			tempo=msg->om_Tempo;			// An input has notified us of a tempo change
			ReplyMsg((Message *)msg);	// So we need to tell all the outputs
			replied=TRUE;
			SetTempo(tempo,TRUE);
			break;
		case OM_CONFIG:
			switch (msg->om_Type){
			case OMEGAINPUT:
				pTrack=inputPlugins.GetItemAddress(msg->om_ID);
				break;
			case OMEGAOUTPUT:
				pTrack=outputPlugins.GetItemAddress(msg->om_ID);
				break;
			}
			pTrack->pt_EffectList=msg->om_Effects;
			pTrack->pt_Tracks=msg->om_Track;
			CheckConfig();
			replied=TRUE;
			break;
		case OM_RENDER:
			Error("Internal Error","Plugin Not Initialised With Renderer");
			break;
		default:
			OmegaApp::DealWithMessage(msg);
			replied=TRUE;
		}
		if (!replied)
			ReplyMsg((Message *)msg);
	}
}

void Coordinator::Quit(){
	UINT size=inputPlugins.GetSize();
	UINT n;

	dataMessage->om_Class=OM_QUIT;

	// Tell the inputs we're quiting
	for (n=0;n<size;n++){
		if (inputPlugins[n].pt_MsgPort){
			SendToPort(inputPlugins[n].pt_MsgPort);
		}
	}
	// Tell outputs we're quiting
	size=outputPlugins.GetSize();
	for (n=0;n<size;n++){
		if (outputPlugins[n].pt_MsgPort){
			SendToPort(outputPlugins[n].pt_MsgPort);
		}
	}
	// Tell the renderer we're quiting
	SendToPort(rendPort);
}


void Coordinator::MessagePlugin(ULONG p_type, ULONG number, UINT m_type){
	UINT tempo;
	// Should possibly do some checks here for valid m_types
	dataMessage->om_Class=m_type;
	dataMessage->om_ID=number;
	char plugError[30];
	
	
	switch(p_type){
	case OMEGAINPUT:
		// Don't tell the input that we're using it for tempo
		// if it's already been told
		if (m_type==OM_TEMPO){
			if (curTempoPlugin==number)
				return;
			else{
				// Tell old input we don't want it for tempo anymore
				// Nice recursive call here :)
				if (curTempoPlugin!=-1)
					MessagePlugin(OMEGAINPUT,curTempoPlugin,OM_TEMPO_STOP);
				curTempoPlugin=number;
			}
		}
		if (number<inputPlugins.GetSize())
			SendToPort(inputPlugins[number].pt_MsgPort,TRUE);
		else{
			sprintf(&plugError[0],"Invalid Input No: %d",number);
			Error("Internal Error",&plugError[0]);
		}
		break;
	case OMEGAOUTPUT:
		if (number<outputPlugins.GetSize())
			SendToPort(outputPlugins[number].pt_MsgPort,TRUE);
		else{
			sprintf(&plugError[0],"Invalid Output No: %d",number);
			Error("Internal Error",&plugError[0]);
		}
		break;
	}
	// Try extracting the tempo from the reply
	if (m_type==OM_TEMPO){
		tempo=dataMessage->om_Tempo;
		SetTempo(tempo,TRUE);
	}
}	

void Coordinator::SetTempo(UINT tempo, BOOL notify){
	UINT totOutputs=outputPlugins.GetSize();
	// If our new tempo is different then we need to tell the outputs
	if (tempo!=oa_Tempo){
		oa_Tempo=tempo;
		if (notify){
			dataMessage->om_Type=OMEGAOUTPUT;
			dataMessage->om_Class=OM_TEMPO;
			dataMessage->om_Tempo=oa_Tempo;
			for (int n=0;n<totOutputs;n++){
				SendToPort(outputPlugins[n].pt_MsgPort);
			}
		}
	}
}

void Coordinator::CancelTrack(ULONG trk){
	CoordinatorTrack *track=coordTracks.GetItemAddress(trk);
	PluginTrack *pTrack;
	UINT in, trkNo, out, effectNo;

	if (track){
		in=track->ct_Input;
		trkNo=track->ct_InputTrk;
		out=track->ct_Output;
		effectNo=track->ct_OutputTrk;
		// Reduce the usage count for the input
		pTrack=inputPlugins.GetItemAddress(in);	
		if (pTrack){
			pTrack->pt_Used--;	
			// If we're running then we need to stop the input as it's not needed
			if (IsRunning() && pTrack->pt_Used==0){
				dataMessage->om_Class=OM_STOP;
				SendToPort(inputPlugins[in].pt_MsgPort);
			}
		}
		else
			Error("Internal Error","Cancelling unkown input");							

		// Do the same for the output
		pTrack=outputPlugins.GetItemAddress(out);
		if (pTrack){
			pTrack->pt_Used--;	
			if (IsRunning() && pTrack->pt_Used==0){
				dataMessage->om_Class=OM_STOP;
				SendToPort(pTrack->pt_MsgPort);
			}											
		
			dataMessage->om_Class=OM_CANCEL;
			dataMessage->om_Track=trkNo;
			dataMessage->om_Port=pTrack->pt_MsgPort;
			dataMessage->om_Value=effectNo;
			coordTracks.DeleteItem(trk);
			SendToPort(inputPlugins[in].pt_MsgPort);
		}
		else
			Error("Internal Error","Cancelling uknown output");
	}
	else
		Error("Internal Error","Trying to Cancel Non X Track");
}
	
// Save out the plugin ID and the plugin track for each coord track
void Coordinator::SaveTracks(FILE *file){
	UINT size=coordTracks.GetSize();
	CoordinatorTrack track;
	if (file){
		for (int n=0;n<size;n++){
			track=coordTracks.GetItem(n);
			fprintf(file,"%x %d %x %d\n",
					track.ct_InputID, track.ct_InputTrk,
					track.ct_OutputID, track.ct_OutputTrk);
		}
		Report("Config File Saved",NULL);
	}
	else
		Error("Unable To Write To","Config File");
}

// Read through the config file and check each track to see if we have still got
// the plugin specified.  If so then set the track as normal and initialise
// inputs.  If not then ignore it and move on
//
// Need also to check if the plugins track is okay
UINT Coordinator::LoadTracks(FILE *file){
	UINT i_size=inputPlugins.GetSize();
	UINT o_size=outputPlugins.GetSize();
	PluginTrack *pIn, *pOut;
	UINT inID, outID;
	UINT in=i_size,out=o_size,i_trk,o_trk;
	BOOL loop=TRUE, foundIn, foundOut;
	UINT n=0;
	char plugin[20];
	
	if (file){
		while (!feof(file)){
			// Check this is a coordinator config file
			if(fscanf(file,"%x%d%x%d",&inID,&i_trk,&outID,&o_trk)==4){
				foundIn=FALSE; foundOut=FALSE;
				// See if we've got our input
				loop=TRUE; n=0;
				while(loop && n<i_size){
					pIn=inputPlugins.GetItemAddress(n);
					if (pIn->pt_ID==inID && pIn->pt_Tracks>=i_trk){
						in=n; foundIn=TRUE;
						loop=FALSE;
					}
					else
						n++;
				}
				if (loop){
					sprintf(&plugin[0],"Plugin ID: %x Track: %d",inID, i_trk);
					Error("Input Plugin or Track Not Found",&plugin[0]);		// Find a way of specifying plugin type
				}
				// See if we've got our output
				loop=TRUE;  n=0;
				while(loop && n<o_size){
					pOut=outputPlugins.GetItemAddress(n);
					if (pOut->pt_ID==outID && pOut->pt_Tracks>=o_trk){
						out=n; foundOut=TRUE;
						loop=FALSE;
					}
					else
						n++;
				}
				if (loop){
					sprintf(&plugin[0],"Plugin ID: %x Track: %d",outID, o_trk);
					Error("Output Plugin or Track Not Found",&plugin[0]);
				}
				// If we've got our plugin then initialise the input and add the track
				if (foundIn && foundOut)
					InitInput(in,i_trk,out,o_trk);
			}	// End scanf
		}	// End while
	} // End file
	else
		Error("Unable to Load Config file","Check for coordinator.cfg");
	return coordTracks.GetSize();
}

CoordinatorTrack *Coordinator::GetTrack(UINT n){
	return coordTracks.GetItemAddress(n);
}

// We need to notify the inputs involved that the track has been cancelled
void Coordinator::ResetTracks(){
	UINT size=coordTracks.GetSize();
	// Calling cancel track also deletes it from our list
	for (UINT n=0;n<size;n++)
		CancelTrack(0);
}

// Searches through the arrays to find the index of the plugin with the 
// specified id
// Returns index if ID found or -1 if not
//
LONG Coordinator::FindPluginIndex(ULONG type, ULONG id){
	PluginTrack *pTrk;
	UINT n, size;
	
	switch (type){
	case OMEGAINPUT:
		size=inputPlugins.GetSize();
		for (n=0;n<size;n++){		
			pTrk=inputPlugins.GetItemAddress(n);
			if (pTrk->pt_ID==id)
				return n;
		}
		break;
	case OMEGAOUTPUT:
		size=outputPlugins.GetSize();
		for (n=0;n<size;n++){		
			pTrk=outputPlugins.GetItemAddress(n);
			if (pTrk->pt_ID==id)
				return n;
		}
		break;
	}
	return -1;
}				

// Get the ID of a given plugin
// Returns: ID if plugin found at the index specified
//				-1 if invalid index given
UWORD Coordinator::GetPluginID(ULONG type, ULONG index){
	PluginTrack *pTrk=NULL;
	BOOL found=TRUE;
		
	switch (type){
	case OMEGAINPUT:
		if (index<inputPlugins.GetSize() && index>=0)
			pTrk=inputPlugins.GetItemAddress(index);
		else
			found=FALSE;
		break;
	case OMEGAOUTPUT:
		if (index>=0 && index<outputPlugins.GetSize())
			pTrk=outputPlugins.GetItemAddress(index);
		else
			found=FALSE;
		break;
	}
	if (found && pTrk!=NULL)
		return pTrk->pt_ID;
	else
		return -1;
}

void Coordinator::CheckConfig(){
	UINT size=coordTracks.GetSize();
	CoordinatorTrack *pCTrack;
	PluginTrack *pPTrack;
	BOOL modify=FALSE;
		
	for (UINT n=0;n<size;n++){
		pCTrack=coordTracks.GetItemAddress(n);

		pPTrack=inputPlugins.GetItemAddress(pCTrack->ct_Input);
		if (pCTrack->ct_InputTrk>pPTrack->pt_Tracks)
			modify=TRUE;
			
		pPTrack=outputPlugins.GetItemAddress(pCTrack->ct_Output);
		if (pCTrack->ct_OutputTrk>pPTrack->pt_Tracks)
			modify=TRUE;
	}	
	if (modify)
		Report("The Track Assignment Refers to ","Non-existant Plugin Track")
}