/* Distributed under the Apache License, Version 2.0.
   See accompanying NOTICE file for details.*/

//----------------------------------------------------------------------------
/// @file PScalar.h
/// @author Chris Volpe
///
/// PScalar is short for Physics Scalar. It is an encapsulation of a 
/// double precision floating point value and a CompoundUnit object. 
/// It allows all the expected arithmetic operations, as well as the 
/// functions pow() for raising to a power, and sqrt() for computing the
/// square root.
//----------------------------------------------------------------------------

#pragma once

class CPScalar
{
public:
  // Default ctor
  CPScalar()
    :m_dValue(0.0)
  {
    // Do nothing
  }

  // Construct from value and string unit spec
  CPScalar(double val, const std::string &unitSpec)
    :m_dValue(val), m_CCU(unitSpec)
  {
    // Do nothing
  }

  // Construct from value. No units.
  explicit CPScalar(double val)
    :m_dValue(val)
  {
    // Do nothing
  }

  // Construct from string unit. Initial value is 1.0 so that we can multiply by a pure 
  // unit specification. 
  CPScalar(const std::string &unitSpec)
    :m_dValue(1.0), m_CCU(unitSpec)
  {
    // Do nothing
  }

  // Copy ctor
  CPScalar(const CPScalar &src)
    :m_dValue(src.m_dValue), m_CCU(src.m_CCU)
  {
    // Do nothing
  }

  // Overload arithmetic operators in the proper fashion.
  CPScalar & operator=(const CPScalar &rhs)
  {
    if (this != &rhs)
    {
      m_dValue = rhs.m_dValue;
      m_CCU = rhs.m_CCU;
    }
    return *this;
  }

  CPScalar & operator*=(const CPScalar &rhs)
  {
    m_dValue *= rhs.m_dValue;
    m_CCU *= rhs.m_CCU;
    return *this;
  }

  CPScalar & operator/=(const CPScalar &rhs)
  {
    m_dValue /= rhs.m_dValue;
    m_CCU /= rhs.m_CCU;
    return *this;
  }

  CPScalar operator*(const CPScalar &rhs) const
  {
    return CPScalar(*this)*= rhs;
  }

  CPScalar operator/(const CPScalar &rhs) const
  {
    return CPScalar(*this)/= rhs;
  }

  // Addition and subtraction require a little care. Adding values requires
  // they be in the same units, so we will convert the right hand side operand
  // to the units of the left hand side. We use the interval conversion function
  // so that we don't get thrown off by biases in the conversion process

  CPScalar & operator+=(const CPScalar &rhs)
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValueInterval(rhs.m_dValue,rhs.m_CCU,m_CCU);
    m_dValue += newVal;
    return *this;
  }

  CPScalar & operator-=(const CPScalar &rhs)
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValueInterval(rhs.m_dValue,rhs.m_CCU,m_CCU);
    m_dValue -= newVal;
    return *this;
  }
    
  // Use ConvertValue here rather than ConvertValueInterval so that we can
  // correctly determine that 0 degC == 32 degF
  bool operator==(const CPScalar &rhs)const
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValue(rhs.m_dValue,rhs.m_CCU,m_CCU);
    return m_dValue == newVal;
  }

  // We can implement this in terms of overloaded == because in case of type mismatch, NaN is
  // returned by the conversion, which "==" will treat as false, and we want "!=" to
  // yield true in that circumstance
  bool operator!=(const CPScalar &rhs)const
  {
    return !(*this == rhs);
  }

  // Instead of implementing < in terms of !(>=), and > in terms of !(<=) (or vice versa),
  // we provide separate implementations for them all, because we don't want anything
  // to return true in case of NaN (see above), since NAN is always supposed to have an
  // unordered result.
  bool operator<(const CPScalar &rhs)const
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValue(rhs.m_dValue,rhs.m_CCU,m_CCU);
    return m_dValue < newVal;
  }

  bool operator>(const CPScalar &rhs)const
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValue(rhs.m_dValue,rhs.m_CCU,m_CCU);
    return m_dValue > newVal;
  }

  bool operator<=(const CPScalar &rhs)const
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValue(rhs.m_dValue,rhs.m_CCU,m_CCU);
    return m_dValue <= newVal;
  }

  bool operator>=(const CPScalar &rhs)const
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newVal = uce.ConvertValue(rhs.m_dValue,rhs.m_CCU,m_CCU);
    return m_dValue >= newVal;
  }


  CPScalar operator+(const CPScalar &rhs) const
  {
    return CPScalar(*this)+= rhs;
  }

  CPScalar operator-(const CPScalar &rhs) const
  {
    return CPScalar(*this)-= rhs;
  }

  CPScalar operator+() const // unary +
  {
    return CPScalar(*this); // make a copy anyway!
  }

  CPScalar & Negate(void)
  {
    m_dValue *= -1.0;
    return *this;
  }

  CPScalar operator-() const // unary -
  {
    return CPScalar(*this).Negate();
  }

  // conversion operators. Casting to the designated type invokes these functions.
  operator bool() const
  {
    return m_dValue != 0.0;
  }

  operator int() const
  {
    return static_cast<int>(static_cast<double>(*this));
  }

  operator float() const
  {
    return static_cast<float>(static_cast<double>(*this));
  }

  operator double() const
  {
    double newval;
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    static CCompoundUnit unitless(""); // dimensionless unitless CompoundUnit
    if (*unitless.GetDimension() == *m_CCU.GetDimension())
    {
      // We are dimensionless, but we potentially have a scale factor
      // due to quantity-type cancellation, such as knots/mph. So
      // convert to *unitless* to incorporate that bigness difference 
      // into the value.

       newval = uce.ConvertValue(m_dValue, m_CCU, unitless);
    }
    else
    {
      throw "PScalar value can't be cast to numeric unless already dimensionless!";
    }
    return newval;
  }

  double GetValue() const
  {
    return m_dValue;
  }

  // Function call operator! Overload to take a string unit spec and return a new
  // value equal to the conversion of the current instance to the specified unit.
  // Usage: myPScalar("newUnit").
  // This creates a new CPScalar value and does not change the state of the object
  // upon which it is invoked (unlike the method it calls: ConvertTo)
  CPScalar operator()(const std::string &unitSpec) const
  {
    return CPScalar(*this).ConvertTo(unitSpec);
  }

  // Raise myself to a given power
  // Return reference to self so it can be applied to the temporary created
  // in implementation of pow(). Same thing below for SQRoot() and sqrt()
  CPScalar & Raise(double pwr)
  {
    m_dValue = pow(m_dValue, pwr);
    m_CCU.Raise(pwr);
    return *this;
  }

  // Compute square root of myself
  // This method modifies the current object
  CPScalar & SQRoot()
  {
    // Rather than just invoking Raise(0.5) on ourselves, use sqrt(double) on the
    // value so that we can take advantage of intrinsic implementations on the X86
    m_dValue = sqrt(m_dValue);
    m_CCU.Raise(0.5);
    return *this;
  }

  // Compute absolute value of myself
  // This method modified the current object
  CPScalar & Abs()
  {
    m_dValue = std::abs(m_dValue);
    return *this;
  }

  // Convert this object to a new set of units
  CPScalar & ConvertTo(const std::string &unitSpec)
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    CCompoundUnit newUnit(unitSpec);
    double newval = uce.ConvertValue(m_dValue, m_CCU, newUnit);
    m_CCU = newUnit;
    m_dValue = newval;
    return *this;
  }

  // Convert this object to a new set of units
  CPScalar & ConvertTo(const CCompoundUnit &newUnit)
  {
    CUnitConversionEngine &uce = CUnitConversionEngine::GetEngine();
    double newval = uce.ConvertValue(m_dValue, m_CCU, newUnit);
    m_CCU = newUnit;
    m_dValue = newval;
    return *this;
  }

  // Convert this object to the units of another object
  CPScalar & ConvertTo(const CPScalar &target)
  {
    return ConvertTo(target.m_CCU);
  }

  std::ostream & PrintSelf(std::ostream &output) const
  {
    return output << m_dValue << " " << m_CCU;
  }

  bool IsSameType(const CPScalar &target) const
  {
    return (*m_CCU.GetDimension() == *target.m_CCU.GetDimension());
  }

  bool IsDimensionless() const
  {
    return m_CCU.IsDimensionless();
  }

  // Is this CompoundUnit in "decibel" mode
  bool IsDecible() const
  {
    return m_CCU.IsDecibel();
  }

  template <class T>
    friend CPScalar & operator *=( CPScalar &lhs, const T &rhs);
  template <class T>
    friend CPScalar & operator /=( CPScalar &lhs, const T &rhs);  

private:
  double m_dValue;
  CCompoundUnit m_CCU;
};

inline CPScalar pow(const CPScalar &baseref, double pwr)
{
  return (CPScalar(baseref)).Raise(pwr);
}

inline CPScalar pow(const CPScalar &baseref, int pwr)
{
  return (CPScalar(baseref)).Raise(static_cast<double>(pwr));
}

inline CPScalar sqrt(const CPScalar &argref)
{
  return (CPScalar(argref)).SQRoot();
}

inline CPScalar abs(const CPScalar &argref)
{
  return (CPScalar(argref)).Abs();
}

inline std::ostream & operator<<(std::ostream &output, const CPScalar &self)
{
  return self.PrintSelf(output);
}

// Due to the fact that we have casts to double, etc defined, we can't 
// rely on the CPScalar(double) constructor to promote non-CPScalar
// operands, since the conversion in the other direction is just as
// valid, according to the compiler, and therefore ambiguous. 
// In these templates, "T" can be bool, int, float, double
// Each operator has two signatures, one in which the CPScalar
// value comes first, and one in which it comes second. In each case, 
// we construct a CPScalar out of the templated arg and add it to the 
// existing CPScalar object using the overloaded *member* form of the
// operator. 
// Note that the assignment versions return a reference to the LHS operand

// Multiply
template <class T>
CPScalar operator*(const CPScalar &lhs, const T &rhs)
{
  return lhs * CPScalar(rhs);
}

template <class T>
CPScalar operator*(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) * rhs;
}

template <class T>
CPScalar & operator *=(CPScalar &lhs, const T &rhs)
{
  lhs.m_dValue *= rhs;
  return lhs;
}

// Divide
template <class T>
CPScalar operator/(const CPScalar &lhs, const T &rhs)
{
  return lhs / CPScalar(rhs);
}

template <class T>
CPScalar operator/(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) / rhs;
}

template <class T>
CPScalar & operator/=(CPScalar &lhs, const T &rhs)
{
  lhs.m_dValue /= rhs;
  return lhs;
}

// Add
template <class T>
CPScalar operator+(const CPScalar &lhs, const T &rhs)
{
  return lhs + CPScalar(rhs);
}

template <class T>
CPScalar operator+(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) + rhs;
}

// Subtract
template <class T>
CPScalar operator-(const CPScalar &lhs, const T &rhs)
{
  return lhs - CPScalar(rhs);
}

template <class T>
CPScalar operator-(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) - rhs;
}

// Less than
template <class T>
bool operator<(const CPScalar &lhs, const T &rhs)
{
  return lhs < CPScalar(rhs);
}

template <class T>
bool operator<(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) < rhs;
}

// Less than or equal to
template <class T>
bool operator<=(const CPScalar &lhs, const T &rhs)
{
  return lhs <= CPScalar(rhs);
}

template <class T>
bool operator<=(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) <= rhs;
}

// Greater than
template <class T>
bool operator>(const CPScalar &lhs, const T &rhs)
{
  return lhs > CPScalar(rhs);
}

template <class T>
bool operator>(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) > rhs;
}

// Greater than or equal to
template <class T>
bool operator>=(const CPScalar &lhs, const T &rhs)
{
  return lhs >= CPScalar(rhs);
}

template <class T>
bool operator>=(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) >= rhs;
}

// Equal 
template <class T>
bool operator==(const CPScalar &lhs, const T &rhs)
{
  return lhs == CPScalar(rhs);
}

template <class T>
bool operator==(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) == rhs;
}

// Not equal
template <class T>
bool operator!=(const CPScalar &lhs, const T &rhs)
{
  return lhs != CPScalar(rhs);
}

template <class T>
bool operator!=(const T &lhs, const CPScalar &rhs)
{
  return CPScalar(lhs) != rhs;
}


