/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2019 Danny Robson <danny@nerdcruft.net>
 */

#pragma once

#include "../thread/primitive.hpp"
#include "../debug/assert.hpp"
#include "../thread/spinlock.hpp"
#include "../view.hpp"

#include <atomic>
#include <vector>
#include <mutex>

#include <cstddef>


namespace cruft::parallel {
    /// A trivial thread safe stack with a fixed capacity.
    ///
    /// This implementation uses a spinlock, so don't try to push large
    /// objects under high contention. In the future it may be replaced with a
    /// different locking mechanism.
    template <typename ValueT>
    class stack {
    public:
        stack (stack const &) = delete;
        stack& operator=(stack const &) = delete;

        stack (stack &&rhs):
            stack (0)
        {
            swap (rhs);
        }

        stack& operator= (stack &&rhs)
        {
            swap (rhs);
            return *this;
        }

        stack clone (void) const
        {
            stack res (capacity ());

            std::lock_guard lk (m_lock);
            res.m_cursor = m_cursor.load ();
            res.m_store  = m_store;

            return res;
        }


        /// Thread safe swapping with the supplied object
        void swap (stack &rhs)
        {
            std::lock_guard lk0 (m_lock);
            std::lock_guard lk1 (rhs.m_lock);

            auto tmp = m_cursor.load ();
            m_cursor = rhs.m_cursor.load ();
            rhs.m_cursor = tmp;

            std::swap (m_store,  rhs.m_store);
            std::swap (m_lock,   rhs.m_lock);
        }


        stack (std::size_t capacity)
            : m_cursor (0)
            , m_store (capacity)
        {
            // NOTE: a capacity of zero must be supported as it forms part of
            //       how we structure the move operations
        }

        /// Move the value from the top of the stack into an output pointer.
        ///
        /// The internal object will be destroyed after the output has been set.
        ///
        /// The value `true` will be returned if the value was successfully
        /// copied. If no object is available for popping, or locking failed,
        /// the value `false` will be returned.
        ///
        /// NOTE: There are no exception guarantees at this time.
        bool pop (ValueT *out)
        {
            std::lock_guard lk (m_lock);
            if (m_cursor == 0)
                return false;

            auto ptr = reinterpret_cast<ValueT*> (&m_store[--m_cursor]);
            *out = std::move (*ptr);
            ptr->~ValueT ();
            return true;
        }


        /// Constructs a ValueT on the top of the stack.
        ///
        /// The value `true` will be returned if the object was successfully
        /// stored. In the event of a lack of capacity, locking failure, or
        /// other error the value `false` will be returned and the user may
        /// retry the operation.
        ///
        /// NOTE: There are no exception guarantees at this time.
        template <typename InputT>
        bool push (InputT &&arg)
        {
            std::lock_guard lk (m_lock);
            if (m_cursor >= m_store.size ())
                return false;

            auto ptr = reinterpret_cast<ValueT *> (&m_store[m_cursor++]);
            new (ptr) ValueT (std::forward<InputT> (arg));
            return true;
        }

        std::size_t capacity (void) const { return m_store.size (); }
        std::size_t size     (void) const { return m_cursor; }
        bool empty (void) const { return m_cursor == 0; }


        void clear (void)
        {
            std::lock_guard lk (m_lock);
            for (auto &raw: store (contract::I_HAVE_LOCKED_THIS_STRUCTURE))
                raw.~ValueT ();
            m_cursor = 0;
        }


        // DO NOT make this enum easier to use. It's supposed to be annoying
        // so that people don't use it.
        enum class contract { I_HAVE_LOCKED_THIS_STRUCTURE };

        /// Returns the currently available data.
        ///
        /// Data is laid out bottom-to-top of the stack.
        ///
        /// DO NOT call this if there are any other potential clients
        /// accessing the structure as it will not be protected by locks.
        cruft::view<ValueT*> store (contract)
        {
            auto begin = reinterpret_cast<ValueT*> (m_store.data ());
            auto end   = begin + m_cursor.load ();

            return cruft::view<ValueT*> { begin, end };
        }

    private:
        using index_type = std::size_t;
        using raw_type = std::aligned_storage_t<sizeof(ValueT), alignof(ValueT)>;

        mutable cruft::thread::spinlock m_lock;
        std::atomic<index_type> m_cursor;
        std::vector<raw_type> m_store;
    };
}