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

namespace FormulaEditor
{
    /// <summary>
    /// Elementary binary operation
    /// </summary>
    public class ElementaryBinaryOperation : IObjectOperation, ICloneable, IDerivationOperation,
        IFormulaCreatorOperation
    {

        /// <summary>
        /// Object of char type
        /// </summary>
        public const Char CharType = (char)0;

        /// <summary>
        /// Object of sbyte type
        /// </summary>
        public const SByte SbyteType = (sbyte)0;

        /// <summary>
        /// Object of short type
        /// </summary>
        public const Int16 Int16Type = (short)0;

        /// <summary>
        /// Object of int type
        /// </summary>
        public const Int32 Int32Type = (int)0;

        /// <summary>
        /// Object of long type
        /// </summary>
        public const Int64 Int64Type = (long)0;

        /// <summary>
        /// Object of byte type
        /// </summary>
        public const Byte ByteType = (byte)0;

        /// <summary>
        /// Object of ushort type
        /// </summary>
        public const UInt16 Uint16Type = (ushort)0;

        /// <summary>
        /// Object of uint type
        /// </summary>
        public const UInt32 Uint32Type = (uint)0;

        /// <summary>
        /// Object of ulong type
        /// </summary>
        public const UInt64 Uint64Type = (ulong)0;

        /// <summary>
        /// Object of bool type
        /// </summary>
        public const Boolean BooleanType = false;

        /// <summary>
        /// Object of double type
        /// </summary>
        public const Double DoubleType = 0;

        /// <summary>
        /// Object of string type
        /// </summary>
        public const string StringType = "";

        /// <summary>
        /// Types of objects
        /// </summary>
        public static readonly object[] Types = new object[]{DoubleType, BooleanType, ByteType, Uint16Type, Uint32Type, Uint64Type,
			SbyteType, Int16Type, Int32Type, Int64Type, StringType};


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

        /// <summary>
        /// Type of operation
        /// </summary>
        private const Double a = 0;

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

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="symbol">Operation symbol</param>
        /// <param name="type">Type of operation</param>
        public ElementaryBinaryOperation(char symbol, object type)
        {
            this.symbol = symbol;
            this.type = type;
        }

        /// <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);
            for (int i = 0; i < 2; i++)
            {
                IFormulaCreatorOperation op = tree[i].Operation as IFormulaCreatorOperation;
                MathFormula f = op.CreateFormula(tree[i], level, sizes);
                MathFormula fp = null;
                if (op.OperationPriority < OperationPriority)
                {
                    fp = new MathFormula(level, sizes);
                    MathSymbol s = new BracketsSymbol();
                    s.Append(fp);
                    fp.First[0] = f;
                }
                else
                {
                    fp = f;
                }
                form.Add(fp);
                if (i == 0)
                {
                    MathSymbol s = new BinarySymbol(symbol);
                    s.Append(form);
                }
            }
            return form;
        }



        /// <summary>
        /// Operation priority
        /// </summary>
        public int OperationPriority
        {
            get
            {
                if (symbol == '*')
                {
                    return (int)ElementaryOperationPriorities.Multiply;
                }
                return (int)ElementaryOperationPriorities.PlusMinus;
            }
        }


        /// <summary>
        /// Clones itself
        /// </summary>
        /// <returns>A clone</returns>
        public object Clone()
        {
            return new ElementaryBinaryOperation(symbol, type);
        }

        /// <summary>
        /// Calculates derivation
        /// </summary>
        /// <param name="tree">The function for derivation calculation</param>
        /// <param name="s">Derivation string</param>
        /// <returns>The derivation</returns>
        public ObjectFormulaTree Derivation(ObjectFormulaTree tree, string s)
        {
            Double a = 0;
            if ((symbol == '+') | (symbol == '-'))
            {
                IObjectOperation op = new ElementaryBinaryOperation(symbol, a);
                List<ObjectFormulaTree> l = new List<ObjectFormulaTree>();
                for (int i = 0; i < tree.Count; i++)
                {
                    l.Add(DerivationPerformer.Derivation(tree[i], s));
                }
                return new ObjectFormulaTree(op, l);
            }
            ObjectFormulaTree[] der = new ObjectFormulaTree[2];
            for (int i = 0; i < 2; i++)
            {
                der[i] = DerivationPerformer.Derivation(tree[i], s);
            }
            if (symbol == '*')
            {
                List<ObjectFormulaTree> list = new List<ObjectFormulaTree>();
                for (int i = 0; i < 2; i++)
                {
                    ElementaryBinaryOperation o = new ElementaryBinaryOperation('*', a);
                    List<ObjectFormulaTree> l = new List<ObjectFormulaTree>();
                    l.Add(tree[i].Clone() as ObjectFormulaTree);
                    l.Add(der[1 - i]);
                    list.Add(new ObjectFormulaTree(o, l));
                }
                ElementaryBinaryOperation op = new ElementaryBinaryOperation('+', a);
                return new ObjectFormulaTree(op, list);
            }
            return null;
        }

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

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

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

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

        #region Specific Members

        /// <summary>
        /// Detects whether object type is integer
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if type is integer and false otherwise</returns>
        public static bool IsInteger(object o)
        {
            return (o is SByte) | (o is Byte) | (o is Int16) | (o is UInt16)
                | (o is Int32) | (o is UInt32) | (o is Int64) | (o is UInt64);
        }

        /// <summary>
        /// Detects whether object type is unsigned
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if type is unsigned and false otherwise</returns>
        public static bool IsUnsigned(object o)
        {
            return (o is Byte) | (o is UInt16) | (o is UInt32) | (o is UInt64);
        }

        /// <summary>
        /// Detects whether object type is double
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if type is double and false otherwise</returns>
        public static bool IsReal(object o)
        {
            return o is Double;
        }

        /// <summary>
        /// Detects whether object type is numercial
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if type is numercial and false otherwise</returns>
        public static bool IsNumber(object o)
        {
            return IsReal(o) | IsInteger(o);
        }

        /// <summary>
        /// Detects whether object is 64 bit integer
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if object is 64 bit integer and false otherwise</returns>
        public static bool IsInteger64(object o)
        {
            return (o is UInt64) | (o is Int64);
        }

        /// <summary>
        /// Detects whether object is negative
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>True if object is negative and false otherwise</returns>
        public static bool IsNegative(object o)
        {
            return ElementaryIntegerOperation.ToDouble(o) < 0;
        }

        /// <summary>
        /// Calculates number of object bits
        /// </summary>
        /// <param name="o">The object</param>
        /// <returns>Number of bits</returns>
        public static int NBits(object o)
        {
            if ((o is Byte) | (o is SByte))
            {
                return 8;
            }
            if ((o is Int16) | (o is UInt16))
            {
                return 16;
            }
            if ((o is Int32) | (o is UInt32))
            {
                return 32;
            }
            if ((o is Int64) | (o is UInt64))
            {
                return 64;
            }
            return 0;
        }

        public char Symbol
        {
            get
            {
                return symbol;
            }
        }


        public static object DetectType(object typeA, object typeB)
        {
            if (!IsNumber(typeA) | !IsNumber(typeB))
            {
                return null;
            }
            if (IsReal(typeA) | IsReal(typeB))
            {
                return a;
            }
            int nA = NBits(typeA);
            int nB = NBits(typeB);
            if (nA > nB)
            {
                return typeA;
            }
            if (nB > nA)
            {
                return typeB;
            }
            if (IsUnsigned(typeA) & IsUnsigned(typeB))
            {
                return typeA;
            }
            if (!IsUnsigned(typeA))
            {
                return typeA;
            }
            if (!IsUnsigned(typeB))
            {
                return typeB;
            }
            return null;
        }


        /// <summary>
        /// Calculates itself
        /// </summary>
        /// <param name="x">Arguments</param>
        /// <returns>Result of calculation</returns>
        private object calculate(object[] x)
        {
            if (IsReal(type))
            {
                double a = ElementaryIntegerOperation.ToDouble(x[0]);
                double b = ElementaryIntegerOperation.ToDouble(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is SByte)
            {
                sbyte a = ElementaryIntegerOperation.ToSByte(x[0]);
                sbyte b = ElementaryIntegerOperation.ToSByte(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is Int16)
            {
                short a = ElementaryIntegerOperation.ToInt16(x[0]);
                short b = ElementaryIntegerOperation.ToInt16(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is Int32)
            {
                int a = ElementaryIntegerOperation.ToInt32(x[0]);
                int b = ElementaryIntegerOperation.ToInt32(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is Int64)
            {
                long a = ElementaryIntegerOperation.ToInt64(x[0]);
                long b = ElementaryIntegerOperation.ToInt64(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is Byte)
            {
                byte a = ElementaryIntegerOperation.ToByte(x[0]);
                byte b = ElementaryIntegerOperation.ToByte(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is UInt16)
            {
                ushort a = ElementaryIntegerOperation.ToUInt16(x[0]);
                ushort b = ElementaryIntegerOperation.ToUInt16(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is UInt32)
            {
                uint a = ElementaryIntegerOperation.ToUInt32(x[0]);
                uint b = ElementaryIntegerOperation.ToUInt32(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            if (type is UInt64)
            {
                ulong a = ElementaryIntegerOperation.ToUInt64(x[0]);
                ulong b = ElementaryIntegerOperation.ToUInt64(x[1]);
                switch (symbol)
                {
                    case '+':
                        return a + b;
                    case '-':
                        return a - b;
                    case '*':
                        return a * b;
                }
            }
            return 0;
        }

        /// <summary>
        /// Converts double variale
        /// </summary>
        /// <param name="type">Return type</param>
        /// <param name="x">The variable</param>
        /// <returns>Coversion result</returns>
        static public object Convert(object type, double x)
        {
            if (type.Equals(DoubleType))
            {
                return x;
            }
            if (type.Equals(BooleanType))
            {
                if (x == 0)
                {
                    return false;
                }
                if (x == 1)
                {
                    return true;
                }
                throw new Exception("Can not convert double parameter value to boolean");
            }
            if (type.Equals(SbyteType))
            {
                return (sbyte)x;
            }
            if (type.Equals(Int16Type))
            {
                return (short)x;
            }
            if (type.Equals(Int32Type))
            {
                return (int)x;
            }
            if (type.Equals(Int64Type))
            {
                return (long)x;
            }
            if (type.Equals(ByteType))
            {
                return (byte)x;
            }
            if (type.Equals(Uint16Type))
            {
                return (ushort)x;
            }
            if (type.Equals(Uint32Type))
            {
                return (uint)x;
            }
            if (type.Equals(Uint64Type))
            {
                return (ulong)x;
            }
            throw new Exception("Type cannot be converted from double");
        }
        #endregion
    }
}
