/*

Copyright © 2023-26 Sean Holden. All rights reserved.

*/
/*

This file is part of Connect++.

Connect++ is free software: you can redistribute it and/or modify it 
under the terms of the GNU General Public License as published by the 
Free Software Foundation, either version 3 of the License, or (at your 
option) any later version.

Connect++ 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 General Public License for 
more details.

You should have received a copy of the GNU General Public License along 
with Connect++. If not, see <https://www.gnu.org/licenses/>. 

*/

#pragma once

#include "StackItem.hpp"

/**
* \brief If you implement the stacks used by the prover as a 
*        vector<StackItem> then every time you move down the stack 
*        a StackItem destructor is called. This ends up eating about 
*        1/6th of the runtime, and is completely unnecessary.
* 
* This class just wraps a stack up in such a way that StackItems 
* get left in place when moving down the stack, and overwritten 
* when moving up. A bunch of space is initialised at the outset 
* and extended whenever necessary.
*/
class Stack {
private:
    /**
    * \brief The idea is to store the stack here, but to never actually remove 
    *        anything from it, so we avoid calling the destructor for StackItem.
    */
    StackItem* stack;
    /**
    * \brief Keep track of where the next empty space is.
    */
    size_t next_item_index;
    /**
    * \brief Keep track of how much space we have. If we need more 
    *        then extend as needed.
    */
    size_t capacity;
    /**
    * \brief Make extra space later if needed.
    */
    void extend(size_t _n) {
        size_t old_capacity = capacity;
        // _n == 0 is interpreted as "double the capacity".
        _n == 0 ? capacity *= 2 : capacity += _n;
        StackItem* old_stack = stack;
        stack = new StackItem [capacity];
        for (size_t i = 0; i < old_capacity; i++) {
            stack[i] = old_stack[i];
        }
        delete [] old_stack;
    }
public:
    /**
    * \brief The constructor is straightforward - just make some initial space in 
    *        the vector.
    */
    Stack() : stack(), next_item_index(0), capacity(params::stack_start_size) { 
        stack = new StackItem [params::stack_start_size];
    };
    ~Stack() { delete [] stack; }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline void clear() {
        next_item_index = 0;
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline bool empty() const {
        return next_item_index == 0;
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline size_t size() const {
        return next_item_index;
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline void push_back(const StackItem& _item) {
        if (next_item_index == capacity) {
            extend(params::stack_increment);
        }
        stack[next_item_index++] = _item;
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline StackItem& back() {
        return stack[next_item_index - 1];
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    *        BUT - importantly - avoid calling the StackItem destructor.
    */
    inline void pop_back() {
        next_item_index--;
    }
    /**
    * \brief Exact analogue of the same function for vector<>.
    */
    inline StackItem& operator[](size_t _i) {
        return stack[_i];
    }
    /**
    * \brief (Almost!) exact analogue of the same function for vector<>.
    *
    * Strictly speaking it doesn't do *exactly* the same thing, because 
    * we *only* want to copy into existing storage, without risking actually 
    * constructing (and destructing) a StackItem.
    */
    inline void emplace_back(StackItemType sit,
                      const Clause& _c,
                      const SimplePath& _p,
                      const Lemmata& _l,
                      uint32_t _d) {
        if (next_item_index == capacity) {
            extend(params::stack_increment);
        }
        StackItem* si = stack + next_item_index;
        si->item_type = sit;
        si->c = _c;
        si->p = _p;
        si->l = _l;
        si->sub.clear();
        si->depth = _d;
        si->bt_restriction_index = 0;
        si->stack_item_setup();
        next_item_index++;
    }
    /**
    * \brief (Almost!) exact analogue of the same function for vector<>.
    *
    * Strictly speaking it doesn't do *exactly* the same thing, because 
    * we *only* want to copy into existing storage, without risking actually 
    * constructing (and destructing) a StackItem.
    */
    inline void emplace_back(StackItemType sit, 
                      const Clause& _c, 
                      const SimplePath& _p, 
                      const Lemmata& _l,
                      const Substitution& _sub, 
                      uint32_t _d) {
        if (next_item_index == capacity) {
            extend(params::stack_increment);
        }
        StackItem* si = stack + next_item_index;
        si->item_type = sit;
        si->c = _c;
        si->p = _p;
        si->l = _l;
        si->sub = _sub;
        si->depth = _d;
        si->bt_restriction_index = 0;
        si->stack_item_setup();
        next_item_index++;
    }
    friend ostream& operator<<(ostream&, Stack&);
};

