//############################################################################//
// RAYTHEON PROPRIETARY: THIS DOCUMENT CONTAINS DATA OR INFORMATION           
// PROPRIETARY TO RAYTHEON COMPANY AND IS RESTRICTED TO USE ONLY BY PERSONS   
// AUTHORIZED BY RAYTHEON COMPANY IN WRITING TO USE IT. DISCLOSURE TO         
// UNAUTHORIZED PERSONS WOULD LIKELY CAUSE SUBSTANTIAL COMPETITIVE HARM TO    
// RAYTHEON COMPANY'S BUSINESS POSITION. NEITHER SAID DOCUMENT NOR ITS        
// CONTENTS SHALL BE FURNISHED OR DISCLOSED TO OR COPIED OR USED BY PERSONS   
// OUTSIDE RAYTHEON COMPANY WITHOUT THE EXPRESS WRITTEN APPROVAL OF RAYTHEON  
// COMPANY.                                                                   
//       
// THIS PROPRIETARY NOTICE IS NOT APPLICABLE IF DELIVERED TO THE U.S. GOVERNMENT
//                                                                    
// UNPUBLISHED WORK - COPYRIGHT RAYTHEON COMPANY.                             
//                                                                            
// WARNING: THIS DOCUMENT CONTAINS TECHNICAL DATA AND / OR TECHNOLOGY WHOSE        
// EXPORT OR DISCLOSURE TO NON-U.S. PERSONS, WHEREVER LOCATED, IS RESTRICTED                
// BY THE INTERNATIONAL TRAFFIC IN ARMS REGULATIONS (ITAR) (22 C.F.R. SECTION                     
// 120-130) OR THE EXPORT ADMINISTRATION REGULATIONS (EAR) (15 C.F.R. SECTION                  
// 730-774). THIS DOCUMENT CANNOT BE EXPORTED (E.G., PROVIDED TO A SUPPLIER                   
// OUTSIDE OF THE UNITED STATES) OR DISCLOSED TO A NON-U.S. PERSON, WHEREVER               
// LOCATED, UNTIL A FINAL JURISDICTION AND CLASSIFICATION DETERMINATION HAS                  
// BEEN COMPLETED AND APPROVED BY RAYTHEON, AND ANY REQUIRED U.S. GOVERNMENT    
// APPROVALS HAVE BEEN OBTAINED. VIOLATIONS ARE SUBJECT TO SEVERE CRIMINAL                 
// PENALTIES.      
//
// DOD 5220.22-M, INDUSTRIAL SECURITY MANUAL, CHAPTER 5, SECTION 1 THROUGH 9 : 
// FOR CLASSIFIED DOCUMENTS FOLLOW THE PROCEDURES IN OR DOD 5200.1-R, 
// INFORMATION SECURITY PROGRAM, CHAPTER 6. FOR UNCLASSIFIED, LIMITED DOCUMENTS 
// DESTROY BY ANY  METHOD THAT WILL PREVENT DISCLOSURE OF CONTENTS OR 
// RECONSTRUCTION OF THE DOCUMENT.                                                                                                                                             
//############################################################################//

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

namespace Raytheon.Units
{
    /// <summary>
    /// This class represents a Flow Rate.  
    /// 
    /// It has no constructors.  Instead, it provides multiple methods for creating objects of this 
    /// type.  Those methods are of the form FromXXX, where XXX is a unit (e.g. FromCubicMetersPerSecond).
    /// 
    /// Properties (e.g. CubicMetersPerSecond) are provided to convert the value to other units and return 
    /// as type double.
    /// </summary>
    [Serializable]
    [DataContract]
    public class FlowRate
    {
        public enum Unit
        {
            CubicMetersPerSecond,
            CubicMetersPerMinute,
            CubicMetersPerHour,
            GallonsPerMinute
        }

        public static IDictionary<Unit, List<string>> UnitAbbreviations = new Dictionary<Unit, List<string>>()
        {
            {Unit.CubicMetersPerSecond, new List<string>(){ "m^3/s" } },
            {Unit.CubicMetersPerMinute, new List<string>(){ "m^3/min" } },
            {Unit.CubicMetersPerHour,   new List<string>(){ "m^3/h" } },
            {Unit.GallonsPerMinute,     new List<string>(){ "gal/min" } },
        };

        private const Unit DefaultUnits = Unit.CubicMetersPerSecond;
        private const double CubicMetersToGalPerSecMultipler = 15850;
        private const int SecondsPerMinute = 60;
        private const int SecondsPerHour = 3600;

        [DataMember(Name = "CubicMetersPerSecond", IsRequired = true)]
        private readonly double _flowRate;

        // map: unit => abbreviation, conversion method and property
        private static readonly IDictionary<Unit, UnitInfo<FlowRate>> _unitInfo = new Dictionary<Unit, UnitInfo<FlowRate>>()
        {
            { Unit.CubicMetersPerSecond, new UnitInfo<FlowRate>(UnitAbbreviations[Unit.CubicMetersPerSecond], FromCubicMetersPerSecond, typeof(FlowRate).GetProperty("CubicMetersPerSecond").GetGetMethod()) },
            { Unit.CubicMetersPerMinute, new UnitInfo<FlowRate>(UnitAbbreviations[Unit.CubicMetersPerMinute], FromCubicMetersPerMinute, typeof(FlowRate).GetProperty("CubicMetersPerMinute").GetGetMethod()) },
            { Unit.CubicMetersPerHour,   new UnitInfo<FlowRate>(UnitAbbreviations[Unit.CubicMetersPerHour],   FromCubicMetersPerHour,   typeof(FlowRate).GetProperty("CubicMetersPerHour").GetGetMethod()) },
            { Unit.GallonsPerMinute,     new UnitInfo<FlowRate>(UnitAbbreviations[Unit.GallonsPerMinute],     FromGallonsPerMinute,     typeof(FlowRate).GetProperty("GallonsPerMinute").GetGetMethod()) }
        };

        /// <summary>
        /// Prevents a default instance of the <see cref="FlowRate"/> class from being created
        /// Use FromXXX methods to create objects of this type.
        /// </summary>
        private FlowRate()
        {
            throw new InvalidOperationException("No one should be calling this, it is only here to prevent users from creating default object");
        }

        /// <summary>
        /// Constructor used by this class to create objects in FromXXX methods.
        /// </summary>
        /// <param name="flowRate">The flow rate - Cubic Meters per second</param>
        private FlowRate(double cubicMetersPerSecond)
        {
            _flowRate = cubicMetersPerSecond;
        }

        #region conversion properties
        /// <summary>
        /// Returns Cubic Meters Per Second as type double
        /// </summary>
        public double CubicMetersPerSecond { get { return _flowRate; } }

        /// <summary>
        /// Returns Cubic Meters Per Minutes as type double
        /// </summary>
        public double CubicMetersPerMinute { get { return _flowRate * SecondsPerMinute; } }

        /// <summary>
        /// Returns Cubic Meters Per Hour as type double
        /// </summary>
        public double CubicMetersPerHour { get { return _flowRate * SecondsPerHour; } }

        /// <summary>
        /// Returns Gallons Per Minute
        /// </summary>
        public double GallonsPerMinute { get { return _flowRate * CubicMetersToGalPerSecMultipler; } }
        #endregion

        #region Creation Methods
        /// <summary>
        /// Returns FlowRate object interpreting passed value as Cubic Meters Per Second
        /// </summary>
        /// <param name="flowRate">Cubic Meters Per Second</param>
        /// <returns></returns>
        public static FlowRate FromCubicMetersPerSecond(double m3PerSec)
        {
            return new FlowRate(m3PerSec);
        }

        public static FlowRate FromCubicMetersPerMinute(double m3PerMin)
        {
            return new FlowRate(m3PerMin / SecondsPerMinute);
        }

        public static FlowRate FromCubicMetersPerHour(double m3PerHr)
        {
            return new FlowRate(m3PerHr / SecondsPerHour);
        }

        public static FlowRate FromGallonsPerMinute(double gallonPerMin)
        {
            return new FlowRate(gallonPerMin / CubicMetersToGalPerSecMultipler);
        }
        #endregion

        public override bool Equals(object obj)
        {
            FlowRate flowRate = obj as FlowRate;

            return (flowRate == null) ? false : (this._flowRate == flowRate._flowRate);
        }

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

        #region Binary Operators
        // not implementing %, &, |, ^, <<, >>

        public static FlowRate operator +(FlowRate left, FlowRate right)
        {
            if (null == left)
            {
                throw new ArgumentNullException("left", "Cannot add a null Flow Rate");
            }

            if (null == right)
            {
                throw new ArgumentNullException("right", "Cannot add null Flow Rate");
            }

            return new FlowRate(left._flowRate + right._flowRate);
        }

        public static FlowRate operator *(FlowRate flowRate, double scalar)
        {
            if (null == flowRate)
            {
                throw new ArgumentNullException("flowRate", "Cannot multiply a null Flow Rate");
            }

            return new FlowRate(flowRate._flowRate * scalar);
        }

        public static FlowRate operator *(double scalar, FlowRate flowRate)
        {
            if (null == flowRate)
            {
                throw new ArgumentNullException("flowRate", "Cannot multiply a null Flow Rate");
            }

            return new FlowRate(scalar * flowRate._flowRate);
        }

        public static FlowRate operator /(FlowRate flowRate, double scalar)
        {
            if (null == flowRate)
            {
                throw new ArgumentNullException("flowRate", "Cannot divide a null Flow Rate");
            }

            if (0.0 == scalar)
            {
                throw new DivideByZeroException("Cannot divide Flow Rate by 0");
            }

            return new FlowRate(flowRate._flowRate / scalar);
        }
        #endregion

        #region comparison operators
        public static bool operator ==(FlowRate left, FlowRate right)
        {
            bool bReturn = false;
            if (object.ReferenceEquals(left, null))
            {
                if (object.ReferenceEquals(right, null))
                {
                    //both are null, so we are ==
                    bReturn = true;
                }
            }
            else
            {
                if (!object.ReferenceEquals(left, null) &&
                    !object.ReferenceEquals(right, null))
                {
                    bReturn = left._flowRate == right._flowRate;
                }
            }
            return bReturn;
        }

        public static bool operator !=(FlowRate left, FlowRate right)
        {
            return !(left == right);
        }

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

            if (null == right)
            {
                throw new ArgumentNullException("right", "Cannot compare null Flow Rate");
            }

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

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

            if (null == right)
            {
                throw new ArgumentNullException("right", "Cannot compare null Flow Rate");
            }

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

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

            if (null == right)
            {
                throw new ArgumentNullException("right", "Cannot compare null Flow Rate");
            }

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

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

            if (null == right)
            {
                throw new ArgumentNullException("right", "Cannot compare null Flow Rate");
            }

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

        #endregion

        #region parsing

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

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

        #endregion

        /// <summary>
        /// Returns a <see cref="string" /> appended with units symbol (e.g. "1.23 M^3/s").
        /// </summary>
        /// <returns>
        /// A <see cref="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]}";
        }
    }
}