// dlist.h:  Display list definitions, for Gnash.
// 
//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
//   Free Software Foundation, Inc
// 
// 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 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// 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 St, Fifth Floor, Boston, MA  02110-1301  USA
//

#ifndef GNASH_DLIST_H
#define GNASH_DLIST_H

#include <list>
#include <iosfwd>
#if GNASH_PARANOIA_LEVEL > 1 && !defined(NDEBUG)
#include "DisplayObject.h"
#include <set>  // for testInvariant
#include <algorithm>
#include "log.h"
#endif

#include "snappingrange.h"
#include "dsodefs.h" // for DSOTEXPORT


// GNASH_PARANOIA_LEVEL:
// 0 : (not unimplemented)
// 1 : quick assertions
// 2 : add testInvariant
//
#ifndef GNASH_PARANOIA_LEVEL
# define GNASH_PARANOIA_LEVEL 1
#endif

namespace gnash {
    class SWFCxForm;
    class Renderer;
    struct ObjectURI;
    class Transform;
    class string_table;
    class DisplayObject;
    class SWFMatrix;
}

namespace gnash {

/// A list of on-stage DisplayObjects, ordered by depth
//
/// Any MovieClip has an associated DisplayList
/// that may change from frame to frame due to control
/// tags instructing when to add or remove DisplayObjects
/// from the stage.
///
class DisplayList
{

public:

	typedef std::list<DisplayObject*> container_type;
	typedef container_type::iterator iterator;
	typedef container_type::const_iterator const_iterator;
	typedef container_type::reverse_iterator reverse_iterator;
	typedef container_type::const_reverse_iterator const_reverse_iterator;

    DisplayList() {}
    ~DisplayList() {}

    /// Output operator
	friend std::ostream& operator<< (std::ostream&, const DisplayList&);

	/// \brief
	/// Place a new DisplayObject at the specified depth,
	/// replacing any existing DisplayObject at the same depth.
	//
	/// If a DisplayObject is replaced, it's unload() method
	/// is invoked.
	///
	/// If applicable, the event_id::LOAD event
	/// associated with the given DisplayObject
	/// is called as last step of addition. 
	///
	/// @param ch 
	///	the new DisplayObject to be added into the list.
	///
	/// @param depth 
	///	depth at which the new DisplayObject is placed.
    DSOTEXPORT void placeDisplayObject(DisplayObject* ch, int depth);

	/// \brief
	/// Replace the old DisplayObject at the specified depth with
	/// the given new DisplayObject.
	//
	/// Calls unload on the removed DisplayObject.
	///
	/// @param ch 
	///	the new DisplayObject to be put
	///
	/// @param depth 
	///	depth to be replaced
	///
	/// @param use_old_cxform
	/// true:  set the new DisplayObject's SWFCxForm to the old one.
	/// false: keep the new DisplayObject's SWFCxForm.
	///
	/// @param use_old_matrix
	/// true:  set the new DisplayObject's transformation SWFMatrix to the old one.
	/// false: keep the new DisplayObject's transformation SWFMatrix.
	void replaceDisplayObject(DisplayObject* ch, int depth, bool use_old_cxform,
		bool use_old_matrix);

	/// \brief
	/// Change depth of the given DisplayObjects in the list,
	/// swapping with any existing DisplayObject at target depth.
	//
	/// List ordering will be maintained by this function.
	///
	/// Any DisplayObject affected by this operation (none on invalid call,
	/// 1 if new depth is not occupied, 2 otherwise) will be:
	///	- bounds invalidated (see DisplayObject::set_invalidated)
	///	- marked as script-transformed (see DisplayObject::transformedByScript)
	/// 
	/// @param ch
	///	The DisplayObject to apply depth swapping to.
	///	If not found in the list, an error is raised
	///	and no other action is taken.
	///
	/// @param depth
	///	The new depth to assign to the given DisplayObject.
	///	If occupied by another DisplayObject, the target DisplayObject
	///	will get the current depth of the first.
	///	If target depth equals the current depth of DisplayObject, an
	///	assertion fails, as I think the caller should check this instead.
	///
	void swapDepths(DisplayObject* ch, int depth);

	/// \brief
	/// Updates the transform properties of the object at the
	/// specified depth, unless its get_accept_anim_moves() returns false.
	//
	///  See DisplayObject::get_accept_anim_moves()
	///
	/// @param color_xform
	///	The color tranform to assign to the DisplayObject at the given depth.
	///	If NULL the orignial color transform will be kept.
	//
	/// @param mat
	///	The SWFMatrix tranform to assign to the DisplayObject at the given depth.
	///	If NULL the orignial SWFMatrix will be kept.
	///
	/// @param ratio
	/// The new ratio value to assign to the DisplayObject at the given depth.
	/// If NULL the original ratio will be kept.
	void moveDisplayObject(int depth, const SWFCxForm* color_xform,
            const SWFMatrix* mat, boost::uint16_t* ratio);

	/// Removes the object at the specified depth.
	//
	/// Calls unload on the removed DisplayObject.
	void removeDisplayObject(int depth);

	/// Remove all unloaded DisplayObject from the list
	//
	/// Removed DisplayObjects still in the list are those
	/// on which onUnload event handlers were defined..
	///
	/// NOTE: we don't call the function recursively in the 
	///       contained elements, as that should not be needed
	///	  (ie: any inned thing will not be accessible anyway)
	void removeUnloaded();

	/// Unload the DisplayObjects in this DisplayList removing
	/// all but the ones with on onUnload event defined
	/// (checked by calling ::unload on them) and keeping
	/// the others, w/out depth-shifting them.
	///
	/// Return true if any child was kept (as they had onUnload defined)
	bool unload();

	/// destroy all DisplayObjects in this DisplayList
	void destroy();

	/// Add a DisplayObject in the list, maintaining depth-order
	//
	///
	/// @param ch
	///	The DisplayObject to add
	///
	/// @param replace
	///	If true the given DisplayObject would replace any
	///	pre-existing DisplayObject at the same depth.
	void add(DisplayObject* ch, bool replace);

    /// Inserts a DisplayObject at the specified index (depth)
    //
    /// If a DisplayObject is already at that index, it is moved up.
    /// This implements AS3 DisplayObjectContainer.addChildAt().
    //
    /// @param obj      The DisplayObject to insert. This should already be
    ///                 removed from any other DisplayLists. It should not be
    ///                 the owner of this DisplayList or any parent of that
    ///                 owner.
    /// @param index    The index at which to insert the DisplayObject.
    void insertDisplayObject(DisplayObject* obj, int index);

	/// Display the list's DisplayObjects.
    //
	/// Lower depths are obscured by higher depths.
	void display(Renderer& renderer, const Transform& xform);
	
	void omit_display();

	/// May return NULL.
	DSOTEXPORT DisplayObject* getDisplayObjectAtDepth(int depth) const;

	/// If there are multiples, returns the *first* match only!
	//
	/// @param st
	///     The string_table to use for finding
	///     lowercase equivalent of names if
	///     `caseless' parameter is true.
	/// @param uri
	///     Object identifier
	/// @param caseless
	///     Wheter comparison must be case-insensitive.
	///
	DSOTEXPORT DisplayObject* getDisplayObjectByName(string_table& st,
            const ObjectURI& uri, bool caseless) const;

	/// \brief 
	/// Visit each DisplayObject in the list in reverse depth
	/// order (higher depth first).
	//
	/// The visitor functor
	/// will receive a DisplayObject pointer; must return true if
	/// it wants next item or false
	/// to exit the loop.
	///
	/// NOTE: all elements in the list are visited, even
	///       the removed ones (unloaded)
	/// TODO: inspect if worth providing an arg to skip removed
	template <class V> inline void visitBackward(V& visitor);
    template <class V> inline void visitBackward(V& visitor) const;

	/// \brief 
	/// Visit each and all DisplayObject in the list.
	//
	/// Scan happens in arbitrary order, if order is
	/// important use visitBackward or visitForward
	///
	/// The visitor functor will receive a DisplayObject pointer,
	/// it's return value is not used so can return void.
	///
	/// NOTE: all elements in the list are visited, even
	///       the removed ones (unloaded)
	/// TODO: inspect if worth providing an arg to skip removed
	template <class V> inline void visitAll(V& visitor);
	template <class V> inline void visitAll(V& visitor) const;

    /// Like DisplayObject_instance::add_invalidated_bounds() this method calls the
    /// method with the same name of all childs.	
	void add_invalidated_bounds(InvalidatedRanges& ranges, bool force);	
	
	/// Return number of elements in the list
	size_t size() const { 
		return _charsByDepth.size();
	}

	/// Return true if the list contains no elements 
	bool empty() const {
		return _charsByDepth.empty();
	}

	/// Return the next highest available depth
	//
	/// Placing an object at the depth returned by
	/// this function should result in a DisplayObject
	/// that is displayd above all others
	///
	int getNextHighestDepth() const;
	
	/// Merge the given display list
    //
    /// Call set_invalidated on the given DisplayObject if this list changes
    ///
	void mergeDisplayList(DisplayList& newList, DisplayObject& o);

	bool operator==(const DisplayList& other) const {
        return _charsByDepth == other._charsByDepth;
    }

	bool operator!=(const DisplayList& other) const {
        return _charsByDepth != other._charsByDepth;
    }
	
#if GNASH_PARANOIA_LEVEL > 1 && !defined(NDEBUG)
    DisplayList::const_iterator nonRemoved() const;

    void testInvariant() const
	{
		DisplayList sorted = *this;

        // check no duplicated depths above non-removed zone.
		std::set<int> depths;
		for (const_iterator it = nonRemoved(),
                itEnd = _charsByDepth.end(); it != itEnd; ++it) {

			DisplayObject* ch = *it;
			int depth = ch->get_depth();
			if (!depths.insert(depth).second) {
				log_debug("Depth %d is duplicated in DisplayList %p",
                        depth, (const void*)this);
                std::abort();
			}
		}
        if (_charsByDepth.empty()) return;
		// check we didn't screw up ordering
        assert(std::adjacent_find(_charsByDepth.begin(), _charsByDepth.end(),
            DepthGreaterThan()) == _charsByDepth.end());
	}
#else
    void testInvariant() const {}
#endif 

private:

	/// Re-insert a removed-from-stage DisplayObject after appropriately
	/// shifting its depth based on the DisplayObject::removedDepthOffset
	/// value.
	//
	/// PRE-CONDITIONS 
	///	- ch::isUnloaded() returns true (assertion fails otherwise)
	///	- ch is not already in the list (assertion fails otherwise)
	///
	/// TODO: inspect what should happen if the target depth is already
    /// occupied
	void reinsertRemovedCharacter(DisplayObject* ch);

	container_type _charsByDepth;
};

template <class V>
void
DisplayList::visitBackward(V& visitor)
{
	for (reverse_iterator it = _charsByDepth.rbegin(),
			itEnd = _charsByDepth.rend(); it != itEnd; ++it) {
		if (!visitor(*it)) break;
	}
}

template <class V>
void
DisplayList::visitBackward(V& visitor) const
{
	for (const_reverse_iterator it = _charsByDepth.rbegin(),
			itEnd = _charsByDepth.rend(); it != itEnd; ++it) {
		if (!visitor(*it)) break;
	}
}

template <class V>
void
DisplayList::visitAll(V& visitor)
{
	for (iterator it = _charsByDepth.begin(), itEnd = _charsByDepth.end();
		it != itEnd; ++it) {

		visitor(*it);
	}
}

template <class V>
void
DisplayList::visitAll(V& visitor) const
{
	for (const_iterator it = _charsByDepth.begin(),
            itEnd = _charsByDepth.end(); it != itEnd; ++it) {

		visitor(*it);
	}
}

DSOTEXPORT std::ostream& operator<< (std::ostream&, const DisplayList&);

} // namespace gnash


#endif // GNASH_DLIST_H



// Local Variables:
// mode: C++
// c-basic-offset: 8 
// tab-width: 8
// indent-tabs-mode: t
// End:
