/*
 * This file is part of libsh4lt.
 *
 * libsh4lt is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#ifndef SH4LT_ANY_H_
#define SH4LT_ANY_H_

#include "./any_defs.hpp"
#include "./safe-bool-log.hpp"
#include <sstream>
#include <string>
#include <type_traits>
#include <typeinfo>
#include <utility>

namespace sh4lt {

/*!
 * The Any class provides type erasure with serialization capability and
 * categorization of the "erased" type (floating point, integral, boolean, other
 * or none).
 */

class Any : public SafeBoolLog {
public:

  /**
   * Checks whether the instance is empty
   *
   * \return true if empty.
   */
  [[nodiscard]] auto empty() const -> bool;

  /**
   * Get the type category of the holded.
   *
   * \return The category.
   */
  [[nodiscard]] auto get_category() const -> AnyCategory;

  /**
   * Serialize the content.
   *
   * \param any The object to serialize.
   *
   * \return The serialization.
   */
  static auto to_string(const Any &any) -> std::string;

  /**
   * Construct an empty Any.
   */
  Any() = default;

  /**
   * Construt a clone.
   *
   * \param that The Any cloned.
   */
  Any(Any& that) noexcept;

  /**
   * Construt an new any through acquisition.
   *
   * \param that The Any to acquire.
   */
  Any(Any &&that) noexcept ;

  /**
   * Construt a clone.
   *
   * \param that The Any cloned.
   */
  Any(const Any& that) noexcept;

  /**
   * Construt a clone.
   *
   * \param that The Any cloned.
   */
  Any(const Any&& that) noexcept;

  /**
   * Construct an empty Any.
   *
   * \param value A null pointer.
   */
  Any(std::nullptr_t value) noexcept;

  ~Any() override;

  /**
   * Construct with an boolean to hold.
   *
   * \param value The value to hold.
   */
  template <typename U = bool>
  Any(bool value)
      : ptr_(new AnyValueDerived<StorageType<U>>(std::forward<U>(value))) {
    ptr_->category_ = AnyCategory::BOOLEAN;
  }

  /**
   * Construct with a character to hold.
   *
   * \param value The value to hold.
   */
  template <typename U = char>
  Any(char value)
      : ptr_(new AnyValueDerived<StorageType<U>>(std::forward<U>(value))) {
    ptr_->category_ = AnyCategory::OTHER;
  }

  /**
   * Construct with an integral to hold.
   *
   * \param value The value to hold.
   */
  template <typename U>
  Any(U &&value,
      typename std::enable_if<!std::is_same<U, bool>::value &&
                              !std::is_same<U, char>::value &&
                              std::is_integral<U>::value>::type * = nullptr)
      : ptr_(new AnyValueDerived<StorageType<U>>(std::forward<U>(value))) {
    ptr_->category_ = AnyCategory::INTEGRAL;
    if constexpr (std::is_same<U, int>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::INT;
    else if constexpr (std::is_same<U, short>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::SHORT;
    else if constexpr (std::is_same<U, long>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::LONG;
    else if constexpr (std::is_same<U, long long>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::LONG_LONG;
    else if constexpr (std::is_same<U, unsigned int>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::UNSIGNED_INT;
    else if constexpr (std::is_same<U, unsigned short>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::UNSIGNED_SHORT;
    else if constexpr (std::is_same<U, unsigned long>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::UNSIGNED_LONG;
    else if constexpr (std::is_same<U, unsigned long long>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::UNSIGNED_LONG_LONG;
    else {
      ptr_->arithmetic_type_ = AnyArithmeticType::NOT_DEFINED;
      add_log("unassigned arithmetic_type_ integral");
    }
  }

  /**
   * Construct with an floating point value to hold.
   *
   * \param value The value to hold.
   */
  template <typename U>
  Any(U &&value,
      typename std::enable_if<!std::is_same<U, bool>::value &&
                              std::is_floating_point<U>::value>::type * =
          nullptr)
      : ptr_(new AnyValueDerived<StorageType<U>>(std::forward<U>(value))) {
    ptr_->category_ = AnyCategory::FLOATING_POINT;
    if constexpr (std::is_same<U, float>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::FLOAT;
    else if constexpr (std::is_same<U, double>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::DOUBLE;
    else if constexpr (std::is_same<U, long double>::value)
      ptr_->arithmetic_type_ = AnyArithmeticType::LONG_DOUBLE;
    else {
      ptr_->arithmetic_type_ = AnyArithmeticType::NOT_DEFINED;
      add_log("unassigned arithmetic_type_ float");
    }
  }

  /**
   * Construct with an object to hold (non arithmetic, non boolean, non char).
   *
   * \param value An object to hold in the Any.
   */
  template <typename U>
  Any(U &&value,
      typename std::enable_if<!std::is_arithmetic<U>::value>::type * = nullptr)
      : ptr_(new AnyValueDerived<StorageType<U>>(std::forward<U>(value))) {
    ptr_->category_ = AnyCategory::OTHER;
  }

  /**
   * Template that test if a value holds a given type.
   *
   * \tparam U The type to test against.
   *
   * \return true if U is the same type as the holded object.
   */
  template <class U> [[nodiscard]] auto is() const -> bool {
    using T = int;
    auto derived = dynamic_cast<AnyValueDerived<T> *>(ptr_);
    return derived;
  }

  /**
   * Access the internal object casted as a specific type.
   *
   * \tparam U The type to cast against.
   *
   * \return reference to the internal object.
   */
  template <class U>
  [[nodiscard]] auto as() const -> StorageType<U>& {
    using T = StorageType<U>;
    auto derived = dynamic_cast<AnyValueDerived<T> *>(ptr_);
    if (!derived)
      return *new U;
    return derived->value_;
  }

  /**
   * Get a copy of the internal object, casted to a specific type.
   * (for non arithmetic objects)
   *
   * \tparam U The type to cast against.
   *
   * \return A copy of the holded object.
   **/
  template <class U,
            typename std::enable_if<!std::is_arithmetic<U>::value &&
                                    !std::is_same<U, bool>::value>::type* = nullptr>
  [[nodiscard]] auto copy_as() const -> StorageType<U> {
    if (AnyCategory::OTHER != ptr_->category_)
      add_log("error copying back any value (is not other)");
    using T = StorageType<U>;
    auto derived = dynamic_cast<AnyValueDerived<T> *>(ptr_);
    if (!derived)
      return U();
    StorageType<U> res = derived->value_;
    return res;
  }

  /**
   * Get a copy of the internal object, casted to a specific type.
   * (for boolean objects)
   *
   * \tparam U The type to cast against.
   *
   * \return A copy of the holded object.
   **/
  template <typename U, typename std::enable_if<
                            std::is_same<U, bool>::value>::type * = nullptr>
  auto copy_as() const -> U {
    if (AnyCategory::BOOLEAN != ptr_->category_) {
      add_log("error copying back any value (is not other)");
      return false;
    }
    return dynamic_cast<AnyValueDerived<bool>*>(ptr_)->value_;
  }

  /**
   * Get a copy of the internal object, casted to a specific type.
   * (for integral objects)
   *
   * \tparam U The type to cast against.
   *
   * \return A copy of the holded object.
   **/
  template <class U,
            typename std::enable_if<std::is_integral<U>::value &&
                                    !std::is_same<U, bool>::value>::type* = nullptr>
  [[nodiscard]] auto copy_as() const -> StorageType<U>{
    if (AnyCategory::FLOATING_POINT != ptr_->category_ &&
        AnyCategory::INTEGRAL != ptr_->category_) {
      add_log(
          "error copying back any value (is not floating point or integral)");
      return 0;
    }
    if (AnyArithmeticType::INT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<int> *>(ptr_)->value_;
    else if (AnyArithmeticType::SHORT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<short> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long long> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_INT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned int> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_SHORT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned short> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned long> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_LONG_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned long long> *>(ptr_)->value_;
    else if (AnyArithmeticType::FLOAT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<float> *>(ptr_)->value_;
    else if (AnyArithmeticType::DOUBLE == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<double> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG_DOUBLE == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long double> *>(ptr_)->value_;
    add_log("bug in copy_as for integral");
    return 0;
  }

  /**
   * Get a copy of the internal object, casted to a specific type.
   * (for floating point objects)
   *
   * \tparam U The type to cast against.
   *
   * \return A copy of the holded object.
   **/
  template <class U,
            typename std::enable_if<std::is_floating_point<U>::value &&
                                    !std::is_same<U, bool>::value>::type* = nullptr>
  [[nodiscard]] auto copy_as() const -> StorageType<U>{
    if (AnyCategory::FLOATING_POINT != ptr_->category_ &&
        AnyCategory::INTEGRAL != ptr_->category_) {
      add_log(
          "error copying back any value (is not floating point or integral)");
      return 0;
    }
    if (AnyArithmeticType::INT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<int> *>(ptr_)->value_;
    else if (AnyArithmeticType::SHORT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<short> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long long> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_INT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned int> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_SHORT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned short> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned long> *>(ptr_)->value_;
    else if (AnyArithmeticType::UNSIGNED_LONG_LONG == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<unsigned long long> *>(ptr_)->value_;
    else if (AnyArithmeticType::FLOAT == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<float> *>(ptr_)->value_;
    else if (AnyArithmeticType::DOUBLE == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<double> *>(ptr_)->value_;
    else if (AnyArithmeticType::LONG_DOUBLE == ptr_->arithmetic_type_)
      return dynamic_cast<AnyValueDerived<long double> *>(ptr_)->value_;

    add_log("bug in copy_as for floating point");
    return 0.f;
  }

  template <class U> operator U() const { return as<StorageType<U>>(); }

  auto operator=(const Any &a) -> Any &;

  auto operator=(Any&& a) noexcept -> Any&;

 private:
  [[nodiscard]] auto clone() const -> AnyValueBase*;
  AnyValueBase* ptr_{};
  friend auto operator<<(std::ostream& os, const Any& any) -> std::ostream&;
  [[nodiscard]] auto is_valid() const -> bool final;
};

} // namespace sh4lt
#endif
