/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef UTIL_CHRE_OPTIONAL_H_
#define UTIL_CHRE_OPTIONAL_H_

#include <type_traits>

namespace chre {

/**
 * This container keeps track of an optional object. The container is similar to
 * std::optional introduced in C++17.
 */
template <typename ObjectType>
class Optional {
 public:
  // Per the standard, a program that instantiates template optional for a
  // reference type is ill-formed
  static_assert(!std::is_reference<ObjectType>::value,
                "Optional references are not allowed");

  /**
   * Default constructs the optional object with no initial value.
   */
  Optional() = default;

  /**
   * Default copy constructor.
   *
   * @param object The object to copy construct from.
   */
  Optional(const Optional<ObjectType> &object) = default;

  /**
   * Default copy constructor.
   *
   * @param object The object to copy construct from.
   */
  Optional(Optional<ObjectType> &object) = default;

  /**
   * Constructs an optional instance with an initial value.
   *
   * @param object The initial value of the object.
   */
  Optional(const ObjectType &object);

  /**
   * Constructs an optional instance with an initial value by moving it.
   *
   * @param object The instance of the initial object to take ownership of.
   */
  Optional(ObjectType &&object);

  /**
   * Destructs the object. Calls through reset() to destroy the contained
   * object before destructing this container.
   */
  ~Optional();

  /**
   * @return Returns true if this container holds an object
   */
  bool has_value() const;

  /**
   * Destroys any contained object, and marks this Optional as empty (i.e.
   * has_value() will return false after this function returns)
   */
  void reset();

  /**
   * Gets a reference to the contained object. Does not check that this optional
   * contains a value, so this object will be uninitialized if has_value() is
   * false.
   */
  ObjectType &value();
  const ObjectType &value() const;

  /**
   * Performs a move assignment operation to the underlying object managed by
   * this container.
   *
   * @param other The other object to move from.
   * @return Returns a reference to this object.
   */
  Optional<ObjectType> &operator=(ObjectType &&other);

  /**
   * Performs a move assignment from one optional to another. Note that the
   * other object still holds a value, but it is left in the moved-from state
   * (as is the case in std::optional).
   *
   * @param other The other object to move.
   * @return Returns a reference to this object.
   */
  Optional<ObjectType> &operator=(Optional<ObjectType> &&other);

  /**
   * Performs a copy assignment operation to the underlying object managed by
   * this container.
   *
   * @param other The other object to copy from.
   * @return Returns a reference to this object.
   */
  Optional<ObjectType> &operator=(const ObjectType &other);

  /**
   * Performs a copy assignment from one optional to another.
   *
   * @param other The other object to copy.
   * @return Returns a reference to this object.
   */
  Optional<ObjectType> &operator=(const Optional<ObjectType> &other);

  /**
   * Obtains a reference to the underlying object managed by this container.
   * The behavior of this is undefined if has_value() returns false.
   *
   * @return Returns a reference to the underlying object tracked by this
   *         container.
   */
  ObjectType &operator*();

  /**
   * Obtains a const reference to the underlying object managed by this
   * container. The behavior of this is undefined if has_value() returns false.
   *
   * @return Returns a const reference to the underlying object tracked by this
   *         container.
   */
  const ObjectType &operator*() const;

  /**
   * Obtains a pointer to the underlying object managed by this container. The
   * object may not be well-formed if has_value() returns false.
   *
   * @return Returns a pointer to the underlying object tracked by this
   *         container.
   */
  ObjectType *operator->();

  /**
   * Obtains a const pointer to the underlying object managed by this container.
   * The object may not be well-formed if has_value() returns false.
   *
   * @return Returns a const pointer to the underlying object tracked by this
   *         container.
   */
  const ObjectType *operator->() const;

 private:
  //! The optional object being tracked by this container.
  typename std::aligned_storage<sizeof(ObjectType), alignof(ObjectType)>::type
      mObject;

  //! Whether or not the object is set.
  bool mHasValue = false;

  ObjectType &object();
  const ObjectType &object() const;

  ObjectType *objectAddr();
  const ObjectType *objectAddr() const;
};

}  // namespace chre

#include "chre/util/optional_impl.h"

#endif  // UTIL_CHRE_OPTIONAL_H_
