using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Text.RegularExpressions;
using System.IO;
using System.Threading;
using csmic.CodedFunctions;
using csmic.Interpreter;
using csmic.ComputableEngine;
using System.Runtime.Remoting.Messaging;

// namespace: csmic
//
// summary:	.
namespace csmic
{
    /// <summary>
    /// An interpreter object that reads user input and evaluates the code.
    /// </summary>
    /// <remarks>The interpreter does not support exceptions by design. Instead, invalid 
    /// calculations, parameters, etc. will result in a result of zero.
    /// <code>
    /// InputInterpreter interpreter = new InputInterpreter();
    /// interpreter.Interpret("1/0"); // The result will be 0, not an exception.
    /// </code>
    /// </remarks>
    public class InputInterpreter
    {
        #region Members

        /// <summary>
        /// The output generated.
        /// </summary>
        private string output;
        /// <summary>
        /// The variables assigned.
        /// </summary>
        internal Dictionary<string, Variable> variables;
        /// <summary>
        /// The time for execution.
        /// </summary>
        private TimeSpan executionTime;
        /// <summary>
        /// The verbose message of the calculation.
        /// </summary>
        private string message;
        /// <summary>
        /// The list of coded functions that can be executed.
        /// </summary>
        private List<ICodedFunction> codedFunctions;
        /// <summary>
        /// The list of user defined functions that can be executed.
        /// </summary>
        private List<InterpretedFunction> interpretedFunctions;
        /// <summary>
        /// The private calculated value.
        /// </summary>
        private decimal calculatedValue;

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new InputInterpreter.
        /// </summary>
        public InputInterpreter()
        {
            this.output = string.Empty;
            this.variables = new Dictionary<string, Variable>();
            this.executionTime = new TimeSpan();
            this.codedFunctions = new List<ICodedFunction>();
            this.interpretedFunctions = new List<InterpretedFunction>();
            LoadDefaultCodedFunctions();
        }

        /// <summary>
        /// Creates a new InputInterpreter from an original.
        /// </summary>
        /// <param name="original">The orginal input interpreter to copy.</param>
        public InputInterpreter(InputInterpreter original)
        {
            this.output = original.output;
            this.variables = original.variables;
            this.executionTime = original.executionTime;
            this.codedFunctions = original.codedFunctions;
            this.interpretedFunctions = original.interpretedFunctions;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the message that represents the InputInterpreters output.
        /// </summary>
        public string Output
        {
            get
            {
                return this.output;
            }
        }

        /// <summary>
        /// Gets the verbose message that is generated with a calculation.
        /// </summary>
        public string Message
        {
            get
            {
                return this.message;
            }
        }

        /// <summary>
        /// Gets the value of the output as a decimal.
        /// </summary>
        public decimal Decimal
        {
            get
            {
                return this.calculatedValue;
            }
        }

        /// <summary>
        /// Gets the value of the output cast as an int.
        /// </summary>
        public int Int
        {
            get
            {
                return (int)decimal.Round(this.calculatedValue);
            }
        }

        /// <summary>
        /// Gets the value of the output cast as a float.
        /// </summary>
        public float Float
        {
            get
            {
                return (float)this.calculatedValue;
            }
        }

        /// <summary>
        /// Gets the value of the output cast as a double.
        /// </summary>
        public double Double
        {
            get
            {
                return (double)this.calculatedValue;
            }
        }

        /// <summary>
        /// Gets the value (cast as a long) converted to its binary equivalent.
        /// </summary>
        public string Binary
        {
            get
            {
                return Convert.ToString((long)this.calculatedValue, 2).PadLeft(64, '0');
            }
        }

        /// <summary>
        /// Gets the execution time of the last calculation.
        /// </summary>
        public TimeSpan LastExecutionTime
        {
            get
            {
                return this.executionTime;
            }
        }

        /// <summary>
        /// Gets or sets a list of coded functions that the interpreter supports.
        /// </summary>
        public List<ICodedFunction> CodedFunctions
        {
            get
            {
                return this.codedFunctions;
            }
            set
            {
                this.codedFunctions = value;
            }
        }

        /// <summary>
        /// Gets or sets a list of user generated interpreted functions that the interpreter supports.
        /// </summary>
        /// <value> The interpreted functions. </value>
        public List<InterpretedFunction> InterpretedFunctions
        {
            get
            {
                return this.interpretedFunctions;
            }
            set
            {
                this.interpretedFunctions = value;
            }
        }

        /// <summary> Gets the variables. </summary>
        /// <value> The variables. </value>
        public Dictionary<string, Variable> Variables
        {
            get
            {
                return this.variables;
            }
            set
            {
                this.variables = value;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Interprets and executes given input.
        /// </summary>
        /// <param name="input">The input to interpret and execute.</param>
        public void Interpret(string input)
        {
            if (string.IsNullOrEmpty(input))
            {
                return;
            }

            DateTime timeStart = DateTime.Now;
            this.message = string.Empty;

            UTF8Encoding encoder = new UTF8Encoding();
            Parser p = new Parser(new Scanner(new MemoryStream(encoder.GetBytes(input))));
            p.Interpreter = this;
            try
            {
                p.Parse();
                this.calculatedValue = p.CalculatedValue;
                if (p.errors.count > 0)
                {
                    ProduceOutput(this.calculatedValue, p.errors.errMsgFormat);
                }
            }
            catch (Exception e)
            {
                this.calculatedValue = 0;
                ProduceOutput(this.calculatedValue, e.Message);
            }

            DateTime timeEnd = DateTime.Now;
            this.executionTime = timeEnd - timeStart;
        }

        /// <summary>
        /// Computes an expression and returns the result as a decimal.
        /// </summary>
        /// <param name="expression">The expression to be calculated.</param>
        /// <returns>The value that was computed.</returns>
        public decimal ComputeExpression(string expression)
        {
            this.Interpret(expression);
            return this.calculatedValue;
        }

        /// <summary>
        /// Assigns a decimal value to a variable.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="value">The value of the variable.</param>
        /// <returns>True if the variable was set, false otherwise.</returns>
        internal bool Assign(string name, decimal value)
        {
            Variable v = new Variable();
            v.Type = VariableType.Decimal;
            v.Value = value;

            if (!this.variables.ContainsKey(name))
            {
                this.variables.Add(name, v);
            }
            else
            {
                this.variables[name] = v;
            }

            return true;
        }

        /// <summary>
        /// Assigns a decimal value to a variable.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="expression">The expression of the variable.</param>
        /// <returns>True if the variable was set, false otherwise.</returns>
        internal bool Assign(string name, string expression)
        {
            Variable v = new Variable();
            v.Type = VariableType.Equation;
            v.Value = expression;

            if (!this.variables.ContainsKey(name))
            {
                this.variables.Add(name, v);
            }
            else
            {
                this.variables[name] = v;
            }

            return true;
        }

        /// <summary>
        /// Assigns a decimal value to a variable.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="values">The values of the variable.</param>
        /// <returns>True if the variable was set, false otherwise.</returns>
        internal bool Assign(string name, decimal[] values)
        {
            Variable v = new Variable();
            v.Type = VariableType.Array;
            v.Value = values;

            if (!this.variables.ContainsKey(name))
            {
                this.variables.Add(name, v);
            }
            else
            {
                this.variables[name] = v;
            }

            return true;
        }

        /// <summary>
        /// Executes a function stored in the interpreter.
        /// </summary>
        /// <param name="name">The name of the function to execute.</param>
        /// <param name="args">The arguments to pass to the function.</param>
        /// <returns>The value computed from the function execution.</returns>
        internal decimal ExecuteFunction(string name, decimal[] args)
        {
            foreach (ICodedFunction codedFunction in this.codedFunctions)
            {
                if (codedFunction.FunctionName == name)
                {
                    return codedFunction.Execute(args);
                }
            }

            foreach (InterpretedFunction interpretedFunction in this.interpretedFunctions)
            {
                if (interpretedFunction.Name == name)
                {
                    string answer = interpretedFunction.Compute(args);
                    decimal parsed = 0;
                    if (decimal.TryParse(answer, out parsed))
                    {
                        return parsed;
                    }
                }
            }

            return 0;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Loads the default coded functions supported by the interpreter.
        /// </summary>
        private void LoadDefaultCodedFunctions()
        {
            this.codedFunctions.Add(new CF_Sin());
            this.codedFunctions.Add(new CF_Cos());
            this.codedFunctions.Add(new CF_Tan());
            this.codedFunctions.Add(new CF_Round());
            this.codedFunctions.Add(new CF_Sqrt());
            this.codedFunctions.Add(new CF_Abs());
            this.codedFunctions.Add(new CF_Exp());
            this.codedFunctions.Add(new CF_Log());
            this.codedFunctions.Add(new CF_Precision());
        }

        /// <summary>
        /// Produces output given a single object.
        /// </summary>
        /// <param name="output">The object representing the output.</param>
        internal void ProduceOutput(object output)
        {
            if (output is bool)
            {
                bool o = (bool)output;
                if (o)
                {
                    this.calculatedValue = 1;
                }
                else
                {
                    this.calculatedValue = 0;
                }
            }
            this.output = string.Format("{0}", output);
        }

        /// <summary>
        /// Produces output given an object and a message.
        /// </summary>
        /// <param name="output">The object representing the output.</param>
        /// <param name="message">The message to be displayed with the output.</param>
        private void ProduceOutput(object output, string message)
        {
            if (!string.IsNullOrEmpty(message))
            {
                this.output = string.Format("{0}", output);
                this.message = message;
            }
            else
            {
                ProduceOutput(output);
                this.message = string.Empty;
            }
        }

        #endregion

        #region Asynchronous

        /// <summary> Interpret an input asynchronously. </summary>
        /// <param name="input">    The input to interpret and execute. </param>
        /// <param name="callback"> The callback. </param>
        public void InterpretAsync(string input, Action<InputInterpreter> callback)
        {
            InterpretAsyncDelegate del = new InterpretAsyncDelegate(InterpretAsyncThreadingWork);
            del.BeginInvoke(input, (result) => 
            {
                AsyncResult returned = result as AsyncResult;
                if (returned != null)
                {
                    InterpretAsyncDelegate end = returned.AsyncDelegate as InterpretAsyncDelegate;
                    if (end != null)
                    {
                        InputInterpreter returnValue = end.EndInvoke(result);
                        callback(returnValue);
                    }
                }
            }, null);
        }

        /// <summary> Interpret asynchronous threading work. </summary>
        /// <param name="input">    The input to interpret and execute. </param>
        private InputInterpreter InterpretAsyncThreadingWork(string input)
        {
            Interpret(input);
            return this;
        }

        /// <summary> Interpret asynchronous delegate. </summary>
        /// <param name="input"> The input. </param>
        /// <returns> . </returns>
        private delegate InputInterpreter InterpretAsyncDelegate(string input);

        #endregion

        #region Computable

        /// <summary> Converts this object to a computable. </summary>
        /// <returns> This object as a Computable. </returns>
        public Computable ToComputable()
        {
            return new Computable(this.Decimal.ToString(), this);
        }

        /// <summary> Treats the current object as a computable and performs an action in that context. </summary>
        /// <param name="action"> The action to execute as a computable object. </param>
        /// <returns> This object as a Computable, after the given action. </returns>
        public Computable AsComputable(Func<Computable, Computable> action)
        {
            return action(this.ToComputable());
        }

        #endregion
    }
}
