/*
    Copyright (C) 2010 Paul Davis

    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., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#ifndef __libpbd_sequence_property_h__
#define __libpbd_sequence_property_h__

#include <iostream>

#include <set>
#include <list>

#include <boost/function.hpp>

#include "pbd/convert.h"
#include "pbd/id.h"
#include "pbd/property_basics.h"

namespace PBD {

/** A base class for properties whose state is a container of other
 *  things.  Its behaviour is `specialised' for this purpose in that
 *  it holds state changes as additions to and removals from the
 *  container, which is more efficient than storing entire state after
 *  any change.
 */
template<typename Container>
class SequenceProperty : public PropertyBase
{
  public:
        typedef std::set<typename Container::value_type> ChangeContainer;

	/** A record of changes made */
        struct ChangeRecord {

		void add (typename Container::value_type const & r) {
			typename ChangeContainer::iterator i = removed.find (r);
			if (i != removed.end()) {
				/* we're adding, and this thing has already been marked as removed, so
				   just remove it from the removed list
				*/
				removed.erase (r);
			} else {
				added.insert (r);
			}
		}

		void remove (typename Container::value_type const & r) {
			typename ChangeContainer::iterator i = added.find (r);
			if (i != added.end()) {
				/* we're removing, and this thing has already been marked as added, so
				   just remove it from the added list
				*/
				added.erase (i);
			} else {
				removed.insert (r);
			}
		}

		ChangeContainer added;
		ChangeContainer removed;
	};

	SequenceProperty (PropertyID id, const boost::function<void(const ChangeRecord&)>& update)
                : PropertyBase (id), _update_callback (update) {}
        
	virtual typename Container::value_type lookup_id (const PBD::ID&) = 0;

        void invert_changes () {

                /* reverse the adds/removes so that this property's change member
                   correctly describes how to undo the changes it currently
                   reflects. A derived instance of this type of property will
                   create a diff() pair by copying the property twice, and
                   calling this method on the "before" item of the pair.
                */

		_change.removed.swap (_change.added);
        }

	void add_history_state (XMLNode* history_node) const {

		/* We could record the whole of the current state here, but its
		   obviously more efficient just to record what has changed.
		*/
                
                XMLNode* child = new XMLNode (PBD::capitalize (property_name()));
                history_node->add_child_nocopy (*child);
                
		/* record the change described in our change member */

		if (!_change.added.empty()) {
			for (typename ChangeContainer::iterator i = _change.added.begin(); i != _change.added.end(); ++i) {
                                XMLNode* add_node = new XMLNode ("Add");
                                child->add_child_nocopy (*add_node);
                                add_node->add_property ("id", (*i)->id().to_s());
			}
		}
		if (!_change.removed.empty()) {
			for (typename ChangeContainer::iterator i = _change.removed.begin(); i != _change.removed.end(); ++i) {
                                XMLNode* remove_node = new XMLNode ("Remove");
                                child->add_child_nocopy (*remove_node);
                                remove_node->add_property ("id", (*i)->id().to_s());
			}
		}
	}

	bool set_state_from_owner_state (XMLNode const& owner_state) {

		XMLProperty const* n = owner_state.property ("name");

                if (!n) {
                        return false;
                }

                assert (g_quark_from_string (n->value().c_str()) == property_id());

		const XMLNodeList& children = owner_state.children();

		for (XMLNodeList::const_iterator c = children.begin(); c != children.end(); ++c) {

			if ((*c)->name() == "Added") {
				const XMLNodeList& grandchildren = (*c)->children();
				for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
					const XMLProperty* prop = (*gc)->property ("id");
					if (prop) {
						typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
						if (v) {
							_change.add (v);
						}
					}
				}
			} else if ((*c)->name() == "Removed") {
				const XMLNodeList& grandchildren = (*c)->children();
				for (XMLNodeList::const_iterator gc = grandchildren.begin(); gc != grandchildren.end(); ++gc) {
					const XMLProperty* prop = (*gc)->property ("id");
					if (prop) {
						typename Container::value_type v = lookup_id (PBD::ID (prop->value()));
						if (v) {
							_change.remove (v);
						}
					}
				}
			}
		}

		return true;
	}

	void add_state_to_owner_state (XMLNode& owner_state_node) const {
                for (typename Container::const_iterator i = _val.begin(); i != _val.end(); ++i) {
                        owner_state_node.add_child_nocopy ((*i)->get_state ());
                } 
	}

	bool changed () const {
		return !_change.added.empty() || !_change.removed.empty();
	}
	
	void clear_history () {
		_change.added.clear ();
		_change.removed.clear ();
	}

	void set_state_from_property (PropertyBase const * p) {
		const ChangeRecord& change (dynamic_cast<const SequenceProperty*> (p)->change ());
		update (change);
	}

	/** Given a record of changes to this property, pass it to a callback that will
	 *  update the property in some appropriate way. 
	 *
	 *  This exists because simply using std::sequence methods to add/remove items
	 *  from the property is far too simplistic - the semantics of add/remove may
	 *  be much more complex than that.
	 */
	void update (const ChangeRecord& cr) {
		_update_callback (cr);
	}

	/* Wrap salient methods of Sequence
	 */

	typename Container::iterator begin() { return _val.begin(); }
	typename Container::iterator end() { return _val.end(); }
	typename Container::const_iterator begin() const { return _val.begin(); }
	typename Container::const_iterator end() const { return _val.end(); }

	typename Container::reverse_iterator rbegin() { return _val.rbegin(); }
	typename Container::reverse_iterator rend() { return _val.rend(); }
	typename Container::const_reverse_iterator rbegin() const { return _val.rbegin(); }
	typename Container::const_reverse_iterator rend() const { return _val.rend(); }

	typename Container::iterator insert (typename Container::iterator i, const typename Container::value_type& v) {
		_change.add (v);
		return _val.insert (i, v);
	}

	typename Container::iterator erase (typename Container::iterator i) {
		if (i != _val.end()) {
			_change.remove (*i);
		}
		return _val.erase (i);
	}

	typename Container::iterator erase (typename Container::iterator f, typename Container::iterator l) {
		for (typename Container::const_iterator i = f; i != l; ++i) {
			_change.remove (*i);
		}
		return _val.erase (f, l);
	}

	void push_back (const typename Container::value_type& v) {
		_change.add (v);
		_val.push_back (v);
	}

	void push_front (const typename Container::value_type& v) {
		_change.add (v);
		_val.push_front (v);
	}

	void pop_front () {
                if (!_val.empty()) {
                        _change.remove (front());
                }
		_val.pop_front ();
	}

	void pop_back () {
                if (!_val.empty()) {
                        _change.remove (back());
                }
		_val.pop_back ();
	}

	void clear () {
		for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
			_change.remove (*i);
		}
		_val.clear ();
	}
	
	typename Container::size_type size() const { 
		return _val.size();
	}

	bool empty() const { 
		return _val.empty();
	}

	Container& operator= (const Container& other) {
		for (typename Container::iterator i = _val.begin(); i != _val.end(); ++i) {
			_change.remove (*i);
		}
		for (typename Container::iterator i = other.begin(); i != other.end(); ++i) {
			_change.add (*i);
		}
		return _val = other;
	}

	typename Container::reference front() { 
		return _val.front ();
	}

	typename Container::const_reference front() const { 
		return _val.front ();
	}

	typename Container::reference back() { 
		return _val.back ();
	}

	typename Container::const_reference back() const { 
		return _val.back ();
	}

	void sort() { 
		_val.sort ();
	}

	template<class BinaryPredicate> void sort(BinaryPredicate comp) {
		_val.sort (comp);
	}
        
        const ChangeRecord& change() const { return _change; }

  protected:
	Container _val;
	ChangeRecord _change;
	boost::function<void(const ChangeRecord&)> _update_callback;

        /** Load serialized change history.
         * @return true if loading succeeded, false otherwise
         */

        bool load_history_state (const XMLNode& history_node) {

                const XMLNodeList& children (history_node.children());

                for (XMLNodeList::const_iterator i = children.begin(); i != children.end(); ++i) {
                        const XMLProperty* prop = (*i)->property ("id");
                        if (prop) {
                                PBD::ID id (prop->value());
                                typename Container::value_type v = lookup_id (id);
                                if (!v) {
                                        std::cerr << "No such item, ID = " << id.to_s() << " (from " << prop->value() << ")\n";
                                        return false;
                                }
                                if ((*i)->name() == "Add") {
                                        _change.added.insert (v);
                                } else if ((*i)->name() == "Remove") {
                                        _change.removed.insert (v);
                                }
                        }
                }

                return true;
        }
};

}

#endif /* __libpbd_sequence_property_h__ */
