/*********************************************************************/
// dar - disk archive - a backup/restoration program
// Copyright (C) 2002-2022 Denis Corbin
//
// This program 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 2
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// to contact the author, see the AUTHOR file
/*********************************************************************/

    /// \file pile.hpp
    /// \brief class pile definition. Used to manage a stack of generic_file objects
    /// \ingroup Private


#ifndef PILE_HPP
#define PILE_HPP

#include "../my_config.h"

#include <list>
#include "generic_file.hpp"

namespace libdar
{

	/// \addtogroup Private
	/// @{

	/// stores a stack of generic_files writing/reading on each others

    class pile : public generic_file
    {
    public:
	    /// the constructor

	    /// \note the mode (gf_mode) of the pile is the one of the first object pushed on the stack,
	    /// thus when empty we choose the arbitrary gf_read_only value, because the stack is empty.

	pile() : generic_file(gf_read_only) { stack.clear(); };
	pile(const pile & ref) = delete;
	pile(pile && ref) noexcept = delete;
	pile & operator = (const pile & ref) = delete;
	pile & operator = (pile && ref) noexcept = delete;
	~pile() { detruit(); };

	    /// add a generic_file on the top

	    /// \param[in] f is the address of the object to add to the stack
	    /// \param[in] label unique label associated to this object in the current stack, exception thrown if label already used in stack
	    /// \param[in] extend_mode allow the new object to have more read/write permission than the already placed object, which have the effect to change the read/write permission of the stack itself, future push() will accept wider permission even if extend_mode is not set
	    /// \note once added, the object memory allocation is managed by the pile object
	    /// the pile is responsible of destroying this object if its destructor is called
	    /// however, the pile object releases its responsibility about any object that
	    /// will be poped (see pop() below) from the stack.
	    /// \note empty label (empty string) is the only identical value that can be used for several objects of the stack
	void push(generic_file *f, const std::string & label = "", bool extend_mode = false);

	    /// remove the top generic_file from the top

	    /// \returns the address of the object that has been poped from the stack or nullptr if the stack is empty
	    /// \note this is now the duty of the caller to release this object memory when the object is no more needed
	generic_file *pop();

	    /// remove the top generic_file and destroy it

	    /// \param[in] ptr is the type of the object that must be found on the top of the stack,
	    /// It may also be the type of a parent class. Note that the value of the pointer is ignored.
	    /// \return true if and only if the object on the top of the stack could be matched to the given type, this object is then poped from the stack and destroyed.
	template <class T> bool pop_and_close_if_type_is(T *ptr);

	    /// returns the address of the top generic_file
	generic_file *top() const { if(stack.empty()) return nullptr; else return stack.back().ptr; };

	    /// returns the address of the bottom generic_file
	generic_file *bottom() const { if(stack.empty()) return nullptr; else return stack[0].ptr; };

	    /// returns the number of objects in the stack
	U_I size() const { return stack.size(); };

	    /// returns true if the stack is empty, false otherwise.
	bool is_empty() const { return stack.empty(); };

	    /// clears the stack
	void clear() { detruit(); };

	    /// this template let the class user find out the higher object on the stack of the given type

	    /// \param[in,out] ref gives the type of the object to look for, and gets the address of the first object found starting from the top
	    /// \note if no object of that type could be found in the stack ref is set to nullptr
	template<class T> void find_first_from_top(T * & ref) const;

	    /// this template is similar to the template "find_first_from_top" except that the search is started from the bottom of the stack
	template<class T> void find_first_from_bottom(T * & ref) const;


	    /// return the generic_file object just below the given object or nullptr if the object is at the bottom of the stack or is not in the stack
	generic_file *get_below(const generic_file *ref);

	    /// return the generic_file object just above the given object or nullptr if the object is at the bottom of the stack or is not in the stack
	generic_file *get_above(const generic_file *ref);


	    /// find the object associated to a given label

	    /// \param[in] label is the label to look for, empty string is forbidden
	    /// \return the object associated to label, else an exception is thrown
	generic_file *get_by_label(const std::string & label);



	    /// if label is associated to a member of the stack, makes this member of the stack an anoymous member (the label is no more associated to this object, while this object stays in the stack untouched

	    /// \param[in] label the label to clear, empty string is not a valid label an exception is thrown if used here
	    /// \note no exception is thrown else, even if the label is not present in the stack
	void clear_label(const std::string & label);


	    /// associate a additional label to the object currently at the top of the stack

	    /// \param[in] label the label to add
	    /// \note this does not remove an eventually existing label that had been added either by push() or add_label() previously
	    /// \note an object of the stack can thus be refered by several different labels
	void add_label(const std::string & label);


	    /// call the generic_file::sync_write() method of all object found above ptr in the stack
	void sync_write_above(generic_file *ptr);

	    /// call the generic_file::flush_read() method of all objects found above ptr in the stack
	void flush_read_above(generic_file *ptr);

	    // inherited methods from generic_file
	    // they all apply to the top generic_file object, they fail by Erange() exception if the stack is empty

	virtual bool skippable(skippability direction, const infinint & amount) override;
	virtual bool skip(const infinint & pos) override;
	virtual bool skip_to_eof() override;
	virtual bool skip_relative(S_I x) override;
	virtual bool truncatable(const infinint & amount) const override;
	virtual infinint get_position() const override;

	void copy_to(generic_file & ref) override;
	void copy_to(generic_file & ref, const infinint & crc_size, crc * & value) override;

    protected:
	virtual void inherited_read_ahead(const infinint & amount) override;
	virtual U_I inherited_read(char *a, U_I size) override;
	virtual void inherited_write(const char *a, U_I size) override;
	virtual void inherited_truncate(const infinint & pos) override;
	virtual void inherited_sync_write() override;
	virtual void inherited_flush_read() override;
	virtual void inherited_terminate() override;

    private:
	struct face
	{
	    generic_file * ptr;
	    std::list<std::string> labels;
	};  // ok, had not much idea to find a name for that struct, "face" was the first idea found to be associated with "pile", which means stack
	    // in French but also is the name of the face of a coin where its value is written. The opposite face of a coin is called "face" in French
	    // because often a face is design there and the expression "tirer `a pile ou face" (meaning "to toss up") is very common.

	std::deque<face> stack;

	void detruit();
	std::deque<face>::iterator look_for_label(const std::string & label);
    };


    template <class T> bool pile::pop_and_close_if_type_is(T *ptr)
    {
	generic_file *top = nullptr;

	if(!stack.empty())
	{
	    top = stack.back().ptr;
	    ptr = dynamic_cast<T *>(top);
	    if(ptr != nullptr)
	    {
		ptr->terminate();
		stack.pop_back();
		delete ptr;
		return true;
	    }
	    else
		return false;
	}
	else
	    return false;
    }

    template <class T> void pile::find_first_from_top(T * & ref) const
    {
	ref = nullptr;
	for(std::deque<face>::const_reverse_iterator it = stack.rbegin(); it != stack.rend() && ref == nullptr; ++it)
	    ref = dynamic_cast<T *>(it->ptr);
    }


    template <class T> void pile::find_first_from_bottom(T * & ref) const
    {
	ref = nullptr;
	for(std::deque<face>::const_iterator it = stack.begin(); it != stack.end() && ref == nullptr; ++it)
	    ref = dynamic_cast<T *>(it->ptr);
    }

    	/// @}

} // end of namespace

#endif
