/*
   +----------------------------------------------------------------------+
   | HipHop for PHP                                                       |
   +----------------------------------------------------------------------+
   | Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com)     |
   +----------------------------------------------------------------------+
   | This source file is subject to version 3.01 of the PHP license,      |
   | that is bundled with this package in the file LICENSE, and is        |
   | available through the world-wide-web at the following url:           |
   | http://www.php.net/license/3_01.txt                                  |
   | If you did not receive a copy of the PHP license and are unable to   |
   | obtain it through the world-wide-web, please send a note to          |
   | license@php.net so we can mail you a copy immediately.               |
   +----------------------------------------------------------------------+
*/

#ifndef incl_HPHP_APC_TYPED_VALUE_H_
#define incl_HPHP_APC_TYPED_VALUE_H_

#include "hphp/runtime/base/types.h"
#include "hphp/runtime/base/apc-handle.h"

namespace HPHP {
///////////////////////////////////////////////////////////////////////////////

/*
 * APCTypedValue is the APC object that holds non refcounted data. That is,
 * primitive values and static strings.
 * APCTypedValue has the same layout as TypedValue so it can be returned in
 * some cases and be used directly without any other allocation (see
 * APCLocalArray::getValueRef()). See comment below for more details.
 */
class APCTypedValue {
public:
  static APCHandle* MakeSharedArray(ArrayData* array);

  APCTypedValue(DataType type, int64_t data) : m_handle(type) {
    m_data.num = data;
  }

  APCTypedValue(DataType type, double data) : m_handle(type) {
    m_data.dbl = data;
  }

  APCTypedValue(DataType type, StringData* data) : m_handle(type) {
    m_data.str = data;
  }

  explicit APCTypedValue(StringData* data) : m_handle(KindOfString) {
    m_handle.setUncounted();
    m_data.str = data;
  }

  explicit APCTypedValue(ArrayData* data) : m_handle(KindOfArray) {
    m_handle.setUncounted();
    m_data.arr = data;
  }

  explicit APCTypedValue(DataType type) : m_handle(type) {
    m_data.num = 0;
  }

  static APCTypedValue* fromHandle(APCHandle* handle) {
    return reinterpret_cast<APCTypedValue*>(handle - 1);
  }

  static const APCTypedValue* fromHandle(const APCHandle* handle) {
    return reinterpret_cast<const APCTypedValue*>(handle - 1);
  }

  APCHandle* getHandle() {
    return &m_handle;
  }

  bool getBoolean() const {
    assert(m_handle.type() == KindOfBoolean);
    return m_data.num != 0;
  }

  int64_t getInt64() const {
    assert(m_handle.type() == KindOfInt64);
    return m_data.num;
  }

  double getDouble() const {
    assert(m_handle.type() == KindOfDouble);
    return m_data.dbl;
  }

  StringData* getStringData() const {
    assert(m_handle.type() == KindOfStaticString ||
           (m_handle.isUncounted() && m_handle.type() == KindOfString));
    return m_data.str;
  }

  ArrayData* getArrayData() const {
    assert(m_handle.isUncounted() && m_handle.type() == KindOfArray);
    return m_data.arr;
  }

  const Variant& asCVarRef() const {
    // Must be non-refcounted types
    assert(m_handle.m_flags == 0);
    assert(!IS_REFCOUNTED_TYPE(m_handle.m_type));
    return tvAsCVarRef(reinterpret_cast<const TypedValue*>(this));
  }

  void deleteUncounted();

private:
  friend struct APCHandle;

  APCTypedValue(const APCTypedValue&) = delete;
  APCTypedValue& operator=(const APCTypedValue&) = delete;

private:

  /*
   * Keep the object layout binary compatible with Variant for primitive types.
   * For non-refcounted types m_flags is guaranteed to be 0, and other parts of
   * runtime will not touch the count.
   *
   * Note that this is assuming a little-endian system: m_unused_space and
   * m_flags have to overlay the higher-order bits of TypedValue::m_type.
   */

  union SharedData {
    int64_t num;
    double dbl;
    StringData* str;
    ArrayData* arr;
  };

  SharedData m_data;
  APCHandle m_handle;

  static void compileTimeAssertions() {
    static_assert(
      offsetof(APCTypedValue, m_handle) == sizeof(SharedData),
      "m_handle must come after SharedData"
    );
    static_assert(
        offsetof(APCTypedValue, m_data) == offsetof(TypedValue, m_data),
        "Offset of m_data must be equal in APCHandle and TypedValue");
    static_assert(
        offsetof(APCTypedValue, m_handle) + offsetof(APCHandle, m_count) ==
            TypedValueAux::auxOffset,
        "Offset of m_count must equal offset of TV.m_aux");
    static_assert(
        offsetof(APCTypedValue, m_handle) + offsetof(APCHandle, m_type) ==
            offsetof(TypedValue, m_type),
        "Offset of m_type must be equal in APCHandle and TypedValue");
    static_assert(
        sizeof(APCTypedValue) == sizeof(TypedValue),
        "Be careful with field layout");
  }
};

///////////////////////////////////////////////////////////////////////////////
}

#endif /* incl_HPHP_APC_TYPED_VALUE_H_ */
