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

//----------------------------------------------------------------------------
/// @file CompoundUnit.h
/// @author Chris Volpe
/// 
/// This class defines an internal representation of a Compound Unit, which is the
/// product of individual unit symbols, each with a scalar prefix and raised to
/// some exponent.
//----------------------------------------------------------------------------
#pragma once

// Let's try to make this as lightweight as possible so that CCompoundUnit 
// objects can be efficiently manipulated within the context of an enclosing
// physical quantity object (name TBD: PhysicsQuantity? DimensionedQuantity?
// How about just Quantity?) when arithmetic with them needs to be done. This 
// means maximizing use of inline functions, even those that manipulate the 
// CUEVecType elements, which in turn requires dealing with Microsoft's BS for
// exporting STL classes and classes containing STL data objects as data members.
// (See http://support.microsoft.com/kb/168958) The only STL class that you can do 
// this with is vector<>, but fortunately that's all we need here. Usually, I get 
// around this by declaring the data objects as pointers to STL template 
// instantiations that I dynamically allocate and manipulate in non-inline functions,
// but since we're going for inline methods here, doing so wouldn't buy me anything,
// so I may as well make them first class data members. That may have the added
// benefit of coaxing out any helpful warning messages that the compiler might
// mistakenly think are not relevant when the data members are merely pointers to
// STL classes. -CRV

// Note: The ordering of the following two declarations is vital. The above-mentioned
// knowledge base article only talks about exporting the vector class that I'm 
// explicitly instantiating, but that results in an error message about the allocator
// needing to be exported. Adding the allocator export after the vector export didn't
// solve the problem. But the paragraph about nested classes in the KB article alludes to
// a circular dependency that manifests itself as different error messages depending
// on which thing you export first. So, on a hunch, I reversed the order, and voila!

class CDM_DECL CCompoundUnit
{
  // Define the vector type that holds our individual components of a CompoundUnit
  typedef std::vector<CCompoundUnitElement> CUEVecType;

public:
  // Default ctor
  CCompoundUnit() : m_dBigness(1.0),
                    m_CUD(nullptr),
                    m_bStaleBigness(true),
                    m_bStaleDimension(true),
                    m_bExplicitNonDBFlag(false),
                    m_bExplicitDBFlag(false),
                    m_bDBFlag(false)
  {
  };

  // Construct directly from a unit string specification
  CCompoundUnit(const std::string &unitString) : m_dBigness(1.0),
                                                 m_CUD(nullptr),
                                                 m_bStaleBigness(true),
                                                 m_bStaleDimension(true),
                                                 m_bExplicitNonDBFlag(false),
                                                 m_bExplicitDBFlag(false),
                                                 m_bDBFlag(false)
  {
    ParseString(unitString);
  }

  // Copy ctor
  CCompoundUnit(const CCompoundUnit &src) : m_strUnit(src.m_strUnit),
                                            m_dBigness(src.m_dBigness),
                                            m_CUEVec(src.m_CUEVec),
                                            m_bStaleBigness(src.m_bStaleBigness),
                                            m_bStaleDimension(src.m_bStaleDimension),
                                            m_bExplicitNonDBFlag(src.m_bExplicitNonDBFlag),
                                            m_bExplicitDBFlag(src.m_bExplicitDBFlag),
                                            m_bDBFlag(src.m_bDBFlag)
  {
    // In the initializer list, I'm assuming that initializing one vector with another 
    // copies the vector correctly. For the CUnitDimension object, we can't just
    // initialize our pointer with the src's pointer, because each CCompoundUnit owns its
    // CUnitDimension object, so we need to invoke the copy constructor on the 
    // CUnitDimension. Unfortunately, since the pointer might be nullptr, we can't simply
    // do this in the initializer list.
    if (src.m_CUD == nullptr)
    {
      m_CUD = nullptr;
    }
    else
    {
      m_CUD = new CUnitDimension(*src.m_CUD);
    }
  }; 

  // dtor
  virtual ~CCompoundUnit()
  {
    if (m_CUD)
    {
      delete m_CUD;
    }
  };

  // Obtain a dimension object that uniquely represents the dimensions of this
  // compound unit in terms of fundamental quantities
  const CUnitDimension *GetDimension() const;

  // Obtain the relative magnitude of this compound unit in relation to
  // an equivalently-dimensioned compound unit comprised of the base units
  // of each fundamental quantity type. The bigness is the key to unit conversion.
  // For any compound unit of a given dimension, the value of a quantity in that
  // unit times the bigness of that unit is an invariant quantity.
  const double &GetBigness() const;

  // Obtain the bias of this compound unit. Biases denote additive offsets of one unit 
  // with respect to another unit of the same type. I am currently operating under the
  // assumption that it is nonsensical to process biases in a compound unit consisting
  // of more than one fundamental quantity type raised to the power of 1.0. If I come 
  // across a counter-example to this, I may have to re-think things, but for now, I am 
  // assuming that when multiplying a biased unit by other quantities, the bias must be 
  // irrelevant. Currently, the only quantity type that has biased units is temperature,
  // and this principle seems to hold. E.g., J/degC means the same thing as J/degK even
  // though there's a bias in converting from degC to degK.
  double GetBias() const;

  // We can do arithmetic with these, so overload a few operators
  // But do *NOT* define operator+, operator-, or the assignment
  // versions of these operators. You cannot "add" two CompoundUnit
  // objects. You can add two *values* if they have equivalent
  // compound unit objects, but the only operations that
  // can be performed on a compound unit object itself are multiplication,
  // division, and raising to a power.
  CCompoundUnit & operator=(const CCompoundUnit &rhs)
  {
    if (this != &rhs)
    {
      m_dBigness = rhs.m_dBigness;
      m_bDBFlag = rhs.m_bDBFlag;
      m_bExplicitDBFlag = rhs.m_bExplicitDBFlag;
      m_bExplicitNonDBFlag = rhs.m_bExplicitNonDBFlag;

      // Vector objects should know how to deep-copy themselves
      m_CUEVec = rhs.m_CUEVec;
      // Free my dimension if I have one, whether it's stale or now
      if (m_CUD)
      {
        delete m_CUD;
        m_CUD = nullptr;
      }
      // We need to deep-copy the CUnitDimension, not copy pointers
      // But only copy if RHS is not stale. No sense duplicating
      // something that's stale. Also, if it's nullptr, it's stale, so
      // no need for a separate nullptr check.
      if (! rhs.m_bStaleDimension)
      {
        m_CUD = new CUnitDimension(*rhs.m_CUD);
      }
      m_bStaleBigness = rhs.m_bStaleBigness;
      m_bStaleDimension = rhs.m_bStaleDimension;
    }
    return *this;
  };

  CCompoundUnit & operator*=(const CCompoundUnit &rhs);
  CCompoundUnit & operator/=(const CCompoundUnit &rhs);

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

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

  // There's no "raise to a power" operator, but this is the next best thing.
  CCompoundUnit & Raise(CCompoundUnitElement::ExponentType);

  // Compare two CompoundUnits
  // We don't care about how the units are precisely represented, whether
  // one contains "J" and the other contains "kg m^2 s^-2", or "s^-2 m^2 kg"
  // for that matter. We care about whether they are equivalent. And they are
  // equivalent if and only if they are equivalent in both dimension and bigness.
  bool operator==(const CCompoundUnit &rhs) const
  {
    return ( (*this->GetDimension() == *rhs.GetDimension()) && 
         (this->GetBigness() == rhs.GetBigness()) &&
         (this->GetBias() == rhs.GetBias()));
  }

  bool operator!=(const CCompoundUnit &rhs) const
  {
    return !(*this == rhs);
  }

  // Is this CompoundUnit dimensionally-compatible with a pre-defined
  // quantity type?
  bool IsOfType(int quantityTypeID);
  bool IsOfType(const std::string &quantityName);

  // Is this CompoundUnit dimensionless
  bool IsDimensionless() const
  {
    return this->GetDimension()->IsDimensionless();
  }

  // Is this CompoundUnit in "decibel" mode
  bool IsDecibel() const;

  bool IsUnitEmpty() const
  {
    return this->m_strUnit.empty();
  }

  // Returns either 10 or 20, depending on whether this compound unit is of a 
  // Quantity Type that obeys the 20-Log-Rule
  double GetDecibelLogScaleFactor() const;

  void DecibelModeOn()
  {
    // Make this explicitly decibel mode, in case we need to do a conversion to/from dB. 
    // We need explicit flags for each condition, so that with both of them off, the
    // dB state is driven by the presence of a dB mode in the Compound Unit Elements 
    // (through the CompoundUnit expansion of a Unit Descriptor).
    m_bExplicitDBFlag = true;
    m_bExplicitNonDBFlag = false;
    m_bDBFlag = true;
  }

  void DecibelModeOff()
  {
    // Make this explicitly not-decibel mode
    m_bExplicitDBFlag = false;
    m_bExplicitNonDBFlag = true;
    m_bDBFlag = false;
  }

  // Used for incrementally building up a CompoundUnit
  CCompoundUnitElement & AddElement(const CCompoundUnitElement &);

  // Erase everything
  void clear()
  {
    if (m_CUD)
    {
      delete m_CUD;
      m_CUD = nullptr;
    }
    m_dBigness = 1.0;
    m_bStaleBigness = true;
    m_bStaleDimension = true;
    // The explicit dB flag is true if the CompoundUnit explicitly contained a "dB" token.
    // The regular dB flag is true if either the explicit dB flag is true or if any of the
    // Compound Unit Elements refer to Unit Descriptors whose Compound Unit expansion has 
    // its dB flag set
    m_bExplicitDBFlag = false;
    m_bDBFlag = false;
    m_CUEVec.clear();
    m_strUnit.clear();
  }

  // Build up my internals from a string specification.
  void ParseString(const std::string &unitString);

  std::string GetString() const {return m_strUnit;}

  // Auxiliary output routine
  std::ostream & PrintSelf(std::ostream &output) const;

protected:
  void BuildDimension() const;
  void ComputeBigness() const;

private:
  // Many of these are declared "mutable" so that they can be changed on a "const"
  // object. Ordinarily we don't let const objects be modified, but this allows us to
  // declare certain member function "const" when the function is supposed to be "read-only",
  // but in reality the "read" causes the update of cached values, and those must be 
  // declared "mutable"
  mutable std::string m_strUnit;
  mutable double m_dBigness;
  CUEVecType m_CUEVec;
  mutable CUnitDimension *m_CUD;
  mutable bool m_bStaleBigness;
  mutable bool m_bStaleDimension;
  mutable bool m_bExplicitNonDBFlag;
  mutable bool m_bExplicitDBFlag;
  mutable bool m_bDBFlag;
};

inline CCompoundUnit pow(const CCompoundUnit &baseref, CCompoundUnitElement::ExponentType exp)
{
  // Construct a "C++ temporary" that we modify by raising to the power.
  // It's the same principle that guides the implementation of operator*
  // (see above) as described by Myers, except that there is no operator
  // that corresponds to raising to a power, so we use an ordinary
  // function instead.

  return (CCompoundUnit(baseref)).Raise(exp);
}

inline CCompoundUnit sqrt(const CCompoundUnit &argref)
{
  return pow(argref, 0.5);
}

inline std::ostream & operator<<(std::ostream &out, const CCompoundUnit &ccu)
{
  //return ccu.PrintSelf(output);
  out << ccu.GetString();
  return out;
}
inline std::ostream& operator<< (std::ostream& out, const CCompoundUnit* ccu)
{
  if (ccu == nullptr)
    out << "";
  else
    out << ccu->GetString();
  return out;
}
