using System;
using System.Collections;

namespace AdaptiveResponse
{
	/// <summary>
	/// Possible Return values from AddResponse
	/// </summary>
	public enum Action 
	{
		/// <summary>
		/// No Action Should be taken
		/// </summary>
		None, 
		/// <summary>
		/// Take Positive Action
		/// </summary>
		PositiveReturn, 
		/// <summary>
		/// Take Negative Action
		/// </summary>
		NegativeReturn,
		/// <summary>
		/// Take Positive Action, and Change Step Size
		/// </summary>
		PStepSizeChange,
		/// <summary>
		/// Take Negative Action and Change Stpe Size
		/// </summary>
		NStepSizeChange,
		/// <summary>
		/// AR Complete
		/// </summary>
		Complete 
	};
	/// <summary>
	/// The two type of steps allowable
	/// </summary>
	public enum StepType 
	{
		/// <summary>
		/// Initial
		/// </summary>
		/// <summary>
		/// Primary Step Type (Start with, usually larger than Secondary)
		/// </summary>
		Primary,
		/// <summary>
		/// Secondary Step Type (Smaller step, for greater resolution)
		/// </summary>
		Secondary
	};

	/// <summary>
	/// The Available Adaptive Response Methods
	/// </summary>
	public enum ARMethod
	{
		/// <summary>
		/// 1 pos, 1 neg
		/// </summary>
		Fifty,
		/// <summary>
		/// 2 pos, 1 neg
		/// </summary>
		Seventy,
		/// <summary>
		/// 1 pos, 2 neg
		/// </summary>
		Thirty
	};
	
		
	/// <summary>
	/// Adaptive Response
	/// </summary>
	public class AR : object
	{
		/// <summary>
		/// The AR Method being used.
		/// Defaults to Fifty
		/// </summary>
		public ARMethod Method
		{
			get{return method;}
			set{method = value;}
		}

		/*public double StandardError
		{
			get{return standardError;}
		}*/

		/// <summary>
		/// Returns the list of boolean Responses
		/// </summary>
		public ArrayList Responses
		{
			get{return responses;}
		}

		public double standardDev;
		/// <summary>
		/// Standard Error
		/// </summary>
		
		public double standardError;
		/// <summary>
		/// private
		/// </summary>
		private ArrayList responses;
		/// <summary>
		/// private
		/// </summary>
		private ArrayList SecondaryResponses;
		/// <summary>
		/// private
		/// </summary>
		public int Reversals;
		/// <summary>
		/// private
		/// </summary>
		private StepType stepType;
		/// <summary>
		/// private
		/// </summary>
		private int Direction;
		/// <summary>
		/// private
		/// </summary>
		private ARMethod method;
		/// <summary>
		/// private
		/// </summary>
		private int ErrorCheat;
		/// <summary>
		/// Private
		/// </summary>
		private int nCount;
		public bool LastResponse;
		public int nStepSize = 5;
		public bool ForceComplete = false;
		public double StdErrVal = 1.8;
		public int PrimaryReversals = 5;
		public int NumOfReversals = 6;//
		public int Mode = 0;//0 = stop on StdErrVal, 1 = Stop on NumOfReversals
		private bool bStopable = false;  //set true on true responses
		
		
		

		/// <summary>
		/// Constructor
		/// </summary>
		public AR()
		{
			standardError = 0.0f;
			responses = new ArrayList();
			SecondaryResponses = new ArrayList();
			Reversals = 0;
			stepType = StepType.Primary;
			Direction =0;
			method = ARMethod.Fifty;
			ErrorCheat = 50;
			nCount=0;
			ForceComplete = false;
		}
		/// <summary>
		/// Adds a boolean response, and evaluates based on the response
		/// </summary>
		/// <param name="bResponse"></param>
		/// <returns>Returns an AdaptiveResponse.Action</returns>
        public Action AddResponse(bool bResponse, int nLevel)
		{
			ErrorCheat = nLevel;
			bStopable = false;
			if(ForceComplete==true)
				return Action.Complete;
			responses.Add(bResponse);
			LastResponse = bResponse;
			
			Action a;
            if(bResponse==true)
				a = TrueResponse();
			else
				a = FalseResponse();
			if(stepType==StepType.Primary&&Reversals>=PrimaryReversals)
			{
				if(a==Action.PositiveReturn)
					a=Action.PStepSizeChange;
				else if (a==Action.NegativeReturn)
					a = Action.NStepSizeChange;
				stepType = StepType.Secondary;
				Reversals = 0;
				SecondaryResponses.Clear();
			}
			else if(stepType==StepType.Secondary)
			{
				GetStandardError();
				/*if(a==Action.PositiveReturn)
				{
					ErrorCheat+=nStepSize;
				}
				else if (a==Action.NegativeReturn)
				{
					ErrorCheat-=nStepSize;
				}*/
				if(CheckStopRule()==true)
				{
					a = Action.Complete;
				}
			}
			return a;
		}

		/// <summary>
		/// Clears and resets the object
		/// </summary>
		public void Clear()
		{
			standardError = 5.0;
			responses = new ArrayList();
			SecondaryResponses.Clear();
			Reversals = 0;
			stepType = StepType.Primary;
			Direction = 0;
			ErrorCheat = 50;
			nCount = 0;
			ForceComplete = false;
		}

		public void SetMethod(int i)
		{
			if(i==1)
				this.method = ARMethod.Fifty;
			else if(i==2)
				this.method = ARMethod.Seventy;
			else if(i==3)
				this.method = ARMethod.Thirty;
		}

		private Action TrueResponse()
		{
			bStopable = true;
			//Direction 0 is null
			//Direction 1 is increasing Volume
			//Direction 2 is decreasing Volume
			/*if(Direction==0)
			{
				Direction = 2;
				return Action.PositiveReturn;
			}
			else */if(method==ARMethod.Fifty || method==ARMethod.Thirty)
				   {
					   //Doesn't matter what direction we were going
					   //Methods 1 and 3 decrease on any positive
					   //Method 2 decreases only on double positive
					   if(Direction==1)
					   {
						   GetStandardError();
						   Reversals++;
						   SecondaryResponses.Add(ErrorCheat);
					   }
					   Direction = 2;
					   nCount = 0;
					   return Action.PositiveReturn;
				   }
				   else
				   {
					   nCount++;
					   if(nCount==2)
					   {
						   //two in a row.
						   if(Direction==1)
						   {
							   Reversals++;
							   SecondaryResponses.Add(ErrorCheat);
						   }
						   Direction = 2;
						   nCount = 0;
						   return Action.PositiveReturn;
					   }
				   }
			return Action.None;
		}
		private Action FalseResponse()
		{
			/*if(Direction==0)//new
			{
				Direction = 1;
				return Action.NegativeReturn;
			}
			else*/ if(method==ARMethod.Fifty || method==ARMethod.Seventy)
			{
				//Doesn't matter what direction we were going
				//Methods 1 and 2 increase on any negative
				//Method 3 increases only on double negative
					   if(Direction==2)
					   {
						   Reversals++;
						   SecondaryResponses.Add(ErrorCheat);
					   }
				Direction = 1;
				nCount = 0;
				return Action.NegativeReturn;
			}
			else
			{
				nCount++;
				if(nCount==2)
				{
					//two in a row
					if(Direction==2)
					{
						Reversals++;
						SecondaryResponses.Add(ErrorCheat);
					}
					Direction = 1;
					nCount = 0;
					return Action.NegativeReturn;
				}
			}
			return Action.None;
		}

		private void GetStandardError()
		{
			if(SecondaryResponses.Count<=3)
			{
				standardError = 5.0f;
				return;
			}
			double XSquared = 0.0f;
			double XSum = 0.0f;
			int n = SecondaryResponses.Count;
			
			foreach(int i in SecondaryResponses)
			{
				XSquared += i*i;
				XSum += i;
			}
			double SD = Math.Sqrt((n*XSquared-(XSum*XSum))/(n*(n-1)));
			double SE = SD/(Math.Sqrt(n));
			
			this.standardDev = SD;
			standardError = SE;
		}

		private bool CheckStopRule()
		{
			if(bStopable==false)
				return false;
			if(Mode==0)//stop on Standard Error
			{
				if(standardError<=StdErrVal)
					return true;
			}
			else if(Mode==1)
			{
				if(Reversals>=NumOfReversals)
					return true;
			}
			else if(Mode==2)
			{
				if(standardError<=StdErrVal)
					return true;
				if(Reversals>=NumOfReversals)
					return true;
			}
			return false;
		}


	}
}
