#pragma once

#include <memory>
#include <type_traits>
#include <initializer_list>

namespace NMaybe {
    struct TInPlace {};

    template <class T, bool = std::is_trivially_destructible<T>::value>
    struct TStorageBase {
        constexpr TStorageBase() noexcept
            : NullState_('\0')
        {
        }

        template <class... Args>
        constexpr TStorageBase(TInPlace, Args&&... args)
            : Data_(std::forward<Args>(args)...)
            , Defined_(true)
        {
        }

        constexpr TStorageBase(TStorageBase&&) = default;
        constexpr TStorageBase(const TStorageBase&) = default;

        ~TStorageBase() = default;

        TStorageBase& operator=(const TStorageBase&) = default;
        TStorageBase& operator=(TStorageBase&&) = default;

        union {
            char NullState_;
            T Data_;
        };
        bool Defined_ = false;
    };

    template <class T>
    struct TStorageBase<T, false> {
        constexpr TStorageBase() noexcept
            : NullState_('\0')
        {
        }

        template <class... Args>
        constexpr TStorageBase(TInPlace, Args&&... args)
            : Data_(std::forward<Args>(args)...)
            , Defined_(true)
        {
        }

        constexpr TStorageBase(TStorageBase&&) = default;
        constexpr TStorageBase(const TStorageBase&) = default;

        ~TStorageBase() {
            if (this->Defined_) {
                this->Data_.~T();
            }
        }

        TStorageBase& operator=(const TStorageBase&) = default;
        TStorageBase& operator=(TStorageBase&&) = default;

        union {
            char NullState_;
            T Data_;
        };
        bool Defined_ = false;
    };

    // -------------------- COPY CONSTRUCT --------------------

    template <class T, bool = std::is_trivially_copy_constructible<T>::value>
    struct TCopyBase: TStorageBase<T> {
        using TStorageBase<T>::TStorageBase;
    };

    template <class T>
    struct TCopyBase<T, false>: TStorageBase<T> {
        using TStorageBase<T>::TStorageBase;

        constexpr TCopyBase() = default;
        constexpr TCopyBase(const TCopyBase& rhs) {
            if (rhs.Defined_) {
                new (std::addressof(this->Data_)) T(rhs.Data_);
                this->Defined_ = true;
            }
        }
        constexpr TCopyBase(TCopyBase&&) = default;
        TCopyBase& operator=(const TCopyBase&) = default;
        TCopyBase& operator=(TCopyBase&&) = default;
    };

    // -------------------- MOVE CONSTRUCT --------------------

    template <class T, bool = std::is_trivially_move_constructible<T>::value>
    struct TMoveBase: TCopyBase<T> {
        using TCopyBase<T>::TCopyBase;
    };

    template <class T>
    struct TMoveBase<T, false>: TCopyBase<T> {
        using TCopyBase<T>::TCopyBase;

        constexpr TMoveBase() noexcept = default;
        constexpr TMoveBase(const TMoveBase&) = default;
        constexpr TMoveBase(TMoveBase&& rhs) noexcept(std::is_nothrow_move_constructible<T>::value) {
            if (rhs.Defined_) {
                new (std::addressof(this->Data_)) T(std::move(rhs.Data_));
                this->Defined_ = true;
            }
        }
        TMoveBase& operator=(const TMoveBase&) = default;
        TMoveBase& operator=(TMoveBase&&) = default;
    };

    // -------------------- COPY ASSIGN --------------------

    template <class T, bool = std::is_trivially_copy_assignable<T>::value>
    struct TCopyAssignBase: TMoveBase<T> {
        using TMoveBase<T>::TMoveBase;
    };

    template <class T>
    struct TCopyAssignBase<T, false>: TMoveBase<T> {
        using TMoveBase<T>::TMoveBase;

        constexpr TCopyAssignBase() noexcept = default;
        constexpr TCopyAssignBase(const TCopyAssignBase&) = default;
        constexpr TCopyAssignBase(TCopyAssignBase&&) = default;
        TCopyAssignBase& operator=(const TCopyAssignBase& rhs) {
            if (this->Defined_) {
                if (rhs.Defined_) {
                    this->Data_ = rhs.Data_;
                } else {
                    this->Data_.~T();
                    this->Defined_ = false;
                }
            } else if (rhs.Defined_) {
                new (std::addressof(this->Data_)) T(rhs.Data_);
                this->Defined_ = true;
            }
            return *this;
        }
        TCopyAssignBase& operator=(TCopyAssignBase&&) = default;
    };

    // -------------------- MOVE ASSIGN --------------------

    template <class T, bool = std::is_trivially_move_assignable<T>::value>
    struct TMoveAssignBase: TCopyAssignBase<T> {
        using TCopyAssignBase<T>::TCopyAssignBase;
    };

    template <class T>
    struct TMoveAssignBase<T, false>: TCopyAssignBase<T> {
        using TCopyAssignBase<T>::TCopyAssignBase;

        constexpr TMoveAssignBase() noexcept = default;
        constexpr TMoveAssignBase(const TMoveAssignBase&) = default;
        constexpr TMoveAssignBase(TMoveAssignBase&&) = default;
        TMoveAssignBase& operator=(const TMoveAssignBase&) = default;
        TMoveAssignBase& operator=(TMoveAssignBase&& rhs) noexcept(
            std::is_nothrow_move_assignable<T>::value&&
                std::is_nothrow_move_constructible<T>::value)
        {
            if (this->Defined_) {
                if (rhs.Defined_) {
                    this->Data_ = std::move(rhs.Data_);
                } else {
                    this->Data_.~T();
                    this->Defined_ = false;
                }
            } else if (rhs.Defined_) {
                new (std::addressof(this->Data_)) T(std::move(rhs.Data_));
                this->Defined_ = true;
            }
            return *this;
        }
    };
}

template <class T>
using TMaybeBase = NMaybe::TMoveAssignBase<T>;