// ****************************************************************
// Copyright 2007, Charlie Poole
// This is free software licensed under the NUnit license. You may
// obtain a copy of the license at http://nunit.org/?p=license&r=2.4
// ****************************************************************

using System;
using System.IO;
using System.Collections;

namespace NUnit.Framework.Constraints
{
	/// <summary>
	/// The Constraint class is the base of all built-in or
	/// user-defined constraints in NUnit. It provides the operator
	/// overloads used to combine constraints.
	/// </summary>
    public abstract class Constraint
    {
        #region UnsetObject Class
        /// <summary>
        /// Class used to detect any derived constraints
        /// that fail to set the actual value in their
        /// Matches override.
        /// </summary>
        private class UnsetObject
        {
            public override string ToString()
            {
                return "UNSET";
            }
        }
        #endregion

		#region Static and Instance Fields
        /// <summary>
        /// Static UnsetObject used to detect derived constraints
        /// failing to set the actual value.
        /// </summary>
        protected static object UNSET = new UnsetObject();

		/// <summary>
		/// If true, all string comparisons will ignore case
		/// </summary>
		protected bool caseInsensitive;

        /// <summary>
        /// If true, strings in error messages will be clipped
        /// </summary>
        protected bool clipStrings = true;

		/// <summary>
		/// If true, arrays will be treated as collections, allowing
		/// those of different dimensions to be compared
		/// </summary>
		protected bool compareAsCollection;

		/// <summary>
		/// If non-zero, equality comparisons within the specified 
		/// tolerance will succeed.
		/// </summary>
		protected object tolerance;

        /// <summary>
        /// IComparer object used in comparisons for some constraints.
        /// </summary>
        protected IComparer compareWith;

		/// <summary>
        /// The actual value being tested against a constraint
        /// </summary>
        protected object actual = UNSET;
        #endregion

        #region Properties
        /// <summary>
		/// Flag the constraint to ignore case and return self.
		/// </summary>
		public virtual Constraint IgnoreCase
		{
			get
			{
				caseInsensitive = true;
				return this;
			}
		}

        /// <summary>
        /// Flag the constraint to suppress string clipping 
        /// and return self.
        /// </summary>
        public Constraint NoClip
        {
            get
            {
                clipStrings = false;
                return this;
            }
        }

		/// <summary>
		/// Flag the constraint to compare arrays as collections
		/// and return self.
		/// </summary>
		public Constraint AsCollection
		{
			get
			{
				compareAsCollection = true;
				return this;
			}
		}

        /// <summary>
        /// Flag the constraint to use a tolerance when determining equality.
        /// Currently only used for doubles and floats.
        /// </summary>
        /// <param name="tolerance">Tolerance to be used</param>
        /// <returns>Self.</returns>
        public Constraint Within(object tolerance)
		{
			this.tolerance = tolerance;
			return this;
		}

        /// <summary>
        /// Flag the constraint to use the supplied IComparer object.
        /// </summary>
        /// <param name="comparer">The IComparer object to use.</param>
        /// <returns>Self.</returns>
        public Constraint Comparer(IComparer comparer)
        {
            this.compareWith = comparer;
            return this;
        }
		#endregion

		#region Public Methods
        /// <summary>
        /// Write the failure message to the MessageWriter provided
        /// as an argument. The default implementation simply passes
        /// the constraint and the actual value to the writer, which
        /// then displays the constraint description and the value.
        /// 
        /// Constraints that need to provide additional details,
        /// such as where the error occured can override this.
        /// </summary>
        /// <param name="writer">The MessageWriter on which to display the message</param>
        public virtual void WriteMessageTo(MessageWriter writer)
        {
            writer.DisplayDifferences(this);
        }

        /// <summary>
        /// Test whether the constraint is satisfied by a given value
        /// </summary>
        /// <param name="actual">The value to be tested</param>
        /// <returns>True for success, false for failure</returns>
        public abstract bool Matches(object actual);

        /// <summary>
        /// Write the constraint description to a MessageWriter
        /// </summary>
        /// <param name="writer">The writer on which the description is displayed</param>
        public abstract void WriteDescriptionTo(MessageWriter writer);

		/// <summary>
		/// Write the actual value for a failing constraint test to a
		/// MessageWriter. The default implementation simply writes
		/// the raw value of actual, leaving it to the writer to
		/// perform any formatting.
		/// </summary>
		/// <param name="writer">The writer on which the actual value is displayed</param>
		public virtual void WriteActualValueTo(MessageWriter writer)
		{
			writer.WriteActualValue( actual );
		}
		#endregion

        #region Operator Overloads
        /// <summary>
        /// This operator creates a constraint that is satisfied only if both 
        /// argument constraints are satisfied.
        /// </summary>
        public static Constraint operator &(Constraint left, Constraint right)
        {
            return new AndConstraint(left, right);
        }

        /// <summary>
        /// This operator creates a constraint that is satisfied if either 
        /// of the argument constraints is satisfied.
        /// </summary>
        public static Constraint operator |(Constraint left, Constraint right)
        {
            return new OrConstraint(left, right);
        }

        /// <summary>
        /// This operator creates a constraint that is satisfied if the 
        /// argument constraint is not satisfied.
        /// </summary>
        public static Constraint operator !(Constraint m)
        {
            return new NotConstraint(m == null ? new EqualConstraint(null) : m);
        }
        #endregion
	}
}