#ifndef K3DSDK_DATA_H
#define K3DSDK_DATA_H

// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#include "idocument.h"
#include "istate_change_set.h"
#include "istate_container.h"
#include "istate_recorder.h"

#include "data_private.h"

#include <cassert>
#include <memory>

namespace k3d
{

/////////////////////////////////////////////////////////////////////////////
// iconstraint

/// Abstract interface for a chain-of-responsibility constraint object
template<typename data_t>
class iconstraint
{
public:
	virtual ~iconstraint() {}

	void constrain(data_t& Value)
	{
		on_constrain(Value);

		if(m_next_constraint.get())
			m_next_constraint->constrain(Value);
	}

protected:
	explicit iconstraint(iconstraint<data_t>* NextConstraint) :
		m_next_constraint(NextConstraint)
	{
	}

private:
	/// Implement this method in a derived class to modify / constrain the input Value
	virtual void on_constrain(data_t& Value) = 0;

	/// Storage for the (optional) next constraint to apply in the chain
	const std::auto_ptr<iconstraint<data_t> > m_next_constraint;
};

namespace constraint
{

/////////////////////////////////////////////////////////////////////////////
// minimum_t

/// Constraint object that enforces a minimum value (using the std::max concept)
template<typename data_t>
class minimum_t :
	public iconstraint<data_t>
{
public:
	minimum_t(const data_t MinimumValue, iconstraint<data_t>* NextConstraint = 0) :
		iconstraint<data_t>(NextConstraint),
		m_minimum_value(MinimumValue)
	{
	}

private:
	void on_constrain(data_t& Value)
	{
		Value = std::max(Value, m_minimum_value);
	}

	const data_t m_minimum_value;
};

/////////////////////////////////////////////////////////////////////////////
// minimum

/// Convenience factory function for creating minimum_t constraint objects
template<typename data_t>
iconstraint<data_t>* minimum(const data_t MinimumValue, iconstraint<data_t>* NextConstraint = 0)
{
	return new minimum_t<data_t>(MinimumValue, NextConstraint);
}

/////////////////////////////////////////////////////////////////////////////
// maximum_t

/// Constraint object that enforces a maximum value (using the std::min concept)
template<typename data_t>
class maximum_t :
	public iconstraint<data_t>
{
public:
	maximum_t(const data_t MaximumValue, iconstraint<data_t>* NextConstraint = 0) :
		iconstraint<data_t>(NextConstraint),
		m_maximum_value(MaximumValue)
	{
	}

private:
	void on_constrain(data_t& Value)
	{
		Value = std::min(Value, m_maximum_value);
	}

	const data_t m_maximum_value;
};

/////////////////////////////////////////////////////////////////////////////
// maximum

/// Convenience factory function for creating maximum_t constraint objects
template<typename data_t>
iconstraint<data_t>* maximum(const data_t MaximumValue, iconstraint<data_t>* NextConstraint = 0)
{
	return new maximum_t<data_t>(MaximumValue, NextConstraint);
}

} // namespace constraint

/////////////////////////////////////////////////////////////////////////////
// no_constraint

/// Policy for data objects whose values aren't constrained
template<typename data_t>
class no_constraint
{
protected:
	template<typename init_t>
	no_constraint(const init_t& Init)
	{
	}

	void constrain(data_t& Value)
	{
	}
};

/////////////////////////////////////////////////////////////////////////////
// with_constraint

/// Policy for data objects whose values are constrained by a chain of one-to-many iconstraint objects
template<typename data_t>
class with_constraint
{
protected:
	template<typename init_t>
	with_constraint(const init_t& Init) :
		m_constraint(Init.constraint())
	{
		assert(m_constraint.get());
	}

	/// Called to apply the constraint chain to the incoming value
	void constrain(data_t& Value)
	{
		m_constraint->constrain(Value);
	}

private:
	/// Stores the (mandatory) chain of constraint objects to apply to incoming values
	const std::auto_ptr<iconstraint<data_t> > m_constraint;
};

/////////////////////////////////////////////////////////////////////////////
// local_storage

/// Read-write storage policy that stores data by value
template<typename data_t, typename signal_policy>
class local_storage :
	public signal_policy
{
public:
	template<typename init_t>
	local_storage(const init_t& Init) :
		signal_policy(Init),
		m_value(Init.value())
	{
	}

	/// Writable access to the underlying data - this is handy for working with STL containers, but be careful - writing data in this way will bypass signal and undo policies
	data_t& value()
	{
		return m_value;
	}

	/// Read-only access to the underlying data
	const data_t& value() const
	{
		return m_value;
	}

protected:
	~local_storage() {}

	/// Optionally called by an undo policy to store the original state of the data prior to modification
	void start_recording(k3d::istate_recorder& StateRecorder)
	{
		signal_policy::signal_start_recording(StateRecorder);
		StateRecorder.current_change_set()->record_old_state(new value_container<data_t>(m_value));
	}

	/// Sets a new state for the data
	void set_value(const data_t& Value)
	{
		m_value = Value;
		signal_policy::signal_set_value(Value);
	}

	/// Optionally called by an undo policy to store the new state of the data after one-or-more modifications
	void finish_recording(k3d::istate_recorder& StateRecorder)
	{
		StateRecorder.current_change_set()->record_new_state(new value_container<data_t>(m_value));
		signal_policy::signal_finish_recording(StateRecorder);
	}

private:
	/// Local storage for the data stored by this policy
	data_t m_value;

	/// Provides an implementation of istate_container for storing objects by value (ValueType must have a copy constructor and assignment operator)
	template<typename value_t>
	class value_container :
		public istate_container
	{
	public:
		value_container(value_t& Instance) :
			m_instance(Instance),
			m_value(Instance)
		{
		}

		void restore_state()
		{
			m_instance = m_value;
		}

	private:
		value_t& m_instance;
		const value_t m_value;
	};
};

/////////////////////////////////////////////////////////////////////////////
// demand_storage

/// Read-only storage policy that stores a value by pointer, created on-demand
template<typename data_t, typename signal_policy>
class demand_storage :
	public signal_policy
{
public:
	template<typename init_t>
	demand_storage(const init_t& Init) :
		signal_policy(Init),
		m_value(0)
	{
	}

	~demand_storage()
	{
		delete m_value;
	}

	/// Returns true iff the underlying data hasn't been created, yet
	bool empty()
	{
		return !m_value;
	}

	/// Read-only access to the underlying data
	data_t value()
	{
		if(!m_value)
			m_value = m_need_data_signal.emit();

		return m_value;
	}

	/// Resets the underlying data so it will be created-again next read
	void reset(data_t NewValue = 0)
	{
		delete m_value;
		m_value = NewValue;

		signal_policy::signal_set_value(NewValue);
	}

	/// Defines a signal that will be emitted to obtain data on-demand
	typedef SigC::Signal0<data_t> need_data_signal_t;
	need_data_signal_t& need_data_signal()
	{
		return m_need_data_signal;
	}

private:
	data_t m_value;
	need_data_signal_t m_need_data_signal;
};

/////////////////////////////////////////////////////////////////////////////
// no_name

/// Name policy for data objects that are unnamed
template<typename data_t>
class no_name
{
protected:
	template<typename init_t>
	no_name(const init_t& Init)
	{
	}

	~no_name()
	{
	}
};

/////////////////////////////////////////////////////////////////////////////
// immutable_name

/// Name policy for data objects whose name cannot be changed
template<typename data_t>
class immutable_name
{
public:
	template<typename init_t>
	immutable_name(const init_t& Init) :
		m_name(Init.name())
	{
	}

	/// Returns the object name
	const std::string name()
	{
		return m_name;
	}

protected:
	~immutable_name() {}

private:
	/// Storage for the object name
	const std::string m_name;
};

/////////////////////////////////////////////////////////////////////////////
// no_signal

/// Signal policy for data objects that does not notify observers when the data changes
template<typename data_t>
class no_signal
{
protected:
	template<typename init_t>
	no_signal(const init_t& Init)
	{
	}

	~no_signal()
	{
	}

	void signal_start_recording(k3d::istate_recorder& StateRecorder)
	{
	}

	void signal_set_value(const data_t& Value)
	{
	}

	void signal_finish_recording(k3d::istate_recorder& StateRecorder)
	{
	}
};

/////////////////////////////////////////////////////////////////////////////
// change_signal

/// Signal policy for data objects that transmit a "data changed" event when their state changes
template<typename data_t>
class change_signal
{
public:
	/// Defines a signal emitted when the data state changes
	typedef SigC::Signal0<void> changed_signal_t;
	changed_signal_t& changed_signal()
	{
		return m_changed_signal;
	}

protected:
	template<typename init_t>
	change_signal(const init_t& Init)
	{
	}

	~change_signal()
	{
	}

	void signal_start_recording(k3d::istate_recorder& StateRecorder)
	{
	}

	void signal_set_value(const data_t& Value)
	{
		m_changed_signal.emit();
	}

	void signal_finish_recording(k3d::istate_recorder& StateRecorder)
	{
		StateRecorder.current_change_set()->undo_signal().connect(m_changed_signal.slot());
		StateRecorder.current_change_set()->redo_signal().connect(m_changed_signal.slot());
	}

private:
	changed_signal_t m_changed_signal;
};

/////////////////////////////////////////////////////////////////////////////
// no_undo

/// Undo policy for data objects that do not record undo/redo data as they are modified
template
	<
		typename data_t,
		class storage_policy
	>
class no_undo :
	public storage_policy
{
public:
	typedef storage_policy storage_policy_t;

	template<typename init_t>
	no_undo(const init_t& Init) :
		storage_policy_t(Init)
	{
	}

protected:
	~no_undo() {}

	void undo_set_value(const data_t& Value)
	{
		storage_policy_t::set_value(Value);
	}
};

/////////////////////////////////////////////////////////////////////////////
// with_undo

/// Undo policy for data objects that record undo/redo data as they are modified
template
	<
		typename data_t,
		class storage_policy
	>
class with_undo :
	public storage_policy,
	public virtual SigC::Object
{
public:
	typedef with_undo<data_t, storage_policy> this_t;
	typedef storage_policy storage_policy_t;

	template<typename init_t>
	with_undo(const init_t& Init) :
		storage_policy_t(Init),
		m_state_recorder(Init.document().state_recorder()),
		m_changes(false)
	{
	}

	/// Returns true iff the next write to the underlying storage will generate undo/redo data (useful if you need to perform additional undo/redo related operations, such as creating a signal connection)
	bool ready_to_record()
	{
		return !m_changes && m_state_recorder.current_change_set();
	}

	/// Returns the state recorder for storing undo/redo data
	k3d::istate_recorder& state_recorder()
	{
		return m_state_recorder;
	}

protected:
	~with_undo() {}

	void undo_set_value(const data_t& Value)
	{
		if(ready_to_record())
			{
				m_changes = true;
				m_recording_connection = m_state_recorder.current_change_set()->recording_done_signal().connect(SigC::slot(*this, &this_t::on_recording_done));

				storage_policy_t::start_recording(m_state_recorder);
			}

		storage_policy_t::set_value(Value);
	}

private:
	/// Called by the signal system once undo/redo recording is complete, so we can record our final value for redo purposes
	void on_recording_done()
	{
		// Sanity check ...
		assert(m_changes);
		assert(m_state_recorder.current_change_set());

		m_changes = false;
		m_recording_connection.disconnect();

		storage_policy_t::finish_recording(m_state_recorder);
	}

	/// Central location where undo/redo data is stored
	k3d::istate_recorder& m_state_recorder;
	/// Set to true iff the underlying data has changed during undo/redo recording
	bool m_changes;
	/// Used to manage the (temporary) connection established while we wait for recording to finish ...
	SigC::Connection m_recording_connection;
};

/////////////////////////////////////////////////////////////////////////////
// data

/// Host class for storing data using name, signal, undo, storage, and constraint policies
template
	<
		typename data_t,
		class name_policy,
		class undo_policy,
		class constraint_policy
	>
class data :
	public name_policy,
	public undo_policy,
	public constraint_policy
{
public:
	typedef data_t value_t;
	typedef name_policy name_policy_t;
	typedef undo_policy undo_policy_t;
	typedef constraint_policy constraint_policy_t;

	template<typename init_t>
	data(const init_t& Init) :
		name_policy_t(Init),
		undo_policy_t(Init),
		constraint_policy_t(Init)
	{
	}

	void set_value(data_t Value)
	{
		constraint_policy_t::constrain(Value);

		if(Value == undo_policy_t::value())
			return;

		undo_policy_t::undo_set_value(Value);
	}

	void force_value(const data_t& Value)
	{
		undo_policy_t::undo_set_value(Value);
	}
};

template<class init_t>
class initializer_t :
	public init_t
{
public:
	explicit initializer_t(const init_t& Init) :
		init_t(Init)
	{
	};
};

class name_t
{
public:
	explicit name_t(const std::string& Name) :
		m_name(Name)
	{
	}

	const std::string& name() const
	{
		return m_name;
	}

private:
	const std::string m_name;
};

inline const initializer_t<name_t> init_name(const std::string& Name)
{
	return initializer_t<name_t>(name_t(Name));
}

class description_t
{
public:
	explicit description_t(const char* const Description) :
		m_description(Description)
	{
	}

	const char* const description() const
	{
		return m_description;
	}

private:
	const char* const m_description;
};

inline const initializer_t<description_t> init_description(const char* const Description)
{
	return initializer_t<description_t>(description_t(Description));
}

template<typename data_t>
class value_t
{
public:
	explicit value_t(const data_t& Value) :
		m_value(Value)
	{
	}

	const data_t& value() const
	{
		return m_value;
	}

private:
	const data_t m_value;
};

template<typename data_t>
inline const initializer_t<value_t<data_t> > init_value(const data_t& Value)
{
	return initializer_t<value_t<data_t> >(value_t<data_t>(Value));
}

class document_t
{
public:
	explicit document_t(idocument& Document) :
		m_document(Document)
	{
	}

	idocument& document() const
	{
		return m_document;
	}

private:
	idocument& m_document;
};

inline const initializer_t<document_t> init_document(idocument& Document)
{
	return initializer_t<document_t>(document_t(Document));
}

template<typename data_t>
class constraint_t
{
public:
	explicit constraint_t(iconstraint<data_t>* Constraint) :
		m_constraint(Constraint)
	{
	}

	iconstraint<data_t>* constraint() const
	{
		return m_constraint;
	}

private:
	iconstraint<data_t>* const m_constraint;
};

template<typename data_t>
inline const initializer_t<constraint_t<data_t> > init_constraint(iconstraint<data_t>* Constraint)
{
	return initializer_t<constraint_t<data_t> >(constraint_t<data_t>(Constraint));
}

template<typename lhs_t, typename rhs_t>
class composition_t :
	public lhs_t,
	public rhs_t
{
public:
	explicit composition_t(const lhs_t& LHS, const rhs_t& RHS) :
		lhs_t(LHS),
		rhs_t(RHS)
	{
	}
};

template<typename lhs_t, typename rhs_t>
inline const initializer_t<composition_t<lhs_t, rhs_t> > operator+(const initializer_t<lhs_t>& LHS, const initializer_t<rhs_t>& RHS)
{
	return initializer_t<composition_t<lhs_t, rhs_t> >(composition_t<lhs_t, rhs_t>(LHS, RHS));
}

/// Convenience macro so we can declare k3d::data objects almost as if we had template template arguments
#define k3d_data(data_type, name_policy, signal_policy, undo_policy, storage_policy, constraint_policy) \
	k3d::data<data_type, name_policy<data_type>, undo_policy<data_type, storage_policy<data_type, signal_policy<data_type> > >, constraint_policy<data_type> >

/////////////////////////////////////////////////////////////////////////////
// undoable_new

/// To make creation of an object undoable / redoable, pass it to this function after calling operator new
template<typename object_t>
void undoable_new(object_t* const Object, k3d::idocument& Document)
{
	// If we aren't recording undos, we're done...
	istate_change_set* const changeset = Document.state_recorder().current_change_set();
	if(!changeset)
		return;

	// Create an instance container that only deletes the object if it has been undone
	typedef detail::instance_container<object_t> container_t;
	container_t* const container = new container_t(Object, false);
	changeset->undo_signal().connect(SigC::bind(SigC::slot(*container, &container_t::on_owned), true));
	changeset->redo_signal().connect(SigC::bind(SigC::slot(*container, &container_t::on_owned), false));

	changeset->record_old_state(container);
}

/////////////////////////////////////////////////////////////////////////////
// undoable_delete

/// To make deletion of an object undoable / redoable, pass it to this function instead of calling operator delete
template<typename object_t>
void undoable_delete(object_t* const Object, k3d::idocument& Document)
{
	// If we aren't recording undos, delete it the old-fashioned way ...
	istate_change_set* const changeset = Document.state_recorder().current_change_set();
	if(!changeset)
		{
			delete Object;
			return;
		}

	// Create an instance container that only deletes the object if it hasn't been undone
	typedef detail::instance_container<object_t> container_t;
	container_t* const container = new container_t(Object, true);
	changeset->undo_signal().connect(SigC::bind(SigC::slot(*container, &container_t::on_owned), false));
	changeset->redo_signal().connect(SigC::bind(SigC::slot(*container, &container_t::on_owned), true));

	changeset->record_old_state(container);
}

} // namespace k3d

#endif // !K3DSDK_DATA_H


