//===-- MIUtilVariant.h -----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#pragma once

// In-house headers:
#include "MIDataTypes.h"

//++
//============================================================================
// Details: MI common code utility class. The class implements behaviour of a
//          variant object which holds any data object of type T. A copy of the
//          data object specified is made and stored in *this wrapper. When the
//          *this object is destroyed the data object hold within calls its
//          destructor should it have one.
//--
class CMIUtilVariant {
  // Methods:
public:
  /* ctor */ CMIUtilVariant();
  /* ctor */ CMIUtilVariant(const CMIUtilVariant &vrOther);
  /* ctor */ CMIUtilVariant(CMIUtilVariant &vrOther);
  /* ctor */ CMIUtilVariant(CMIUtilVariant &&vrwOther);
  /* dtor */ ~CMIUtilVariant();

  template <typename T> void Set(const T &vArg);
  template <typename T> T *Get() const;

  CMIUtilVariant &operator=(const CMIUtilVariant &vrOther);
  CMIUtilVariant &operator=(CMIUtilVariant &&vrwOther);

  // Classes:
private:
  //++ ----------------------------------------------------------------------
  // Details: Base class wrapper to hold the variant's data object when
  //          assigned to it by the Set() function. Do not use the
  //          CDataObjectBase
  //          to create objects, use only CDataObjectBase derived objects,
  //          see CDataObject() class.
  //--
  class CDataObjectBase {
    // Methods:
  public:
    /* ctor */ CDataObjectBase();
    /* ctor */ CDataObjectBase(const CDataObjectBase &vrOther);
    /* ctor */ CDataObjectBase(CDataObjectBase &vrOther);
    /* ctor */ CDataObjectBase(CDataObjectBase &&vrwOther);
    //
    CDataObjectBase &operator=(const CDataObjectBase &vrOther);
    CDataObjectBase &operator=(CDataObjectBase &&vrwOther);

    // Overrideable:
  public:
    virtual ~CDataObjectBase();
    virtual CDataObjectBase *CreateCopyOfSelf();
    virtual bool GetIsDerivedClass() const;

    // Overrideable:
  protected:
    virtual void Copy(const CDataObjectBase &vrOther);
    virtual void Destroy();
  };

  //++ ----------------------------------------------------------------------
  // Details: Derived from CDataObjectBase, this class is the wrapper for the
  //          data object as it has an aggregate of type T which is a copy
  //          of the data object assigned to the variant object.
  //--
  template <typename T> class CDataObject : public CDataObjectBase {
    // Methods:
  public:
    /* ctor */ CDataObject();
    /* ctor */ CDataObject(const T &vArg);
    /* ctor */ CDataObject(const CDataObject &vrOther);
    /* ctor */ CDataObject(CDataObject &vrOther);
    /* ctor */ CDataObject(CDataObject &&vrwOther);
    //
    CDataObject &operator=(const CDataObject &vrOther);
    CDataObject &operator=(CDataObject &&vrwOther);
    //
    T &GetDataObject();

    // Overridden:
  public:
    // From CDataObjectBase
    ~CDataObject() override;
    CDataObjectBase *CreateCopyOfSelf() override;
    bool GetIsDerivedClass() const override;

    // Overrideable:
  private:
    virtual void Duplicate(const CDataObject &vrOther);

    // Overridden:
  private:
    // From CDataObjectBase
    void Destroy() override;

    // Attributes:
  private:
    T m_dataObj;
  };

  // Methods
private:
  void Destroy();
  void Copy(const CMIUtilVariant &vrOther);

  // Attributes:
private:
  CDataObjectBase *m_pDataObject;
};


//++
// Details: CDataObject constructor.
// Type:    Method.
// Args:    T   - The object's type.
// Return:  None.
// Throws:  None.
//--
template <typename T> CMIUtilVariant::CDataObject<T>::CDataObject() {}

//++
// Details: CDataObject constructor.
// Type:    Method.
// Args:    T       - The object's type.
//          vArg    - (R) The data object to be stored in the variant object.
// Return:  None.
// Throws:  None.
//--
template <typename T>
CMIUtilVariant::CDataObject<T>::CDataObject(const T &vArg) {
  m_dataObj = vArg;
}

//++
// Details: CDataObject destructor.
// Type:    Overridden.
// Args:    T   - The object's type.
// Return:  None.
// Throws:  None.
//--
template <typename T> CMIUtilVariant::CDataObject<T>::~CDataObject() {
  Destroy();
}

//++
// Details: Retrieve the data object hold by *this object wrapper.
// Type:    Method.
// Args:    T   - The object's type.
// Return:  T & - Reference to the data object.
// Throws:  None.
//--
template <typename T> T &CMIUtilVariant::CDataObject<T>::GetDataObject() {
  return m_dataObj;
}

//++
// Details: Create a new copy of *this class.
// Type:    Overridden.
// Args:    T   - The object's type.
// Return:  CDataObjectBase *   - Pointer to a new object.
// Throws:  None.
//--
template <typename T>
CMIUtilVariant::CDataObjectBase *
CMIUtilVariant::CDataObject<T>::CreateCopyOfSelf() {
  CDataObject *pCopy = new CDataObject<T>(m_dataObj);

  return pCopy;
}

//++
// Details: Determine if *this object is a derived from CDataObjectBase.
// Type:    Overridden.
// Args:    T   - The object's type.
// Return:  bool    - True = *this is derived from CDataObjectBase
//                  - False = *this is an instance of the base class.
// Throws:  None.
//--
template <typename T>
bool CMIUtilVariant::CDataObject<T>::GetIsDerivedClass() const {
  return true;
}

//++
// Details: Perform a bitwise copy of *this object.
// Type:    Overrideable.
// Args:    T       - The object's type.
//          vrOther - (R) The other object.
// Return:  None.
// Throws:  None.
//--
template <typename T>
void CMIUtilVariant::CDataObject<T>::Duplicate(const CDataObject &vrOther) {
  CDataObjectBase::Copy(vrOther);
  m_dataObj = vrOther.m_dataObj;
}

//++
// Details: Release any resources used by *this object.
// Type:    Overridden.
// Args:    None.
// Return:  None.
// Throws:  None.
//--
template <typename T> void CMIUtilVariant::CDataObject<T>::Destroy() {
  CDataObjectBase::Destroy();
}


//++
// Details: Assign to the variant an object of a specified type.
// Type:    Template method.
// Args:    T       - The object's type.
//          vArg    - (R) The object to store.
// Return:  None.
// Throws:  None.
//--
template <typename T> void CMIUtilVariant::Set(const T &vArg) {
  m_pDataObject = new CDataObject<T>(vArg);
}

//++
// Details: Retrieve the data object from *this variant.
// Type:    Template method.
// Args:    T   - The object's type.
// Return:  T * - Pointer the data object, NULL = data object not assigned to
// *this variant.
// Throws:  None.
//--
template <typename T> T *CMIUtilVariant::Get() const {
  if ((m_pDataObject != nullptr) && m_pDataObject->GetIsDerivedClass()) {
    CDataObject<T> *pDataObj = static_cast<CDataObject<T> *>(m_pDataObject);
    return &pDataObj->GetDataObject();
  }

  // Do not use a CDataObjectBase object, use only CDataObjectBase derived
  // objects
  return nullptr;
}
