/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "undo.h"

#include "debugging/debugging.h"
#include "warnings.h"

#include "iundo.h"
#include "preferencesystem.h"
#include "string/string.h"
#include "generic/callback.h"
#include "preferences.h"
#include "stringio.h"

#include <list>
#include <map>
#include <set>

#include "timer.h"

class DebugScopeTimer
{
Timer m_timer;
const char* m_operation;
public:
DebugScopeTimer( const char* operation )
	: m_operation( operation ){
	m_timer.start();
}
~DebugScopeTimer(){
	unsigned int elapsed = m_timer.elapsed_msec();
	if ( elapsed > 0 ) {
		globalOutputStream() << m_operation << ": " << elapsed << " msec\n";
	}
}
};


class RadiantUndoSystem : public UndoSystem
{
UINT_CONSTANT( MAX_UNDO_LEVELS, 1024 );

class Snapshot
{
class StateApplicator
{
public:
Undoable* m_undoable;
private:
UndoMemento* m_data;
public:

StateApplicator( Undoable* undoable, UndoMemento* data )
	: m_undoable( undoable ), m_data( data ){
}
void restore(){
	m_undoable->importState( m_data );
}
void release(){
	m_data->release();
}
};

typedef std::list<StateApplicator> states_t;
states_t m_states;

public:
bool empty() const {
	return m_states.empty();
}
std::size_t size() const {
	return m_states.size();
}
void save( Undoable* undoable ){
	m_states.push_front( StateApplicator( undoable, undoable->exportState() ) );
}
void restore(){
	for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
	{
		( *i ).restore();
	}
}
void release(){
	for ( states_t::iterator i = m_states.begin(); i != m_states.end(); ++i )
	{
		( *i ).release();
	}
}
};

struct Operation
{
	Snapshot m_snapshot;
	CopiedString m_command;

	Operation( const char* command )
		: m_command( command ){
	}
	~Operation(){
		m_snapshot.release();
	}
};


class UndoStack
{
//! Note: using std::list instead of vector/deque, to avoid copying of undos
typedef std::list<Operation*> Operations;

Operations m_stack;
Operation* m_pending;

public:
UndoStack() : m_pending( 0 ){
}
~UndoStack(){
	clear();
}
bool empty() const {
	return m_stack.empty();
}
std::size_t size() const {
	return m_stack.size();
}
Operation* back(){
	return m_stack.back();
}
const Operation* back() const {
	return m_stack.back();
}
Operation* front(){
	return m_stack.front();
}
const Operation* front() const {
	return m_stack.front();
}
void pop_front(){
	delete m_stack.front();
	m_stack.pop_front();
}
void pop_back(){
	delete m_stack.back();
	m_stack.pop_back();
}
void clear(){
	if ( !m_stack.empty() ) {
		for ( Operations::iterator i = m_stack.begin(); i != m_stack.end(); ++i )
		{
			delete *i;
		}
		m_stack.clear();
	}
}
void start( const char* command ){
	if ( m_pending != 0 ) {
		delete m_pending;
	}
	m_pending = new Operation( command );
}
bool finish( const char* command ){
	if ( m_pending != 0 ) {
		delete m_pending;
		m_pending = 0;
		return false;
	}
	else
	{
		ASSERT_MESSAGE( !m_stack.empty(), "undo stack empty" );
		m_stack.back()->m_command = command;
		return true;
	}
}
void save( Undoable* undoable ){
	if ( m_pending != 0 ) {
		m_stack.push_back( m_pending );
		m_pending = 0;
	}
	back()->m_snapshot.save( undoable );
}
};

UndoStack m_undo_stack;
UndoStack m_redo_stack;

class UndoStackFiller : public UndoObserver
{
UndoStack* m_stack;
public:

UndoStackFiller()
	: m_stack( 0 ){
}
void save( Undoable* undoable ){
	ASSERT_NOTNULL( undoable );

	if ( m_stack != 0 ) {
		m_stack->save( undoable );
		m_stack = 0;
	}
}
void setStack( UndoStack* stack ){
	m_stack = stack;
}
};

typedef std::map<Undoable*, UndoStackFiller> undoables_t;
undoables_t m_undoables;

void mark_undoables( UndoStack* stack ){
	for ( undoables_t::iterator i = m_undoables.begin(); i != m_undoables.end(); ++i )
	{
		( *i ).second.setStack( stack );
	}
}

std::size_t m_undo_levels;

typedef std::set<UndoTracker*> Trackers;
Trackers m_trackers;
public:
RadiantUndoSystem()
	: m_undo_levels( 64 ){
}
~RadiantUndoSystem(){
	clear();
}
UndoObserver* observer( Undoable* undoable ){
	ASSERT_NOTNULL( undoable );

	return &m_undoables[undoable];
}
void release( Undoable* undoable ){
	ASSERT_NOTNULL( undoable );

	m_undoables.erase( undoable );
}
void setLevels( std::size_t levels ){
	if ( levels > MAX_UNDO_LEVELS() ) {
		levels = MAX_UNDO_LEVELS();
	}

	while ( m_undo_stack.size() > levels )
	{
		m_undo_stack.pop_front();
	}
	m_undo_levels = levels;
}
std::size_t getLevels() const {
	return m_undo_levels;
}
std::size_t size() const {
	return m_undo_stack.size();
}
void startUndo(){
	m_undo_stack.start( "unnamedCommand" );
	mark_undoables( &m_undo_stack );
}
bool finishUndo( const char* command ){
	bool changed = m_undo_stack.finish( command );
	mark_undoables( 0 );
	return changed;
}
void startRedo(){
	m_redo_stack.start( "unnamedCommand" );
	mark_undoables( &m_redo_stack );
}
bool finishRedo( const char* command ){
	bool changed = m_redo_stack.finish( command );
	mark_undoables( 0 );
	return changed;
}
void start(){
	m_redo_stack.clear();
	if ( m_undo_stack.size() == m_undo_levels ) {
		m_undo_stack.pop_front();
	}
	startUndo();
	trackersBegin();
}
void finish( const char* command ){
	if ( finishUndo( command ) ) {
		globalOutputStream() << command << '\n';
	}
}
void undo(){
	if ( m_undo_stack.empty() ) {
		globalOutputStream() << "Undo: no undo available\n";
	}
	else
	{
		Operation* operation = m_undo_stack.back();
		globalOutputStream() << "Undo: " << operation->m_command.c_str() << "\n";

		startRedo();
		trackersUndo();
		operation->m_snapshot.restore();
		finishRedo( operation->m_command.c_str() );
		m_undo_stack.pop_back();
	}
}
void redo(){
	if ( m_redo_stack.empty() ) {
		globalOutputStream() << "Redo: no redo available\n";
	}
	else
	{
		Operation* operation = m_redo_stack.back();
		globalOutputStream() << "Redo: " << operation->m_command.c_str() << "\n";

		startUndo();
		trackersRedo();
		operation->m_snapshot.restore();
		finishUndo( operation->m_command.c_str() );
		m_redo_stack.pop_back();
	}
}
void clear(){
	mark_undoables( 0 );
	m_undo_stack.clear();
	m_redo_stack.clear();
	trackersClear();
}
void trackerAttach( UndoTracker& tracker ){
	ASSERT_MESSAGE( m_trackers.find( &tracker ) == m_trackers.end(), "undo tracker already attached" );
	m_trackers.insert( &tracker );
}
void trackerDetach( UndoTracker& tracker ){
	ASSERT_MESSAGE( m_trackers.find( &tracker ) != m_trackers.end(), "undo tracker cannot be detached" );
	m_trackers.erase( &tracker );
}
void trackersClear() const {
	for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
	{
		( *i )->clear();
	}
}
void trackersBegin() const {
	for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
	{
		( *i )->begin();
	}
}
void trackersUndo() const {
	for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
	{
		( *i )->undo();
	}
}
void trackersRedo() const {
	for ( Trackers::const_iterator i = m_trackers.begin(); i != m_trackers.end(); ++i )
	{
		( *i )->redo();
	}
}
};



void UndoLevels_importString( RadiantUndoSystem& undo, const char* value ){
	int levels;
	PropertyImpl<int, const char *>::Import( levels, value );
	undo.setLevels( levels );
}
typedef ReferenceCaller<RadiantUndoSystem, void(const char*), UndoLevels_importString> UndoLevelsImportStringCaller;
void UndoLevels_exportString( const RadiantUndoSystem& undo, const Callback<void(const char *)> & importer ){
	PropertyImpl<int, const char *>::Export( static_cast<int>( undo.getLevels() ), importer );
}
typedef ConstReferenceCaller<RadiantUndoSystem, void(const Callback<void(const char *)> &), UndoLevels_exportString> UndoLevelsExportStringCaller;

#include "generic/callback.h"

struct UndoLevels {
    static void Export(const RadiantUndoSystem &self, const Callback<void(int)> &returnz) {
        returnz(static_cast<int>(self.getLevels()));
    }

    static void Import(RadiantUndoSystem &self, int value) {
        self.setLevels(value);
    }
};

void Undo_constructPreferences( RadiantUndoSystem& undo, PreferencesPage& page ){
    page.appendSpinner("Undo Queue Size", 64, 0, 1024, make_property<UndoLevels>(undo));
}
void Undo_constructPage( RadiantUndoSystem& undo, PreferenceGroup& group ){
	PreferencesPage page( group.createPage( "Undo", "Undo Queue Settings" ) );
	Undo_constructPreferences( undo, page );
}
void Undo_registerPreferencesPage( RadiantUndoSystem& undo ){
	PreferencesDialog_addSettingsPage( ReferenceCaller<RadiantUndoSystem, void(PreferenceGroup&), Undo_constructPage>( undo ) );
}

class UndoSystemDependencies : public GlobalPreferenceSystemModuleRef
{
};

class UndoSystemAPI
{
RadiantUndoSystem m_undosystem;
public:
typedef UndoSystem Type;
STRING_CONSTANT( Name, "*" );

UndoSystemAPI(){
    GlobalPreferenceSystem().registerPreference("UndoLevels", make_property_string<UndoLevels>(m_undosystem));

	Undo_registerPreferencesPage( m_undosystem );
}
UndoSystem* getTable(){
	return &m_undosystem;
}
};

#include "modulesystem/singletonmodule.h"
#include "modulesystem/moduleregistry.h"

typedef SingletonModule<UndoSystemAPI, UndoSystemDependencies> UndoSystemModule;
typedef Static<UndoSystemModule> StaticUndoSystemModule;
StaticRegisterModule staticRegisterUndoSystem( StaticUndoSystemModule::instance() );










class undoable_test : public Undoable
{
struct state_type : public UndoMemento
{
	state_type() : test_data( 0 ){
	}
	state_type( const state_type& other ) : UndoMemento( other ), test_data( other.test_data ){
	}
	void release(){
		delete this;
	}

	int test_data;
};
state_type m_state;
UndoObserver* m_observer;
public:
undoable_test()
	: m_observer( GlobalUndoSystem().observer( this ) ){
}
~undoable_test(){
	GlobalUndoSystem().release( this );
}
UndoMemento* exportState() const {
	return new state_type( m_state );
}
void importState( const UndoMemento* state ){
	ASSERT_NOTNULL( state );

	m_observer->save( this );
	m_state = *( static_cast<const state_type*>( state ) );
}

void mutate( unsigned int data ){
	m_observer->save( this );
	m_state.test_data = data;
}
};

#if 0

class TestUndo
{
public:
TestUndo(){
	undoable_test test;
	GlobalUndoSystem().begin( "bleh" );
	test.mutate( 3 );
	GlobalUndoSystem().begin( "blah" );
	test.mutate( 4 );
	GlobalUndoSystem().undo();
	GlobalUndoSystem().undo();
	GlobalUndoSystem().redo();
	GlobalUndoSystem().redo();
}
};

TestUndo g_TestUndo;

#endif
