using System;
using System.Collections;

using Icarus.Logic.Controllers;
using Icarus.Logic.Physics;

namespace TuxHunter.Controllers
{
	/// <summary>
	/// This is basically a subcontroller that does some thing for a number of steps and then quits.
	/// </summary>
	public abstract class AbstractSubController
	{
		private int mCurrentStep = 0;
		private int mMaxSteps = 0;

		public AbstractSubController(int maxSteps)
		{
			mMaxSteps = maxSteps;
		}

		/// <summary>
		/// The number of steps to take when using this controller.
		/// </summary>
		public int MaximumSteps
		{
			get { return mMaxSteps; }
			set { mMaxSteps = value; }
		}

		/// <summary>
		/// Resets the controller to the original state.
		/// </summary>
		public virtual void Reset()
		{
			mCurrentStep = 0;
		}

		/// <summary>
		/// Updates the object and returns true if we want to do another turn of this same update, or false if we don't.
		/// </summary>
		/// <param name="obj"></param>
		/// <returns></returns>
		public bool UpdateObject(Icarus.Logic.PhysicsObject obj)
		{
			this.Update(obj);

			mCurrentStep ++;

			return mCurrentStep < mMaxSteps;
		}

		protected abstract void Update(Icarus.Logic.PhysicsObject obj);
	}

	/// <summary>
	/// This is a controller that consists of a sequence of actions. Basically, suppose we want our object to move left then blow up. We could use this compositve controller to do that.
	/// </summary>
	public class SequenceController : AbstractSubController
	{
		/// <summary>
		/// The stages of execution for the controller. Basically, a queue of subcontrollers.
		/// </summary>
		private Queue mControllerStages = new Queue();
		private IList mControllers = new ArrayList();

		public SequenceController() : base(0) { }

		/// <summary>
		/// Adds a new controller to the end of the queue for the actions to take.
		/// </summary>
		/// <param name="c"></param>
		public void AddSubController(AbstractSubController c)
		{
			mControllerStages.Enqueue(c);
			mControllers.Add(c);
			this.MaximumSteps += c.MaximumSteps;
		}

		protected override void Update(Icarus.Logic.PhysicsObject obj)
		{
			//Use top controller to update and dequeue it if it says its done:
			if (!(mControllerStages.Peek() as AbstractSubController).UpdateObject(obj))
			{
				mControllerStages.Dequeue();
                (obj as Icarus.Logic.Physics.PhysicsPlayer).stopAll();
			}
		}

		public override void Reset()
		{
			base.Reset();

			mControllerStages.Clear();

			foreach (AbstractSubController c in mControllers)
			{
				c.Reset();
				mControllerStages.Enqueue(c);
			}
		}
	}

	/// <summary>
	/// This is basically a controller that is given several child subcontrollers and the probability that each will be chosen and then chooses one of those randomly, according to the probabilities. It will chose a controller, and wait untill it is done with the action, at which point it will pick a new one. The sum of probabilities of the child subcontrollers can add up to 1. If it is less than 1, then with the remaining probability, the DoNothing controller will be chosen. If it is greater than 1... Well, that would be a bad thing. Not sure what would happen, but probably the controller that exceeds the probability will not be chosen.
	/// </summary>
	public class RandomBehaviorController: IController
	{
		private AbstractSubController mCurrentSubController = null;
		private IList mSubControllers = new ArrayList();
		private Random mRandomNumberGenerator = new Random();

		/// <summary>
		/// Adds a new subcontroller to the list of those that may be chosen.
		/// </summary>
		/// <param name="c">Controller to add.</param>
		/// <param name="probability">The probability that the controller will be chosen.</param>
		public void AddSubController(AbstractSubController c, double probability)
		{
			mSubControllers.Add(new ControllerProbabilityPair(c, probability));
		}

		#region IController Members

		public void UpdateObject(Icarus.Logic.PhysicsObject obj)
		{
			if (mCurrentSubController != null)
			{
				bool again = mCurrentSubController.UpdateObject(obj);
                if (!again) 
                {
                    mCurrentSubController = null;
                    (obj as Icarus.Logic.Physics.PhysicsPlayer).stopAll();
                }
			}

			if (mCurrentSubController == null)
			{
				PickNewController(obj);
			}
		}

		#endregion

		#region Private Parts
		private void PickNewController(Icarus.Logic.PhysicsObject obj)
		{
			double chance = mRandomNumberGenerator.NextDouble();
			double currentTotal = 0.0;

			foreach (ControllerProbabilityPair cpp in mSubControllers)
			{
				currentTotal += cpp.pProbability;

				if (chance < currentTotal)
				{
					cpp.pController.Reset();
					mCurrentSubController = cpp.pController;

					break;
				}
			}

			if (mCurrentSubController == null) 
			{
				// Still nothing... Do nothing for a while:
				mCurrentSubController = new DoNothingController(20);
			}
		}

		private class ControllerProbabilityPair
		{
			public AbstractSubController pController;
			public double pProbability;

			public ControllerProbabilityPair(AbstractSubController c, double p)
			{
				pController = c;
				pProbability = p;
			}
		}
		#endregion
	}

	/// <summary>
	/// This is a controller that has the object just sitting there doing nothing.
	/// </summary>
	public class DoNothingController: AbstractSubController
	{
		public DoNothingController(int steps) : base(steps) { }

		protected override void Update(Icarus.Logic.PhysicsObject obj) 
		{
			Icarus.Logic.Physics.PhysicsPlayer player = obj as Icarus.Logic.Physics.PhysicsPlayer;

			// Stop whatever we are doing if needed.
			if (player.ActionState != (int) PhysicsSettings.enumPlayerActionStates.StandingLeft &&
				player.ActionState != (int) PhysicsSettings.enumPlayerActionStates.StandingRight)
			{
				player.stopAll();
			}
		}
	}

	/// <summary>
	/// Has the object move left for a specified number of physics time steps.
	/// </summary>
	public class MoveLeftController: AbstractSubController
	{
		public MoveLeftController(int steps) : base(steps) { }

		protected override void Update(Icarus.Logic.PhysicsObject obj)
		{
			Icarus.Logic.Physics.PhysicsPlayer player = obj as Icarus.Logic.Physics.PhysicsPlayer;

			// Start walking if needed:
			if (player.ActionState != (int) PhysicsSettings.enumPlayerActionStates.WalkingLeft)
			{
				player.walkLeft();
			}
		}
	}

	/// <summary>
	/// Has the object move right for a specified number of physics time steps.
	/// </summary>
	public class MoveRightController: AbstractSubController
	{
		public MoveRightController(int steps) : base(steps) { }

		protected override void Update(Icarus.Logic.PhysicsObject obj)
		{
			Icarus.Logic.Physics.PhysicsPlayer player = obj as Icarus.Logic.Physics.PhysicsPlayer;

			// Start walking if needed:
			if (player.ActionState != (int) PhysicsSettings.enumPlayerActionStates.WalkingRight)
			{
				player.walkRight();
			}
		}
	}
}
