Code/Context.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Expressive.Exceptions;
using Expressive.Expressions;
using Expressive.Functions;
using Expressive.Functions.Conversion;
using Expressive.Functions.Custom;
using Expressive.Functions.Date;
using Expressive.Functions.Logical;
using Expressive.Functions.Mathematical;
using Expressive.Functions.Relational;
using Expressive.Functions.Statistical;
using Expressive.Functions.String;
using Expressive.Operators;
using Expressive.Operators.Additive;
using Expressive.Operators.Bitwise;
using Expressive.Operators.Conditional;
using Expressive.Operators.Grouping;
using Expressive.Operators.Logical;
using Expressive.Operators.Multiplicative;
using Expressive.Operators.Relational;

namespace Expressive
{
    /// <summary>
    /// Represents context related details about compiling and evaluating an <see cref="IExpression"/>.
    /// </summary>
    public class Context
    {
        internal const char DateSeparator = '#';
        internal const char ParameterSeparator = ',';

        #region Fields

        // TODO: Perhaps this knowledge is better held under specific expression compilers? Or is that a level too far?
        private readonly IDictionary<string, Func<IExpression[], IDictionary<string, object>, object>> registeredFunctions;
        private readonly IDictionary<string, IOperator> registeredOperators;

        #endregion

        #region Properties

        internal ExpressiveOptions Options { get; }

        internal CultureInfo CurrentCulture { get; }

        internal CultureInfo DecimalCurrentCulture { get; }

        internal char DecimalSeparator { get; }

        /// <summary>
        /// Gets the currently registered functions described by <see cref="IFunctionMetadata"/>.
        /// </summary>
        public IEnumerable<IFunctionMetadata> RegisteredFunctions => this.registeredFunctions.Values.OfType<IFunctionMetadata>();

        /// <summary>
        /// Gets the currently registered operators described by <see cref="IOperatorMetadata"/>.
        /// </summary>
        public IEnumerable<IOperatorMetadata> RegisteredOperators => this.registeredFunctions.Values.OfType<IOperatorMetadata>();

        internal IEnumerable<string> FunctionNames => this.registeredFunctions.Keys.OrderByDescending(k => k.Length);

        internal IEnumerable<string> OperatorNames => this.registeredOperators.Keys.OrderByDescending(k => k.Length);

        private bool IsCaseInsensitiveEqualityEnabled => 
#pragma warning disable 618 // As it is our own warning this is safe enough until we actually get rid
            Options.HasFlag(ExpressiveOptions.IgnoreCase) || Options.HasFlag(ExpressiveOptions.IgnoreCaseForEquality);
#pragma warning restore 618

        internal StringComparison EqualityStringComparison => IsCaseInsensitiveEqualityEnabled
            ? StringComparison.OrdinalIgnoreCase
            : StringComparison.Ordinal;

        internal bool IsCaseInsensitiveParsingEnabled =>
#pragma warning disable 618 // As it is our own warning this is safe enough until we actually get rid
            Options.HasFlag(ExpressiveOptions.IgnoreCase) || Options.HasFlag(ExpressiveOptions.IgnoreCaseForParsing);
#pragma warning restore 618

        internal IEqualityComparer<string> ParsingStringComparer => IsCaseInsensitiveParsingEnabled
            ? StringComparer.OrdinalIgnoreCase
            : (IEqualityComparer<string>)EqualityComparer<string>.Default;

        internal StringComparison ParsingStringComparison => IsCaseInsensitiveParsingEnabled
            ? StringComparison.OrdinalIgnoreCase
            : StringComparison.Ordinal;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Context"/> class with the specified <paramref name="options"/>.
        /// </summary>
        /// <param name="options">The <see cref="ExpressiveOptions"/> to use when evaluating.</param>
        public Context(ExpressiveOptions options) : this(options, CultureInfo.CurrentCulture, CultureInfo.InvariantCulture)
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Context"/> class with the specified <paramref name="options"/>.
        /// </summary>
        /// <param name="options">The <see cref="ExpressiveOptions"/> to use when evaluating.</param>
        /// <param name="mainCurrentCulture">The <see cref="CultureInfo"/> for use in general parsing/conversions.</param>
        /// <param name="decimalCurrentCulture">The <see cref="CultureInfo"/> for use in decimal parsing/conversions.</param>
        public Context(ExpressiveOptions options, CultureInfo mainCurrentCulture, CultureInfo decimalCurrentCulture)
        {
            Options = options;

            this.CurrentCulture = mainCurrentCulture ?? throw new ArgumentNullException(nameof(mainCurrentCulture));
            // For now we will ignore any specific cultures but keeping it in a single place to simplify changing later if required.
            this.DecimalCurrentCulture = decimalCurrentCulture ?? throw new ArgumentNullException(nameof(decimalCurrentCulture));

            DecimalSeparator = Convert.ToChar(this.DecimalCurrentCulture.NumberFormat.NumberDecimalSeparator, this.DecimalCurrentCulture);
            this.registeredFunctions = new Dictionary<string, Func<IExpression[], IDictionary<string, object>, object>>(this.ParsingStringComparer);
            this.registeredOperators = new Dictionary<string, IOperator>(this.ParsingStringComparer);

            #region Operators
            // TODO: Do we allow for turning off operators?
            // Additive
            this.RegisterOperator(new PlusOperator());
            this.RegisterOperator(new SubtractOperator());
            // Bitwise
            this.RegisterOperator(new BitwiseAndOperator());
            this.RegisterOperator(new BitwiseOrOperator());
            this.RegisterOperator(new BitwiseExclusiveOrOperator());
            this.RegisterOperator(new LeftShiftOperator());
            this.RegisterOperator(new RightShiftOperator());
            // Conditional
            this.RegisterOperator(new NullCoalescingOperator());
            // Grouping
            this.RegisterOperator(new ParenthesisCloseOperator());
            this.RegisterOperator(new ParenthesisOpenOperator());
            // Logic
            this.RegisterOperator(new AndOperator());
            this.RegisterOperator(new NotOperator());
            this.RegisterOperator(new OrOperator());
            // Multiplicative
            this.RegisterOperator(new DivideOperator());
            this.RegisterOperator(new ModulusOperator());
            this.RegisterOperator(new MultiplyOperator());
            // Relational
            this.RegisterOperator(new EqualOperator());
            this.RegisterOperator(new GreaterThanOperator());
            this.RegisterOperator(new GreaterThanOrEqualOperator());
            this.RegisterOperator(new LessThanOperator());
            this.RegisterOperator(new LessThanOrEqualOperator());
            this.RegisterOperator(new NotEqualOperator());
            #endregion

            #region Functions
            // Conversion
            this.RegisterFunction(new DateFunction());
            this.RegisterFunction(new DecimalFunction());
            this.RegisterFunction(new DoubleFunction());
            this.RegisterFunction(new IntegerFunction());
            this.RegisterFunction(new LongFunction());
            this.RegisterFunction(new StringFunction());
            // Date
            this.RegisterFunction(new AddDaysFunction());
            this.RegisterFunction(new AddHoursFunction());
            this.RegisterFunction(new AddMillisecondsFunction());
            this.RegisterFunction(new AddMinutesFunction());
            this.RegisterFunction(new AddMonthsFunction());
            this.RegisterFunction(new AddSecondsFunction());
            this.RegisterFunction(new AddYearsFunction());
            this.RegisterFunction(new DayOfFunction());
            this.RegisterFunction(new DaysBetweenFunction());
            this.RegisterFunction(new HourOfFunction());
            this.RegisterFunction(new HoursBetweenFunction());
            this.RegisterFunction(new MillisecondOfFunction());
            this.RegisterFunction(new MillisecondsBetweenFunction());
            this.RegisterFunction(new MinuteOfFunction());
            this.RegisterFunction(new MinutesBetweenFunction());
            this.RegisterFunction(new MonthOfFunction());
            this.RegisterFunction(new SecondOfFunction());
            this.RegisterFunction(new SecondsBetweenFunction());
            this.RegisterFunction(new YearOfFunction());
            // Mathematical
            this.RegisterFunction(new AbsFunction());
            this.RegisterFunction(new AcosFunction());
            this.RegisterFunction(new AsinFunction());
            this.RegisterFunction(new AtanFunction());
            this.RegisterFunction(new CeilingFunction());
            this.RegisterFunction(new CosFunction());
            this.RegisterFunction(new CountFunction());
            this.RegisterFunction(new ExpFunction());
            this.RegisterFunction(new FloorFunction());
            this.RegisterFunction(new IEEERemainderFunction());
            this.RegisterFunction(new Log10Function());
            this.RegisterFunction(new LogFunction());
            this.RegisterFunction(new PowFunction());
            this.RegisterFunction(new RandomFunction());
            this.RegisterFunction(new RoundFunction());
            this.RegisterFunction(new SignFunction());
            this.RegisterFunction(new SinFunction());
            this.RegisterFunction(new SqrtFunction());
            this.RegisterFunction(new SumFunction());
            this.RegisterFunction(new TanFunction());
            this.RegisterFunction(new TruncateFunction());
            // Mathematical Constants
            this.RegisterFunction(new EFunction());
            this.RegisterFunction(new PIFunction());
            // Logical
            this.RegisterFunction(new IfFunction());
            this.RegisterFunction(new InFunction());
            // Relational
            this.RegisterFunction(new MaxFunction());
            this.RegisterFunction(new MinFunction());
            // Statistical
            this.RegisterFunction(new AverageFunction());
            this.RegisterFunction(new MeanFunction());
            this.RegisterFunction(new MedianFunction());
            this.RegisterFunction(new ModeFunction());
            // String
            this.RegisterFunction(new ContainsFunction());
            this.RegisterFunction(new EndsWithFunction());
            this.RegisterFunction(new LengthFunction());
            this.RegisterFunction(new PadLeftFunction());
            this.RegisterFunction(new PadRightFunction());
            this.RegisterFunction(new RegexFunction());
            this.RegisterFunction(new StartsWithFunction());
            this.RegisterFunction(new SubstringFunction());
            this.RegisterFunction(new ConcatFunction());
            this.RegisterFunction(new IndexOfFunction());
            // Custom
            this.RegisterFunction(new DiceFunction());
            #endregion
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Registers the supplied <paramref name="function"/> for use within compiling and evaluating an <see cref="Expression"/>.
        /// </summary>
        /// <param name="functionName">The name of the function to register.</param>
        /// <param name="function">A <see cref="Func{T}"/> to perform the function evaluation.</param>
        /// <param name="force">Whether to forcefully override any existing function.</param>
        public void RegisterFunction(string functionName, Func<IExpression[], IDictionary<string, object>, object> function, bool force = false)
        {
            this.CheckForExistingFunctionName(functionName, force);

            this.registeredFunctions[functionName] = function;
        }

        /// <summary>
        /// Registers the supplied <paramref name="function"/> for use within compiling and evaluating an <see cref="Expression"/>.
        /// </summary>
        /// <param name="function">The <see cref="IFunction"/> to perform the function evaluation.</param>
        /// <param name="force">Whether to forcefully override any existing function.</param>
        public void RegisterFunction(IFunction function, bool force = false)
        {
            if (function is null)
            {
                throw new ArgumentNullException(nameof(function));
            }

            this.RegisterFunction(
                function.Name,
                (p, a) =>
                {
                    function.Variables = a;

                    return function.Evaluate(p, this);
                },
                force);
        }

        /// <summary>
        /// Registers the supplied <paramref name="op"/> for use within compiling and evaluating an <see cref="Expression"/>.
        /// </summary>
        /// <param name="op">The <see cref="IOperator"/> implementation to register.</param>
        /// <param name="force">Whether to forcefully override any existing <see cref="IOperator"/>.</param>
        /// <remarks>
        /// Please if you are calling this with your own <see cref="IOperator"/> implementations do seriously consider raising an issue to add it in to the general framework:
        /// https://github.com/bijington/expressive
        /// </remarks>
        public void RegisterOperator(IOperator op, bool force = false)
        {
            if (op is null)
            {
                throw new ArgumentNullException(nameof(op));
            }

            foreach (var tag in op.Tags)
            {
                if (!force && this.registeredOperators.ContainsKey(tag))
                {
                    throw new OperatorNameAlreadyRegisteredException(tag);
                }

                this.registeredOperators[tag] = op;
            }
        }

        /// <summary>
        /// Removes the function from the available set of functions when evaluating. 
        /// </summary>
        /// <param name="functionName">The name of the function to remove.</param>
        public void UnregisterFunction(string functionName)
        {
            if (!this.registeredFunctions.ContainsKey(functionName))
            {
                // TODO: do we throw?
            }

            this.registeredFunctions.Remove(functionName);
        }

        /// <summary>
        /// Removes the operator from the available set of operators when evaluating. 
        /// </summary>
        /// <param name="tag">The tag of the operator to remove.</param>
        public void UnregisterOperator(string tag)
        {
            if (!this.registeredOperators.ContainsKey(tag))
            {
                // TODO: do we throw?
            }

            this.registeredOperators.Remove(tag);
        }

        #endregion

        #region Internal Methods

        internal bool TryGetFunction(string functionName, out Func<IExpression[], IDictionary<string, object>, object> value)
        {
            return this.registeredFunctions.TryGetValue(functionName, out value);
        }

        internal bool TryGetOperator(string operatorName, out IOperator value)
        {
            return this.registeredOperators.TryGetValue(operatorName, out value);
        }

        #endregion

        #region Private Methods

        private void CheckForExistingFunctionName(string functionName, bool force)
        {
            if (!force && this.registeredFunctions.ContainsKey(functionName))
            {
                throw new FunctionNameAlreadyRegisteredException(functionName);
            }
        }

        #endregion
    }
}