using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
using System.Diagnostics.CodeAnalysis;

namespace Raytheon.Units
{
    [Serializable]
    [DataContract]
    public class TemperatureDelta
    {
        public enum Unit
        {
            DegreesC,
            DegreesF,
            DegreesK
        }

        public static IDictionary<Unit, List<string>> UnitAbbreviations = new Dictionary<Unit, List<string>>()
        {
            {Unit.DegreesC, new List<string>(){ "C" } },
            {Unit.DegreesF, new List<string>(){ "F" } },
            {Unit.DegreesK, new List<string>(){ "K" } },
        };

        private const Unit DefaultUnits = Unit.DegreesC;
        private const double CdegreesPerFdegree = 5.0 / 9.0;

        [DataMember(Name = "Delta", IsRequired = true)]
        private double _delta; // degrees C or K

        // map: unit => abbreviation, conversion method and property
        private static IDictionary<Unit, UnitInfo<TemperatureDelta>> _unitInfo = new Dictionary<Unit, UnitInfo<TemperatureDelta>>()
        {
            { Unit.DegreesC, new UnitInfo<TemperatureDelta>(UnitAbbreviations[Unit.DegreesC], FromDegreesC, typeof(TemperatureDelta).GetProperty("DegreesC").GetGetMethod()) },
            { Unit.DegreesF, new UnitInfo<TemperatureDelta>(UnitAbbreviations[Unit.DegreesF], FromDegreesF, typeof(TemperatureDelta).GetProperty("DegreesF").GetGetMethod()) },
            { Unit.DegreesK, new UnitInfo<TemperatureDelta>(UnitAbbreviations[Unit.DegreesK], FromDegreesK, typeof(TemperatureDelta).GetProperty("DegreesK").GetGetMethod()) }
        };

        private TemperatureDelta()
        {
            throw new InvalidOperationException("No one should be calling this, it is only here to prevent users from creating default object");
        }

        private TemperatureDelta(double delta)
        {
            _delta = delta;
        }

        #region output properties

        public double DegreesC
        {
            get
            {
                return _delta;
            }
        }

        public double DegreesF
        {
            get
            {
                return _delta / CdegreesPerFdegree;
            }
        }

        public double DegreesK
        {
            get
            {
                return _delta;
            }
        }

        #endregion

        #region creation methods

        public static TemperatureDelta FromDegreesC(double temperature)
        {
            return new TemperatureDelta(temperature);
        }

        public static TemperatureDelta FromDegreesF(double temperature)
        {
            return new TemperatureDelta(temperature * CdegreesPerFdegree);
        }

        public static TemperatureDelta FromDegreesK(double temperature)
        {
            return new TemperatureDelta(temperature);
        }

        #endregion

        // binary operators are in Temperature class
        
        #region comparison operators

        
        public static bool operator <(TemperatureDelta left, TemperatureDelta right)
        {
            if (null == left)
                throw new ArgumentNullException("left", "Cannot compare null TemperatureDelta");

            if (null == right)
                throw new ArgumentNullException("right", "Cannot compare null TemperatureDelta");

            return (left._delta < right._delta);
        }

        
        public static bool operator >(TemperatureDelta left, TemperatureDelta right)
        {
            if (null == left)
                throw new ArgumentNullException("left", "Cannot compare null TemperatureDelta");

            if (null == right)
                throw new ArgumentNullException("right", "Cannot compare null TemperatureDelta");

            return (left._delta > right._delta);
        }

        
        public static bool operator <=(TemperatureDelta left, TemperatureDelta right)
        {
            if (null == left)
                throw new ArgumentNullException("left", "Cannot compare null TemperatureDelta");

            if (null == right)
                throw new ArgumentNullException("right", "Cannot compare null TemperatureDelta");

            return (left._delta <= right._delta);
        }

        
        public static bool operator >=(TemperatureDelta left, TemperatureDelta right)
        {
            if (null == left)
                throw new ArgumentNullException("left", "Cannot compare null TemperatureDelta");

            if (null == right)
                throw new ArgumentNullException("right", "Cannot compare null TemperatureDelta");

            return (left._delta >= right._delta);
        }

        #endregion

        #region parsing

        public static TemperatureDelta Parse(string s)
        {
            return Common.Parse(s, _unitInfo);
        }

        public static bool TryParse(string s, out TemperatureDelta value)
        {
            try
            {
                value = TemperatureDelta.Parse(s);
                return true;
            }
            catch (Exception)
            {
                value = null;
                return false;
            }
        }

        #endregion

        #region Object overrides

        public override bool Equals(object obj)
        {
            TemperatureDelta d = obj as TemperatureDelta;

            return (d == null) ? false : (this._delta == d._delta);
        }

        public override int GetHashCode()
        {
            return _delta.GetHashCode();
        }

        /// <summary>
        /// Returns a string appended with default units (e.g. "1.23 m/sec^2").
        /// </summary>
        /// <returns>
        /// A string that represents this instance
        /// </returns>
        public override string ToString()
        {
            return ToString(DefaultUnits);
        }

        /// <summary>
        /// overload that returns a string appended with specified units (e.g. "1.23 ft/sec^2").
        /// </summary>
        /// <param name="units"></param>
        /// <returns></returns>
        public string ToString(Unit units)
        {
            double value = (double)_unitInfo[units].PropertyGetter.Invoke(this, null);

            return $"{value} {_unitInfo[units].Abbreviation[0]}";
        }

        #endregion
    }
}
