//############################################################################//
// 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;

namespace Raytheon.Units
{
    /// <summary>
    /// This class represents a Pressure  
    /// 
    /// 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. Pascal).
    /// 
    /// Properties (e.g. Pascal) are provided to convert the value to other units and return 
    /// as type double.
    /// </summary>
    [Serializable]
    [DataContract]
    public class Pressure
    {
        public enum Unit
        {
            Bar,
            Millibar,
            Pascal,
            PSI
        }

        public static IDictionary<Unit, List<string>> UnitAbbreviations = new Dictionary<Unit, List<string>>()
        {
            {Unit.Bar,      new List<string>(){ "bar" } },
            {Unit.Millibar, new List<string>(){ "mbar" } },
            {Unit.Pascal,   new List<string>(){ "Pa" } },
            {Unit.PSI,      new List<string>(){ "PSI" } },
        };

        private const Unit DefaultUnits = Unit.PSI;
        private const int PascalPerBar = 100000;
        private const double PascalPerPSI = 6894.75729;

        [DataMember(Name = "Pascal", IsRequired = true)]
        private double _pressure; // Pascal

        // map: unit => abbreviation, conversion method and property
        private static IDictionary<Unit, UnitInfo<Pressure>> _unitInfo = new Dictionary<Unit, UnitInfo<Pressure>>()
        {
            { Unit.Bar,      new UnitInfo<Pressure>(UnitAbbreviations[Unit.Bar],      FromBar, typeof(Pressure).GetProperty("Bar").GetGetMethod()) },
            { Unit.Millibar, new UnitInfo<Pressure>(UnitAbbreviations[Unit.Millibar], FromMillibar, typeof(Pressure).GetProperty("Millibar").GetGetMethod()) },
            { Unit.Pascal,   new UnitInfo<Pressure>(UnitAbbreviations[Unit.Pascal],   FromPascal, typeof(Pressure).GetProperty("Pascal").GetGetMethod()) },
            { Unit.PSI,      new UnitInfo<Pressure>(UnitAbbreviations[Unit.PSI],      FromPSI, typeof(Pressure).GetProperty("PSI").GetGetMethod()) }
        };

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

        /// <summary>
        /// ctor used by this class to create objects in FromXXX methods.
        /// </summary>
        /// <param name="pressure">The pressure - pascal</param>
        private Pressure(double pressure)
        {
            this._pressure = pressure;
        }

        #region conversion properties
        public double Bar { get { return _pressure / PascalPerBar; } }

        public double Millibar { get { return _pressure / PascalPerBar * 1000; } }

        public double Pascal { get { return _pressure; } }        

        public double PSI { get { return _pressure / PascalPerPSI; } }        
        #endregion

        #region Creation Methods
        public static Pressure FromBar(double bars)
        {
            return new Pressure(bars * PascalPerBar);
        }

        public static Pressure FromMillibar(double millibars)
        {
            return new Pressure(millibars * PascalPerBar / 1000);
        }

        /// <summary>
        /// Returns Pressure object interpreting passed value as Pascal
        /// </summary>
        /// <param name="Pressure">Pascal</param>
        /// <returns></returns>
        public static Pressure FromPascal(double pascal)
        {
            return new Pressure(pascal);
        }

        public static Pressure FromPSI(double psi)
        {
            return new Pressure(psi * PascalPerPSI);
        }
        #endregion

        public override bool Equals(object obj)
        {
            Pressure pressure = obj as Pressure;

            return (pressure == null) ? false : (this._pressure == pressure._pressure);
        }

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

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

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

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

            return new Pressure(left._pressure + right._pressure);
        }

        
        public static Pressure operator -(Pressure left, Pressure right)
        {
            if (null == left)
            {
                throw new ArgumentNullException("left", "Cannot add a null Pressure");
            }

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

            return new Pressure(left._pressure - right._pressure);
        }

        
        public static Pressure operator *(Pressure pressure, double scalar)
        {
            if (null == pressure)
            {
                throw new ArgumentNullException("pressure", "Cannot multiply a null Pressure");
            }

            return new Pressure(pressure._pressure * scalar);
        }

        
        public static Pressure operator *(double scalar, Pressure pressure)
        {
            if (null == pressure)
            {
                throw new ArgumentNullException("pressure", "Cannot multiply a null Pressure");
            }

            return new Pressure(scalar * pressure._pressure);
        }

        
        public static Pressure operator /(Pressure pressure, double scalar)
        {
            if (null == pressure)
            {
                throw new ArgumentNullException("pressure", "Cannot divide a null Pressure");
            }

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

            return new Pressure(pressure._pressure / scalar);
        }
        #endregion

        #region comparison operators
        public static bool operator ==(Pressure left, Pressure 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._pressure == right._pressure;
                }
            }
            return bReturn;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

        #endregion

        #region parsing

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

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

        #endregion

        /// <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]}";
        }
    }
}
