using System;
using System.Collections.Generic;
using System.Text;
using System.Collections;

namespace FormulaEditor
{
    /// <summary>
    /// Operation of elementary functions
    /// </summary>
    public class ElementaryIntegerOperation : IObjectOperation, IOperationAcceptor,
        IFormulaCreatorOperation
    {

        /// <summary>
        /// names of integer operations
        /// </summary>
        static private Hashtable intNames = new Hashtable();

        /// <summary>
        /// Type of operation
        /// </summary>
        private object type;

        /// <summary>
        /// Operation symbol
        /// </summary>
        private char symbol;


        /// <summary>
        /// Additional acceptors
        /// </summary>
        private Dictionary<object, List<IOperationAcceptor>> addAcceptors 
            = new Dictionary<object, List<IOperationAcceptor>>();



        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="symbol"></param>
        public ElementaryIntegerOperation(char symbol)
        {
            this.symbol = symbol;
            switch (symbol)
            {
                case 'A':
                    type = ElementaryBinaryOperation.ByteType;
                    break;
                case 'B':
                    type = ElementaryBinaryOperation.Uint16Type;
                    break;
                case 'C':
                    type = ElementaryBinaryOperation.Uint32Type;
                    break;
                case 'D':
                    type = ElementaryBinaryOperation.Uint64Type;
                    break;
                case 'E':
                    type = ElementaryBinaryOperation.SbyteType;
                    break;
                case 'F':
                    type = ElementaryBinaryOperation.Int16Type;
                    break;
                case 'G':
                    type = ElementaryBinaryOperation.Int32Type;
                    break;
                case 'H':
                    type = ElementaryBinaryOperation.Int64Type;
                    break;
                case 'I':
                    type = ElementaryBinaryOperation.DoubleType;
                    break;
            }
        }

        /// <summary>
        /// Adds acceptor
        /// </summary>
        /// <param name="type">Return type</param>
        /// <param name="acc">Acceptor to add</param>
        public void Add(object type, IOperationAcceptor acc)
        {
            List<IOperationAcceptor> l = null;
            if (addAcceptors.ContainsKey(type))
            {
                l = addAcceptors[type];
            }
            else
            {
                l = new List<IOperationAcceptor>();
                addAcceptors[type] = l;
            }
            l.Add(acc);
        }


        /// <summary>
        /// Creates formula
        /// </summary>
        /// <param name="tree">Operation tree</param>
        /// <param name="level">Formula level</param>
        /// <param name="sizes">Sizes of symbols</param>
        /// <returns>The formula</returns>
        public MathFormula CreateFormula(ObjectFormulaTree tree, byte level, int[] sizes)
        {
            MathFormula form = new MathFormula(level, sizes);
            MathFormula f = new MathFormula(level, sizes);
            MathSymbol sym = new SimpleSymbol(symbol, (byte)FormulaConstants.Unary, false, GetString(symbol));
            sym.Append(form);
            sym = new BracketsSymbol();
            sym.Append(form);
            form.Last[0] = FormulaCreator.CreateFormula(tree[0], level, sizes);
            return form;
        }

        /// <summary>
        /// Operation priority
        /// </summary>
        public int OperationPriority
        {
            get
            {
                return (int)ElementaryOperationPriorities.Function;
            }
        }



        /// <summary>
        /// Arity of this operation
        /// </summary>
        public int Arity
        {
            get
            {
                return 1;
            }
        }

        /// <summary>
        /// Calculates result of this operation
        /// </summary>
        public object this[object[] x]
        {
            get
            {
                return unaryValue(x[0]);
            }
        }

        /// <summary>
        /// Return type
        /// </summary>
        public object ReturnType
        {
            get
            {
                return type;
            }
        }

        /// <summary>
        /// Accepts operation
        /// </summary>
        /// <param name="type">Argument type</param>
        /// <returns>The operation</returns>
        public IObjectOperation Accept(object type)
        {
            if (!ElementaryBinaryOperation.IsNumber(type))
            {
                if (!addAcceptors.ContainsKey(type))
                {
                    return null;
                }
                List<IOperationAcceptor> l = addAcceptors[type];
                foreach (IOperationAcceptor acc in l)
                {
                    IObjectOperation op = acc.Accept(type);
                    if (op != null)
                    {
                        return op;
                    }
                }
                return null;
            }
            return this;
        }

        /// <summary>
        /// The "is powered" sign
        /// </summary>
        public bool IsPowered
        {
            get
            {
                return false;
            }
        }

        /// <summary>
        /// Preparation
        /// </summary>
        public static void Prepare()
        {
            intNames['A'] = "Byte";
            intNames['B'] = "UInt16";
            intNames['C'] = "UInt32";
            intNames['D'] = "UInt64";
            intNames['E'] = "SByte";
            intNames['F'] = "Int16";
            intNames['G'] = "Int32";
            intNames['H'] = "Int64";
            intNames['I'] = "Double";
        }

        /// <summary>
        /// Converts object to double
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static double ToDouble(object o)
        {
            if (o is decimal)
            {
                decimal dec = (decimal)o;
                return Double.Parse(dec + "");
            }
            if (o is Double)
            {
                double b = (double)o;
                return b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (double)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (double)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (double)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (double)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (double)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (double)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (double)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (double)b;
            }
            if (o is Boolean)
            {
                bool b = (bool)o;
                return b ? 1 : 0;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to byte
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion resule</returns>
        public static byte ToByte(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (byte)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (byte)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (byte)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (byte)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (byte)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (byte)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (byte)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (byte)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to ushort
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static ushort ToUInt16(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (ushort)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (ushort)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (ushort)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (ushort)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (ushort)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (ushort)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (ushort)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (ushort)b;
            }
            return 0;
        }


        /// <summary>
        /// Converts object to uint
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static uint ToUInt32(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (uint)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (uint)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (uint)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (uint)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (uint)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (uint)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (uint)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (uint)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to ulong
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static ulong ToUInt64(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (ulong)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (ulong)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (ulong)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (ulong)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (ulong)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (ulong)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (ulong)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (ulong)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to sbyte
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static sbyte ToSByte(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (sbyte)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (sbyte)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (sbyte)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (sbyte)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (sbyte)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (sbyte)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (sbyte)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (sbyte)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to short
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static short ToInt16(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (short)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (short)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (short)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (short)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (short)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (short)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (short)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (short)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to int
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static int ToInt32(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (int)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (int)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (int)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (int)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (int)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (int)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (int)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return (int)b;
            }
            return 0;
        }

        /// <summary>
        /// Converts object to long
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Conversion result</returns>
        public static long ToInt64(object o)
        {
            if (o is Double)
            {
                double b = (double)o;
                return (long)b;
            }
            if (o is Byte)
            {
                byte b = (byte)o;
                return (long)b;
            }
            if (o is UInt16)
            {
                ushort b = (ushort)o;
                return (long)b;
            }
            if (o is UInt32)
            {
                uint b = (uint)o;
                return (long)b;
            }
            if (o is UInt64)
            {
                ulong b = (ulong)o;
                return (long)b;
            }
            if (o is SByte)
            {
                sbyte b = (sbyte)o;
                return (long)b;
            }
            if (o is Int16)
            {
                short b = (short)o;
                return (long)b;
            }
            if (o is Int32)
            {
                int b = (int)o;
                return (long)b;
            }
            if (o is Int64)
            {
                long b = (long)o;
                return b;
            }
            return 0;
        }

        /// <summary>
        /// Gets function string that corresponds the symbol
        /// </summary>
        /// <param name="c">The symbol</param>
        /// <returns>The string</returns>
        public static string GetString(char c)
        {
            return intNames[c] as string;
        }

        /// <summary>
        /// Calculates value of integer operation
        /// </summary>
        /// <param name="x">the argument</param>
        /// <returns>The value</returns>
        private object unaryValue(object x)
        {
            if (type is Double)
            {
                return ToDouble(x);
            }
            if (type is SByte)
            {
                return ToSByte(x);
            }
            if (type is UInt16)
            {
                return ToUInt16(x);
            }
            if (type is UInt32)
            {
                return ToUInt32(x);
            }
            if (type is UInt64)
            {
                return ToUInt64(x);
            }
            if (type is Byte)
            {
                return ToByte(x);
            }
            if (type is Int16)
            {
                return ToInt16(x);
            }
            if (type is Int32)
            {
                return ToInt32(x);
            }
            if (type is Int64)
            {
                return ToInt64(x);
            }
            return null;
        }
    }
}
