using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
using Microsoft.SqlServer.Server;

namespace Dimensions
{
    [Serializable]
    [SqlUserDefinedType(Format.UserDefined,
        IsByteOrdered = true,
        IsFixedLength = true,
        MaxByteSize = 17
        )]

    public struct LDimCompact : INullable, IBinarySerialize
    {
        private decimal value;
        private string units;
        public override string ToString()
        {
            if (IsNull)
            {
                return null;
            }

            // Put your code here
            return String.Format("{0} {1}", value, units);
        }
        public bool IsNull
        {
            get
            {
                // Put your code here
                return units == null;
            }
        }
        public static LDimCompact Null
        {
            get
            {
                return new LDimCompact();
            }
        }
        public SqlDecimal Inches
        {
            get
            {
                decimal inValue = this.value;
                if (units == "ft")
                {
                    inValue *= 12;
                }

                if (units == "yd")
                {
                    inValue *= 36;
                }
                return inValue;

            }
            set
            {
                this.value = value.Value;
                units = "in";
            }
        }
        public static LDimCompact Parse(SqlString s)
        {
            if (s.IsNull || s.Value.ToLower() == "null")
                return Null;

            // regular expression to test, extract
            string fp = @"-?([0-9]+(\.[0-9]*)?|\.[0-9]+)";
            Regex vu = new Regex(@"(?<v>" + fp + @") (?<u>in|ft|yd)");

            if (!vu.IsMatch(s.Value))
            {
                throw new Exception("Bad format", null);
            }

            Match m = vu.Match(s.Value);
            LDimCompact d = new LDimCompact();

            d.units = m.Result("${u}");
            d.value = decimal.Parse(m.Result("${v}"));
            // range limited to +-10^6 inches
            decimal inches = d.Inches.Value;
            if((inches < -10E6M) || (inches > 10E6M))
            {
                throw new Exception("out of range", null);
            }
            return d;
        }
        [SqlMethod(IsMutator = true, OnNullCall = false)]
        public void SetNull()
        {
            units = null;
            value = 0;
        }
        [SqlMethod(IsMutator = true, OnNullCall = false)]
        public void ToIn()
        {
            if (units == "ft")
            {
                value *= 12;
            }

            if (units == "yd")
            {
                value *= 36;
            }

            units = "in";
        }
        [SqlFunction]
        public static LDimCompact Radius(LDimCompact X, LDimCompact Y)
        {
            if (X.IsNull || Y.IsNull)
            {
                return new LDimCompact();
            }

            Y.ToIn();
            Y.ToIn();

            LDimCompact l;

            l.value = (decimal)Math.Sqrt((double)X.value * (double)X.value + (double)Y.value * (double)Y.value);
            l.units = "in";
            return l;
        }
        [SqlFunction]
        public static SqlDecimal Angle(LDimCompact X, LDimCompact Y)
        {
            if (X.IsNull || Y.IsNull)
            {
                return SqlDecimal.Null;
            }

            if (X.value == 0.0M && Y.value == 0.0M)
            {
                return SqlDecimal.Null;
            }

            return new SqlDecimal(Math.Atan2((double)X.value, (double)Y.value));
        }
        public void Write(System.IO.BinaryWriter w)
        {
            if (IsNull)
            {
                for (int index = 0; index <= 17; index++)
                {
                    w.Write((byte)0xFF);
                }
            }
            else
            {
                LDimCompact v;

                v.value = value;
                v.units = units;
                v.ToIn();

                // serialize the approx log110 of value first
                // so you can squeeze it into a long
                // not that this order is only approximate
                UInt64 l10 = UInt64.MaxValue >> 1;

                if (v.value != 0.0M)
                {
                    if (v.value > 0.0M)
                    {
                        Int64 l = (Int64)(Math.Log10((double)v.value) * 29921268260570000.0);
                        if (l >= 0)
                        {
                            // divide by 4
                            l10 = (UInt64)l >> 2;
                            // add half of max
                            l10 += (UInt64.MaxValue >> 2) + (UInt64.MaxValue >> 1);
                            // result is in upper quarter of positive numbers
                        }
                        else
                        {
                            // divide by 2
                            l10 = (UInt64)(-l >> 2);
                            l10 += (UInt64.MaxValue >> 2);
                            // result is in lower quarter of upper half of positive numbers
                        }
                    }
                    else
                    {
                        Int64 l = (Int64)(Math.Log10(-(double)v.value) * 29921268260570000.0);

                        if (l >= 0)
                        {
                            l10 = (UInt64.MaxValue >> 1) - (UInt64)l >> 2;
                            // result is in upper quarter of lower half of positive numbers
                        }
                        else
                        {
                            l10 = (UInt64.MaxValue >> 1) - (UInt64.MaxValue >> 2) - (UInt64)(-l >> 2);
                            // result is in lower quarter of lower half of positive numbers
                        }
                    }
                }
                // serialize out the high byte first
                // ordering and sorting starts with the
                // first byte output
                for (int index = 7; index >= 0; index--)
                {
                    w.Write((byte)(l10 >> (8 * index)));
                }
                w.Write(value);
                if (units == "in")
                {
                    w.Write((byte)1);
                }

                if (units == "ft")
                {
                    w.Write((byte)2);
                }

                if (units == "yd")
                {
                    w.Write((byte)3);
                }
            }
        }
        public void Read(System.IO.BinaryReader r)
        {
            // throw order marker
            r.ReadBytes(8);
            value = r.ReadDecimal();

            byte c = r.ReadByte();

            switch (c)
            {
                case 0:
                    SetNull();
                    break;
                case 1:
                    units = "in";
                    break;

                case 2:
                    units = "ft";
                    break;

                case 3:
                    units = "yd";
                    break;
            }
        }
    }

}