//*****************************************************************************************
// Truevision - a 3d modeler for gnome and povray
//
// undo.cc
//
// Vincent LE PRINCE <vincentleprince@users.sourceforge.net>
// Copyright (C) 2000-2005 Vincent LE PRINCE
// This file is part of the TRUEVISION Package

//   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.  */
//*******************************************************************************************
#include "include/undo.h"
#include "include/main.h"
#include "include/viewmanager.h"
#include "include/preferences.h"
#include "include/objectlist.h"
#include "include/matlist.h"


//******************************************
// Undo Item
// each undo item describes an action that may be undone
// They are stored by the UndoRedo Manager in two lists
// one for undo and the other for redo
//******************************************

// Constructor used for undo action on Objects
UndoItem::UndoItem( UndoAction act, Object3D *obj, ObjectLayer *dest )
{
action = act;
layer = dest;

switch ( action )
	{
	case TV_UNDO_OBJ_CREATE:
		created = obj;
		break;
		
	case TV_UNDO_OBJ_DELETE:
		deleted = obj;
		parent_object = deleted->get_parent();
		position = ( parent_object == NULL ) ? layer->get_object_pos( deleted ) : parent_object->get_child_pos( deleted );		
		break;
		
	case TV_UNDO_OBJ_MOVE:
		created  = obj;
		parent_object = created->get_parent();
		position = ( parent_object == NULL ) ? layer->get_object_pos( created ) : parent_object->get_child_pos( created );				
		break;
	
	default:
		break;
	}
}

// Constructor used for undo action on Layers
UndoItem::UndoItem( UndoAction act, ObjectLayer *lay, int sens )
{
action = act;
layer = lay;
position = sens;
}


// Constructor used for undo action on Materials
UndoItem::UndoItem( UndoAction act, Material *mat, int pos )
{
action = act;
material = mat;
position = pos;
}

// Constructor used for undo action on ObjParams
UndoItem::UndoItem( UndoAction act, ObjParam *par, ObjParam *parc )
{
action = act;
param = par;
param_copy = parc;
}

// Constructor used for undo action on Obj3dobjects
UndoItem::UndoItem( UndoAction act, Object3D *obj1, Object3D*obj2 )
{
action = act;
changed = obj1;
changed_copy = obj2;
}


// Destructor
UndoItem::~UndoItem()
{
if ( action == TV_UNDO_OBJ_DELETE ) delete deleted;
if ( action == TV_UNDO_LAYER_DELETE ) delete layer;
if ( action == TV_UNDO_MAT_DELETE ) delete material;
if ( action == TV_UNDO_PARAM_CHANGED ) delete param_copy;
if ( action == TV_UNDO_OBJ3D_CHANGED ) delete changed_copy;
}





//******************************************
// UndoRedoManager
// Called by the interface to 'push' undo items, and to
// undo or redo them.
//******************************************

// Constructor
UndoRedoManager::UndoRedoManager( app_objs *appref )
{
app_ref = appref;
appref->undoman = this;
UndoList.clear();
RedoList.clear();
}


// Init
void UndoRedoManager::init()
{
for ( unsigned int i = 0 ; i < UndoList.size() ; i ++ ) 	delete UndoList[i];
UndoList.clear();
for ( unsigned int i = 0 ; i < RedoList.size() ; i ++ ) 	delete RedoList[i];
RedoList.clear();
set_widgets_sensitivity();
}

// Purge the lists
// used when loading a new scene
void UndoRedoManager::purge_redo_list()
{
//for ( unsigned int i = 0 ; i < RedoList.size() ; i ++ ) 	delete RedoList[i];
RedoList.clear();
}


// Set widgets sensitivity
// Called to set undo / redo widgets ( toolbar buttons & menu items )
// when list contents change.
void UndoRedoManager::set_widgets_sensitivity( )
{
/*if ( tbw_undo != NULL )
	{
	if ( UndoList.size() < 1 ) gtk_widget_set_sensitive( tbw_undo, FALSE );
	else gtk_widget_set_sensitive( tbw_undo, TRUE );
}

if ( tbw_redo != NULL )
	{
	if ( RedoList.size() < 1 ) gtk_widget_set_sensitive( tbw_redo, FALSE );
	else gtk_widget_set_sensitive( tbw_redo, TRUE );
	}
if ( mew_undo != NULL )
	{
	if ( UndoList.size() < 1 ) gtk_widget_set_sensitive( mew_undo, FALSE );
	else gtk_widget_set_sensitive( mew_undo, TRUE );
}

if ( mew_redo != NULL )
	{
	if ( RedoList.size() < 1 ) gtk_widget_set_sensitive( mew_redo, FALSE );
	else gtk_widget_set_sensitive( mew_redo, TRUE );
	}*/
	if ( UndoList.size() < 1 )
		gtk_action_set_sensitive( undo_action, false );
	else
		gtk_action_set_sensitive( undo_action, true );
	
	if ( RedoList.size() < 1 )
		gtk_action_set_sensitive( redo_action, false );
	else
		gtk_action_set_sensitive( redo_action, true );
	
}


// Undo object creation / deletion / move
void UndoRedoManager::push( UndoAction act, Object3D *obj, ObjectLayer *src )
{
UndoItem *item = new UndoItem( act, obj, src );
UndoList.push_back( item );
purge_redo_list();
set_widgets_sensitivity();
limit_undo_level();
}

// Undo layers creation / deletion / move		
void UndoRedoManager::push( UndoAction act, ObjectLayer *lay, int sens  )
{
UndoItem *item = new UndoItem( act, lay, sens );
UndoList.push_back( item );
purge_redo_list();
set_widgets_sensitivity();
limit_undo_level();
}

// Undo materials creation / deletion / move		
void UndoRedoManager::push( UndoAction act, Material *mat, int position  )
{
UndoItem *item = new UndoItem( act, mat, position );
UndoList.push_back( item );
purge_redo_list();
set_widgets_sensitivity();
limit_undo_level();
}

// Undo ObjParam changes
void UndoRedoManager::push( UndoAction act, ObjParam *param, ObjParam *param_copy )
{
UndoItem *item = new UndoItem( act, param, param_copy );
UndoList.push_back( item );
purge_redo_list();
set_widgets_sensitivity();
limit_undo_level();
}

// Undo Obj3D changes
void UndoRedoManager::push( UndoAction act, Object3D *obj1, Object3D *obj2 )
{
UndoItem *item = new UndoItem( act, obj1, obj2 );
UndoList.push_back( item );
purge_redo_list();
set_widgets_sensitivity();
limit_undo_level();
}

// Limit undo level
// Delete oldest undo item from the list
// when its size exceed the limit defined in preferences
void UndoRedoManager::limit_undo_level()
{
PREF_DEF
if ( UndoList.size() > (unsigned int)pref->undo_limit->value() )
	{
	delete UndoList[0];
	UndoList.erase( UndoList.begin() );
	}
}



//***************************************************
// Undo function
// The main function used
// in fact it is also used for redo ;-)
//***************************************************
void UndoRedoManager::undo( bool doredo  )
{		
// Define source & destination lists
// The determine if we act as undo or redo...
vector<UndoItem*> & src_list = doredo ? RedoList : UndoList;
vector<UndoItem*> & dest_list= doredo ? UndoList : RedoList;

UndoItem *item = src_list.back();
VMAN_DEF
OBJLIST_DEF

//cout << "\nGonna undo -> " << item->action; cout.flush();

switch ( item->action )
	{
	// Undo an object deletion
	case TV_UNDO_OBJ_DELETE:
		{
		if ( item->parent_object != NULL ) item->parent_object->insert_child( item->deleted, item->position );
		else item->layer->insert_object( item->deleted, item->position );
		UndoItem *inverse = new UndoItem( TV_UNDO_OBJ_CREATE, item->deleted, item->layer );
		dest_list.push_back( inverse );
		src_list.pop_back();
		item->set_action( TV_UNDO_NULL );
		delete item;
		}
		break;
		
	// Undo an object creation
	case TV_UNDO_OBJ_CREATE:
		{
		item->layer->delete_object( item->created );		
		vmanager->refresh();
		UndoItem *inverse = new UndoItem( TV_UNDO_OBJ_DELETE, item->created, item->layer );
		dest_list.push_back( inverse );
		src_list.pop_back();
		delete item;
		}
		break;
		
	// Undo an object move
	case TV_UNDO_OBJ_MOVE:
		{
		ObjectLayer *nlayer = item->created->get_layer();
		UndoItem *inverse = new UndoItem( TV_UNDO_OBJ_MOVE, item->created, nlayer );		
		nlayer->delete_object( item->created );		
		if ( item->parent_object != NULL ) item->parent_object->insert_child( item->created, item->position );
		else item->layer->insert_object( item->created, item->position );
		dest_list.push_back( inverse );
		src_list.pop_back();						
		delete item;
		}
		break;
	
	// Undo a layer creation
	case TV_UNDO_LAYER_CREATE:
		{
       UndoItem *inverse = new UndoItem( TV_UNDO_LAYER_DELETE, item->layer, objlist->get_layer_index(item->layer) );
       objlist->remove_layer( item->layer );
       dest_list.push_back( inverse );
       src_list.pop_back();
       delete item;
		}
		break;
		
		
	// Undo a layer deletion
	case TV_UNDO_LAYER_DELETE:
		{
		objlist->insert_layer( item->layer, item->position-1 );
		UndoItem *inverse = new UndoItem( TV_UNDO_LAYER_CREATE, item->layer, -1 );
		dest_list.push_back( inverse );
		src_list.pop_back();
		item->set_action( TV_UNDO_NULL );
		delete item;
		}
		break;
	
	// Undo a layer deletion
	case TV_UNDO_LAYER_MOVE:
		{
		objlist->move_layer( item->layer, item->position );
		item->position = ( item->position == 1 ) ? 2 : 1;
		dest_list.push_back( item );
		src_list.pop_back();
		}
		break;
	
	// Undo a material creation
	case TV_UNDO_MAT_CREATE:
		{
		MATLIST_DEF
       UndoItem *inverse = new UndoItem( TV_UNDO_MAT_DELETE, item->material, matlist->get_index_by_pointer(item->material) );
       matlist->remove_material( item->material );
       dest_list.push_back( inverse );
       src_list.pop_back();
       delete item;
		}
		break;
		
	// Undo a material deletion
	case TV_UNDO_MAT_DELETE:
			{
			MATLIST_DEF
			matlist->insert_material( item->material, item->position );
			UndoItem *inverse = new UndoItem( TV_UNDO_MAT_CREATE, item->material, -1 );
	       dest_list.push_back( inverse );
	       src_list.pop_back();
	       item->set_action( TV_UNDO_NULL );
	       delete item;			
			}
			break;
			
			
	// Undo an Object param change
	case TV_UNDO_PARAM_CHANGED:
		{
		item->param->swap_data( item->param_copy );
       dest_list.push_back( item );
       src_list.pop_back();	
		}
	   break;
		
	case TV_UNDO_OBJ3D_CHANGED:
		{
		item->changed->undo( item->changed_copy );
       dest_list.push_back( item );
       src_list.pop_back();				
		}
		break;
		
	default:
		break;
	}	
set_widgets_sensitivity();
}
