// System.Data.SqlTypes.SqlDecimal
//
// Authors:
//	Konstantin Triger <kostat@mainsoft.com>
//	Boris Kirzner <borisk@mainsoft.com>
//	
// (C) 2005 Mainsoft Corporation (http://www.mainsoft.com)
//

//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//

namespace System.Data.SqlTypes
{

    /**
     * <p>Title: </p>
     * <p>Description: </p>
     * <p>Copyright: Copyright (c) 2002</p>
     * <p>Company: MainSoft</p>
     * @author Pavel Sandler
     * @version 1.0
     */

    using System;

    using java.math;

    /*
    * CURRENT LIMITATIONS:
    * 1. public byte[] Data not implemented.
    * 2. public byte[] BinData not implemented.
    * 3. Precision value is ignored.
    * 4. public SqlDecimal AdjustScale(SqlDecimal n, int position) not implemented.
    * 5. public SqlDecimal ConvertToPrecScale(SqlDecimal n, int precision, int scale) not implemented.
    */


    public struct SqlDecimal : INullable
    {

        private Decimal _value;
        private bool _isNull;

        public static readonly SqlDecimal MaxValue = new SqlDecimal(Decimal.MaxValue);
        public static readonly SqlDecimal MinValue = new SqlDecimal(Decimal.MinValue);
        public static readonly int MaxPrecision = 38;
        public static readonly int MaxScale = MaxPrecision;
        public static readonly SqlDecimal Null = new SqlDecimal(true);

        private int _precision;
        private int _scale;
        

        private SqlDecimal(bool isNull)
        {
            _value = Decimal.Zero;
            _isNull = isNull;
            _precision = 38;
            _scale = 0;
        }
        /**
         * Initializes a new instance of the SqlDecimal instance using the supplied Decimal value.
         * @param value The Decimal value to be stored as a SqlDecimal instance.
         */
        public SqlDecimal(Decimal value) 
        {
            _value = value;
            _isNull = false;
            int[] bits = Decimal.GetBits(value);
            int i = bits[3] & 0xff0000;
            _scale = i >> 16;
            _precision = 38;
        }

        /**
         * Initializes a new instance of the SqlDecimal instance using the supplied double value.
         * @param value The double value to be stored as a SqlDecimal instance.
         */
        public SqlDecimal(double value) 
        { 
            _value = new Decimal(value);
            _isNull = false; 
            int[] bits = Decimal.GetBits(_value);
            int i = bits[3] & 0xff0000;
            _scale = i >> 16;
            _precision = 38;
        }

        /**
         * Initializes a new instance of the SqlDecimal instance using the supplied int value.
         * @param value The int value to be stored as a SqlDecimal instance.
         */
        public SqlDecimal(int value) 
        {
            _value = new Decimal(value);
            _isNull = false;
            int[] bits = Decimal.GetBits(_value);
            int i = bits[3] & 0xff0000;
            _scale = i >> 16;
            _precision = 38;
        }

        /**
         * Initializes a new instance of the SqlDecimal instance using the supplied long value.
         * @param value The long value to be stored as a SqlDecimal instance.
         */
        public SqlDecimal(long value) 
        {
            _value = new Decimal(value);
            _isNull = false;
            int[] bits = Decimal.GetBits(_value);
            int i = bits[3] & 0xff0000;
            _scale = i >> 16;
            _precision = 38;
        }

        
        /**
         * Indicates whether or not Value is null.
         * @return true if Value is null, otherwise false.
         */
        public bool IsNull
        {
            get
            {
                return _isNull;
            }
        }

        /**
         * Gets the value of the SqlDecimal instance.
         * @return the value of this instance
         */
        public Decimal Value
        {
            get
            {
                if(IsNull)
                    throw new SqlNullValueException();
                return _value;
            }
        }

        public byte[] BinData
        {
            get
            {
                /** @todo implement this method */
                throw new NotImplementedException();
            }
        }

        public byte[] Data
        {
            get
            {
                /** @todo implement this method */
                throw new NotImplementedException();
            }
        }

        /**
         * Indicates whether or not the Value of this SqlDecimal instance is greater than zero.
         * @return true if the Value is assigned to null, otherwise false.
         */
        public bool IsPositive
        {
            get
            {
                if (!IsNull)
                {
                    if (_value >= 0)
                        return true;

                    return false;
                }
            
                throw new SqlNullValueException("The value of this instance is null");
            }
        }

        /**
         * Gets the maximum number of digits used to represent the Value property.
         * @return The maximum number of digits used to represent the Value of this SqlDecimal instance.
         */
        public int Precision
        {
            get
            {
                return _precision;
            }
        }

        /**
         * Gets the number of decimal places to which Value is resolved.
         * @return The number of decimal places to which the Value property is resolved.
         */
        public int Scale
        {
            get
            {
                return  _precision;
            }
        }

        /**
         * The Abs member function gets the absolute value of the SqlDecimal parameter.
         * @param n A SqlDecimal instance.
         * @return A SqlDecimal instance whose Value property contains the unsigned number representing the absolute value of the SqlDecimal parameter.
         */
        public static SqlDecimal Abs(SqlDecimal n)
        {
            if (n.IsNull)
                return new SqlDecimal();

            Decimal val;

            if (n.IsPositive)
                val = n.Value;
            else
                val = Decimal.Negate(n._value);

            return new SqlDecimal(val);

        }

        /**
         * Calcuates the sum of the two SqlDecimal operators.
         * @param x A SqlDecimal instance.
         * @param y A SqlDecimal instance.
         * @return A new SqlDecimal instance whose Value property contains the sum.
         * If one of the parameters or their value is null return SqlDecimal.Null.
         */
        public static SqlDecimal Add(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlDecimal.Null;

            Decimal res = Decimal.Add(x._value, y._value);

            return new SqlDecimal(res);
        }

        public static SqlDecimal AdjustScale(SqlDecimal n, int digits, bool fround)
        {
            /** @todo find out what the logic */
            throw new NotImplementedException();
        }

        /**
         * Returns the smallest whole number greater than or equal to the specified SqlDecimal instance.
         * @param n The SqlDecimal instance for which the ceiling value is to be calculated.
         * @return A SqlDecimal representing the smallest whole number greater than or equal to the specified SqlDecimal instance.
         */
        public static SqlDecimal Ceiling(SqlDecimal n)
        {
            if (n.IsNull)
                return SqlDecimal.Null;

            double d = Math.Ceiling((double)n._value);
            return new SqlDecimal(d);
        }

        /**
         * Compares this instance to the supplied object and returns an indication of their relative values.
         * @param obj The object to compare.
         * @return A signed number indicating the relative values of the instance and the object.
         * Less than zero This instance is less than object.
         * Zero This instance is the same as object.
         * Greater than zero This instance is greater than object -or-
         * object is a null reference.
         */
        public int CompareTo(Object obj)
        {
            if (obj == null)
                return 1;

            if (obj is SqlDecimal)
            {
                SqlDecimal value = (SqlDecimal)obj;
                
                if (IsNull)
                    return -1;

                if (value.IsNull)
                    return 1;

                if (_value == value._value)
                    return 0;

                return Decimal.Compare(_value, value._value);
            }

            throw new ArgumentException("parameter obj is not SqlDecimal : " + obj.GetType().Name);


        }


        public SqlDecimal ConvertToPrecScale(SqlDecimal n, int precision, int scale)
        {
            /** @todo find out what the logic */
            throw new NotImplementedException();
        }

        /**
         * The division operator divides the first SqlDecimal operand by the second.
         * @param x A SqlDecimal instance.
         * @param y A SqlDecimal instance.
         * @return A SqlDecimal instance containing the results of the division operation.
         * If one of the parameters is null or null value - return SqlDouble.Null.
         */
        public static SqlDecimal Divide(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlDecimal.Null;

            Decimal res = Decimal.Divide(x._value, y._value);

            return new SqlDecimal(res);
        }

        public override bool Equals(Object obj)
        {
            if (obj == null)
                return false;

            if (obj is SqlDecimal)
            {
                SqlDecimal dec = (SqlDecimal)obj;

                return Decimal.Equals(_value, dec._value);
            }

            return false;
        }

        
        /**
         * Performs a logical comparison on two instances of SqlDecimal to determine if they are equal.
         * @param x A SqlDecimal instance.
         * @param y A SqlDecimal instance.
         * @return true if the two values are equal, otherwise false.
         * If one of the parameters is null or null value return SqlBoolean.Null.
         */
        public static SqlBoolean Equals(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;

            if (x.Equals(y))
                return SqlBoolean.True;

            return SqlBoolean.False;
        }

        /**
         * Rounds a specified SqlDecimal number to the next lower whole number.
         * @param n The SqlDecimal instance for which the floor value is to be calculated.
         * @return A SqlDecimal instance containing the whole number portion of this SqlDecimal instance.
         */
        public static SqlDecimal Floor(SqlDecimal n)
        {
            Decimal res = Decimal.Floor(n._value);

            return new SqlDecimal(res);
        }

        /**
         * Compares two instances of SqlDecimal to determine if the first is greater than the second.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return A SqlBoolean that is True if the first instance is greater than the second instance, otherwise False.
         * If either instance of SqlDouble is null, the Value of the SqlBoolean will be Null.
         */
        public static SqlBoolean GreaterThan(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;

            if (x.CompareTo(y) > 0)
                return SqlBoolean.True;

            return SqlBoolean.False;
        }

        /**
         * Compares two instances of SqlDecimal to determine if the first is greater than or equal to the second.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return A SqlBoolean that is True if the first instance is greaater than or equal to the second instance, otherwise False.
         * If either instance of SqlDouble is null, the Value of the SqlBoolean will be Null.
         */
        public static SqlBoolean GreaterThanOrEqual(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;

            if (x.CompareTo(y) >= 0)
                return SqlBoolean.True;

            return SqlBoolean.False;
        }

        /**
         * Compares two instances of SqlDecimal to determine if the first is less than the second.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return A SqlBoolean that is True if the first instance is less than the second instance, otherwise False.
         * If either instance of SqlDouble is null, the Value of the SqlBoolean will be Null.
         */
        public static SqlBoolean LessThan(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;

            if (x.CompareTo(y) < 0)
                return SqlBoolean.True;

            return SqlBoolean.False;
        }

        /**
         * Compares two instances of SqlDecimal to determine if the first is less than the second.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return A SqlBoolean that is True if the first instance is less than the second instance, otherwise False.
         * If either instance of SqlDouble is null, the Value of the SqlBoolean will be Null.
         */
        public static SqlBoolean LessThanOrEqual(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlBoolean.Null;

            if (x.CompareTo(y) <= 0)
                return SqlBoolean.True;

            return SqlBoolean.False;
        }

        /**
         * The multiplication operator computes the product of the two SqlDecimal operands.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return The product of the two SqlDecimal operands.
         */
        public static SqlDecimal Multiply(SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull)
                return SqlDecimal.Null;

            Decimal res = Decimal.Multiply(x._value, y._value);

            return new SqlDecimal(res);
        }

        /**
         * Compares two instances of SqlDecimal to determine if they are equal.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return A SqlBoolean that is True if the two instances are not equal or False if the two instances are equal.
         * If either instance of SqlDouble is null, the Value of the SqlBoolean will be Null.
         */
        public static SqlBoolean NotEquals(SqlDecimal x, SqlDecimal y)
        {
            SqlBoolean eVal = Equals(x, y);

            if (eVal.IsNull)
                return eVal;
            if (eVal.IsTrue)
                return SqlBoolean.False;

            return SqlBoolean.True;
        }

        /**
         * Converts the String representation of a number to its Decimal number equivalent.
         * @param s The String to be parsed.
         * @return A SqlDecimal containing the value represented by the String.
         */
        public static SqlDecimal Parse(String s)
        {
            Decimal val = Decimal.Parse(s);
            SqlDecimal retVal = new SqlDecimal(val);

            if (GreaterThan(retVal, MaxValue).IsTrue || LessThan(retVal, MinValue).IsTrue)
                throw new OverflowException("The parse of this string is overflowing : " + val);

            return retVal;

        }

        /**
         * Raises the value of the specified SqlDecimal instance to the specified exponential power.
         * @param n The SqlDecimal instance to be raised to a power.
         * @param exponent A double value indicating the power to which the number should be raised.
         * @return A SqlDecimal instance containing the results.
         */
        public static SqlDecimal Power(SqlDecimal n, double exponent)
        {
            /** @todo decide if we treat the Decimal as a double and use Math.pow() */
            
            double d = (double)n._value;

            d = java.lang.Math.pow(d, exponent);

            return new SqlDecimal(d);
        }

        /**
         * Gets the number nearest the specified SqlDecimal instance's value with the specified precision.
         * @param n The SqlDecimal instance to be rounded.
         * @param position The number of significant fractional digits (precision) in the return value.
         * @return A SqlDecimal instance containing the results of the rounding operation.
         */
        public static SqlDecimal Round(SqlDecimal n, int position)
        {
            Decimal val = Decimal.Round(n._value, position);

            return new SqlDecimal(val);
        }

        /**
         * Gets a value indicating the sign of a SqlDecimal instance's Value property.
         * @param n The SqlDecimal instance whose sign is to be evaluated.
         * @return A number indicating the sign of the SqlDecimal instance.
         */
        public static int Sign(SqlDecimal n)
        {
            if (n._value < 0)
                return -1;
            if(n._value > 0)
                return 1;
            return 0;
        }

        /**
         * The subtraction operator the second SqlDecimal operand from the first.
         * @param x A SqlDecimal instance
         * @param y A SqlDecimal instance
         * @return The results of the subtraction operation.
         */
        public static SqlDecimal Subtract(SqlDecimal x, SqlDecimal y)
        {
            Decimal val = Decimal.Subtract(x._value, y._value);
            SqlDecimal retVal = new SqlDecimal(val);

            return retVal;

        }

        /**
         * Returns the a double equal to the contents of the Value property of this instance.
         * @return The decimal representation of the Value property.
         */
        public double ToDouble()
        {
            return Decimal.ToDouble(_value);
        }

        /**
         * Converts this SqlDecimal instance to SqlBoolean.
         * @return A SqlBoolean instance whose Value will be True if the SqlDecimal instance's Value is non-zero,
         * False if the SqlDecimal is zero
         * and Null if the SqlDecimal instance is Null.
         */
        public SqlBoolean ToSqlBoolean()
        {
            if (IsNull)
                return SqlBoolean.Null;

            return new SqlBoolean(!_value.Equals(Decimal.Zero));
        }

        /**
         * Converts this SqlDecimal instance to SqlByte.
         * @return A SqlByte instance whose Value equals the Value of this SqlDouble instance.
         */
        public SqlByte ToSqlByte()
        {
            if (IsNull)
                return SqlByte.Null;

            return new SqlByte(checked((byte)_value));
        }

        /**
         * Converts this SqlDecimal instance to SqlDouble.
         * @return A SqlDouble instance whose Value equals the Value of this SqlDecimal instance.
         */
        public SqlDouble ToSqlDouble()
        {
            if (IsNull)
                return SqlDouble.Null;

            return new SqlDouble((double)_value);
        }

        /**
         * Converts this SqlDouble structure to SqlInt16.
         * @return A SqlInt16 structure whose Value equals the Value of this SqlDouble structure.
         */
        public SqlInt16 ToSqlInt16()
        {
            if (IsNull)
                return SqlInt16.Null;

            return new SqlInt16(checked((short)_value));
        }

        /**
         * Converts this SqlDouble structure to SqlInt32.
         * @return A SqlInt32 structure whose Value equals the Value of this SqlDouble structure.
         */
        public SqlInt32 ToSqlInt32()
        {
            if (IsNull)
                return SqlInt32.Null;

            return new SqlInt32(checked((int)_value));
        }

        /**
         * Converts this SqlDecimal structure to SqlInt64.
         * @return A SqlInt64 structure whose Value equals the Value of this SqlDecimal structure.
         */
        public SqlInt64 ToSqlInt64()
        {
            if (IsNull)
                return SqlInt64.Null;

            return new SqlInt64(checked((long)_value));
        }

        /**
         * Converts this SqlDecimal instance to SqlDouble.
         * @return A SqlMoney instance whose Value equals the Value of this SqlDecimal instance.
         */
        public SqlMoney ToSqlMoney()
        {
            if (IsNull)
                return SqlMoney.Null;

            return new SqlMoney(_value);
        }

        /**
         * Converts this SqlDecimal instance to SqlSingle.
         * @return A SqlSingle instance whose Value equals the Value of this SqlDecimal instance.
         */
        public SqlSingle ToSqlSingle()
        {
            if (IsNull)
                return SqlSingle.Null;

            return new SqlSingle(checked((float)_value));
        }

        /**
         * Converts this SqlDecimal structure to SqlString.
         * @return A SqlString structure whose value is a string representing the date and time contained in this SqlDecimal structure.
         */
        public SqlString ToSqlString()
        {
            return new SqlString(ToString());
        }


        public override String ToString()
        {
            if (IsNull)
                return "null";

            return _value.ToString();
        }

        /**
         * Truncates the specified SqlDecimal instance's value to the desired position.
         * @param n The SqlDecimal instance to be truncated.
         * @param position The decimal position to which the number will be truncated.
         * @return Supply a negative value for the position parameter in order to truncate the value to the corresponding positon to the left of the decimal point.
         */
        public static SqlDecimal Truncate(SqlDecimal n, int position)
        {
            if (n.IsNull)
                return n;
            
            Decimal tmp = Decimal.Round(n._value, position);

            return new SqlDecimal(tmp);
        }

        public override int GetHashCode()
        {
            return _value.GetHashCode();
        }
        
        public static SqlDecimal operator + (SqlDecimal x, SqlDecimal y)
        {
            if(x.IsNull || y.IsNull)
                return SqlDecimal.Null;

            return new SqlDecimal(x.Value + y.Value);
        }

        public static SqlDecimal operator / (SqlDecimal x, SqlDecimal y)
        {
            if(x.IsNull || y.IsNull)
                return SqlDecimal.Null;
            return new SqlDecimal (x.Value / y.Value);
        }

        public static SqlBoolean operator == (SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value == y.Value);
        }

        public static SqlBoolean operator > (SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value > y.Value);
        }

        public static SqlBoolean operator >= (SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value >= y.Value);
        }

        public static SqlBoolean operator != (SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value != y.Value);
        }

        public static SqlBoolean operator < (SqlDecimal x, SqlDecimal y)
        {

            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value < y.Value);

        }

        public static SqlBoolean operator <= (SqlDecimal x, SqlDecimal y)
        {
            if (x.IsNull || y.IsNull) 
                return SqlBoolean.Null;

            return new SqlBoolean(x.Value <= y.Value);
        }

        public static SqlDecimal operator * (SqlDecimal x, SqlDecimal y)
        {
            // adjust the scale to the smaller of the two beforehand
            if (x.Scale > y.Scale)
                x = SqlDecimal.AdjustScale(x, y.Scale - x.Scale, true);
            else if (y.Scale > x.Scale)
                y = SqlDecimal.AdjustScale(y, x.Scale - y.Scale, true);

            return new SqlDecimal(x.Value * y.Value);
        }

        public static SqlDecimal operator - (SqlDecimal x, SqlDecimal y)
        {
            if(x.IsNull || y.IsNull)
                return SqlDecimal.Null;

            return new SqlDecimal(x.Value - y.Value);
        }

        public static SqlDecimal operator - (SqlDecimal n)
        {
            if(n.IsNull)
                return n;
            return new SqlDecimal (Decimal.Negate(n.Value));
        }

        public static explicit operator SqlDecimal (SqlBoolean x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.ByteValue);
        }

        public static explicit operator Decimal (SqlDecimal n)
        {
            return n.Value;
        }

        public static explicit operator SqlDecimal (SqlDouble x)
        {
            checked 
            {
                if (x.IsNull) 
                    return Null;
                else
                    return new SqlDecimal ((decimal)x.Value);
            }
        }

        public static explicit operator SqlDecimal (SqlSingle x)
        {
            checked 
            {
                if (x.IsNull) 
                    return Null;
                else
                    return new SqlDecimal ((decimal)x.Value);
            }
        }

        public static explicit operator SqlDecimal (SqlString x)
        {
            checked 
            {
                return Parse (x.Value);
            }
        }

        public static implicit operator SqlDecimal (decimal x)
        {
            return new SqlDecimal (x);
        }

        public static implicit operator SqlDecimal (SqlByte x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.Value);
        }

        public static implicit operator SqlDecimal (SqlInt16 x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.Value);
        }

        public static implicit operator SqlDecimal (SqlInt32 x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.Value);
        }

        public static implicit operator SqlDecimal (SqlInt64 x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.Value);
        }

        public static implicit operator SqlDecimal (SqlMoney x)
        {
            if (x.IsNull) 
                return Null;
            else
                return new SqlDecimal ((decimal)x.Value);
        }

    }
}