/*

	Javascript emulation of thread and processes object model
	
	Requires classes.js
	
	Author: Mantzoukas Alexander
	Company: Enorasis S.A.
	Version: 1.0.0.0
	Email: alexander.mantzoukas@enorasis.com

*/

var ThreadState = new Enum(
	{
		Stopped: 0,
		Ready: 1,
		Running: 2,
		Suspended: 3,
		Locked: 4,
		Ended: 5
	}
);

var Processor = new Class();

Processor.Declare(
	{
		Tick: Static(new Property()),
		Threads: Static(new Array()),
		Processes: Static(new Array()),
		// ThreadRunNum: Static(new Property()),
		name: "",
		timer: null,
		
		Constructor: function( _name )
		{
			if (!_name) throw("Processor must have a name.");
			this.name = _name;
		},
		
		Start: function()
		{
			if (this.isRunning) return;
			this.isRunning = true;
			this.Tick.set(0);
			this.timer = setTimeout( this.name + ".NextTick()", 1 );
		},
		
		Stop: function()
		{
			clearTimeout( this.timer );
			this.isRunning = false;
		},
		
		EndProcess: function( process )
		{
			for(var i = 0; i < process.Threads.length; ++i)
			{
				for(var j = 0; j < this.Threads.length; ++j)
				{
					if (process.Threads[i] == this.Threads[j])
						this.Threads.splice(j, 1);
				}
			}
			
			for(var i = 0; i < this.Processes.length; ++i)
			{
				if (process == this.Processes[i])
					this.Processes.splice(i, 1);
			}
		},
		
		NextTick: function()
		{
			this.Tick.set( this.Tick.get() + 1 );
			for(var i = 0; i < this.Threads.length; ++i)
			{
				if ( !this.RunThread( i ) && 
					(i != this.Threads.length) )
					--i;
			}
			this.timer = setTimeout( this.name + ".NextTick()", 1 );
		},
		
		RunThread: function(i)
		{
			if (this.Threads[i].State == ThreadState.Ready &&
				!( (this.Tick.get() - this.Threads[i].TickStarted) % this.Threads[i].Process.Priority) )
			{
				var pname = this.Threads[i].Process.Name;
				this.Threads[i].State = ThreadState.Running;
				++this.Threads[i].TicksHasRun;
				++this.Threads[i].Process.TicksHasRun;
				
				this.Threads[i].Main();
				
				if (this.Threads[i].State == ThreadState.Ended)
				{
					CPU.EndProcess(this.Threads[i].Process);
					return false;
				}
				
				if (this.Threads[i].State == ThreadState.Running)
						this.Threads[i].State = ThreadState.Ready;
			}
			
			if (this.Threads[i].State == ThreadState.Suspended &&
				this.Threads[i].SuspendedTick == this.Tick.get() )
			{
				this.Threads[i].State = ThreadState.Running;
				++this.Threads[i].TicksHasRun;
				++this.Threads[i].Process.TicksHasRun;
				
				this.Threads[i].Main();
				
				if (this.Threads[i].State == ThreadState.Ended)
				{
					CPU.EndProcess(this.Threads[i].Process);
					return false;
				}
				
				if (this.Threads[i].State == ThreadState.Running)
						this.Threads[i].State = ThreadState.Ready;
			}
			
			return true;
		}
	}
);

// Global
if (!window.CPU)
{
	window.CPU = new Processor("CPU");
}

var Thread = new Class();

Thread.Declare(
	{
		TicksHasRun: 0,
		TickStarted: 0,
		State: ThreadState.Stopped,
		Process: null,
		SuspendedTick: 0,
		ThreadIndex: -1,
		
		Constructor: function( process, main )
		{
			if ( !process || !process.Type || !process.Type.Supports 
				|| (process.Type != Process && !process.Type.Supports(Process)))
				throw ("Invalid process object.");
				
			if (main) this.Main = main;
			if (!this.Main) throw ("You cannot have a thread without a main");
			
			this.Process = process;
			CPU.Threads[ CPU.Threads.length ] = this;
			this.ThreadIndex = CPU.Threads.length - 1;
			this.TickStarted = CPU.Tick.get();
		},
		
		Start: function()
		{
			this.State = ThreadState.Ready;
		},
		
		Stop: function()
		{
			this.State = ThreadState.Stopped;
		},
		
		Wait: function( ticks )
		{
			this.State = ThreadState.Suspended;
			this.SuspendedTick = CPU.Tick.get() + ticks;
		},
		
		Main: null
	}
);

var Process = new Class();

Process.Declare(
	{
		Priority: 10,
		TicksHasRun: 0,
		TickStarted: 0,
		Threads: new Array(),
		Name: null,
		ProcessIndex: -1,
		
		Constructor: function( name, main )
		{
			if (!name) throw ("Process must have a name.");
			
			if (main)
				this.Threads[ this.Threads.length ] = new Thread( this, main );
			
			CPU.Processes[ CPU.Processes.length ] = this;
			this.ProcessIndex = CPU.Processes.length - 1;
			this.TickStarted = CPU.Tick.get();
		},
		
		CreateThread: function( func )
		{
			this.Threads[ this.Threads.length ] = new Thread( this, func );
		},
		
		Start: function()
		{
			for(var i = 0; i < this.Threads.length; ++i)
				this.Threads[ i ].Start();
		},
		
		Stop: function()
		{
			for(var i = 0; i < this.Threads.length; ++i)
				this.Threads[ i ].Stop();
		},
		
		End: function()
		{
			for(var i = 0; i < this.Threads.length; ++i)
			{
				this.Threads[ i ].Stop();
				this.State = ThreadState.Ended;
			}
		}
	}
);

CPU.Start();
