/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:      $Id: XObject.cpp,v 1.10 2001/04/04 12:42:36 aleksandras Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
#pragma implementation "XObject.h"
#pragma interface
#endif

// ;-)

///////////////////////////////////////////////
//											 //
// All files in X-framework are Copyright    //
// of Aleksandras Gluchovas 				 //
// 											 //
// e-mail: alex@soften.ktu.lt                //
//											 //
///////////////////////////////////////////////

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

#include "XObject.h"
#include "wx/region.h"
#include "wx/window.h"	   

#include "wx/log.h"

// FOR NOW:: to workaround absence of GetDeviceOrigin() in wxDC

XPoint _gCurCanvasOrigin = XPoint(0,0);

XPoint XObject::GetCanvasOrigin()
{
	return _gCurCanvasOrigin;
}

void XObject::SetCanvasOrigin( const XPoint& origin )
{
	_gCurCanvasOrigin = origin;
}

/***** Implementation for class XRect *****/

void XRect::Accumulate( const XPoint& point )
{
	if ( x == X_DEFAULT_POS && y == X_DEFAULT_POS )
	{
		x      = point.x;
		y      = point.y;
		width  = 0;
		height = 0;

		return;
	}

	if ( point.x < x )
	{
		width += x - point.x;
		x = point.x;
	}

	if ( point.y < y )
	{
		height += y - point.y;
		y = point.y;
	}

	if ( x + width < point.x )

		width += point.x - ( x + width );

	if ( y + height < point.y )

		height += point.y - ( y + height );
}

XRect XRect::InsersectionWith( const XRect& other ) const
{
	XRect result;

	result.x = ( x > other.x ) ? x : other.x;
	result.y = ( y > other.y ) ? y : other.y;

	PosT botX = ( x + width  < other.x + other.width  ) ? x + width  : other.x + other.width;
	PosT botY = ( y + height < other.y + other.height ) ? y + height : other.y + other.height;

	result.width  = botX - result.x;
	result.height = botY - result.y;

	// if upper-left and lower-right conners aren't correct
	// then return an empty rect - idengifying no intersection

	if ( result.width  < 0 ||
		 result.height < 0 )

		 return XRect();

	// to work-around "not-including last pixel" problem,
	// increase the area obtained intersection towards
	// the lower-right corner of the rectangle
	 
	result.width  += 0;
	result.height += 0;

	return result;
}

/***** Implementation for class XSegement *****/

bool XSegment::ColidesWith( const XSegment& other ) const
{
	XPoint colPoint = GetIntersection( other );

	return Contains( colPoint );
}

XPoint XSegment::GetIntersection( const XSegment& withOther ) const
{
	double k = GetSlope();
	double l = withOther.GetSlope();

	// if segments are parallel (or almost parallel)
	// then retun intersection somewhere in the infinitiy

	double k_l = k - l;

	double a = mFrom.y           - k * mFrom.x;
	double b = withOther.mFrom.y - l * withOther.mFrom.x;

	if ( ( mFrom.x == mTill.x                     ) && 
		 ( withOther.mFrom.x == withOther.mTill.x )
	   )
		XPoint();

	if ( ( mFrom.x == mTill.x                     ) )

		return XPoint( PosT(mFrom.x), PosT(l * mFrom.x + b) );

	if ( ( withOther.mFrom.x == withOther.mTill.x ) )

		return XPoint( PosT(withOther.mFrom.x), PosT(k * withOther.mFrom.x + a) );

	if ( k_l == 0.0 )

		return XPoint();

	double x = ( b - a ) / ( k_l );

	return XPoint( PosT(x), PosT(k * x + a) );
}

bool XSegment::Contains( const XPoint& point ) const
{
	if ( (mFrom == point) || (mTill == point) )
		return 1;

	// suppose the point lies on this line,
	// then the sum of distances from this point
	// to the first end of the segment and to the
	// second one have to be equal to the length
	// of the whole segment, i.e. segLen = ab + bc,
	// having the a-b-c sequence, where b is the test point

	double ab = point.VectorLength( mFrom );
	double bc = point.VectorLength( mTill );
	double ac = mFrom.VectorLength( mTill );

	double epsilon = 1.0;

	return ( fabs(ab + bc - ac) < epsilon );
}

bool XSegment::LiesNearBy( const XPoint& pnt, PosT maxDist )
{
	XPoint pnt2 = pnt;
	pnt2.x += 20;

	pnt2.RotateAround( pnt, GetAngle() + M_PI_2 );

	XPoint intersect = GetIntersection( XSegment( pnt, pnt2 ) );

	/*** OLD VERSION:: ***
	if ( Contains( intersect ) )

		return ( PosT(pnt.VectorLength( intersect )) <= maxDist );
	else
		return ( PosT(mFrom.VectorLength( intersect )) <= maxDist ||
				 PosT(mTill.VectorLength( intersect )) <= maxDist
		       );
	***/
	if ( Contains( intersect ) )

		return ( PosT(pnt.VectorLength( intersect )) <= maxDist );
	else
		return ( PosT(mFrom.VectorLength( pnt )) <= maxDist ||
				 PosT(mTill.VectorLength( pnt )) <= maxDist
		       );
}

/***** Implementation for class XPoly *****/

XPoly::XPoly() {}

XPoly::XPoly( const XPoly& other )
{
	for( size_t i = 0; i != other.mPoints.Count(); ++i )

		mPoints.Add( new XPoint( *other.mPoints[i] ) );
}

XPoly::~XPoly()
{
	WX_CLEAR_ARRAY( mPoints );
}

const XPoly& XPoly::operator=( const XPoly& other )
{
	RemoveAll();

	for( size_t i = 0; i != other.mPoints.Count(); ++i )

		mPoints.Add( new XPoint( *other.mPoints[i] ) );

	return *this;
}

void  XPoly::Add( const XPoint& pnt )
{
	mPoints.Add( new XPoint(pnt) );
}

bool  XPoly::Contains( const XPoint& pnt )
{
	// FOR NOW:: 

	return FALSE;

	/*
	bool clockWise = IsClockWise();

	XPoint a, b;
	XPointPtrListT::iterator i = GetPoints().begin();
	XPointPtrListT::iterator j = GetPoints().begin();

	++j;
	for ( ; i!= GetPoints().end(); )
	{
		// Creating two vectors : one represents shape segment
		// and the other is line from segment vertex to given
		// point

		if (j == GetPoints().end()) j = GetPoints().begin();

		a = XPoint( (*i).x - (*j).x, 
			(*i).y - (*j).y );

		b = XPoint( pnt.x - (*j).x, 
			pnt.y - (*j).y );

		// Turning vector a for angle PI/2 opposite clock direction
		a = XPoint( -a.y, a.x );

		// If the product of vectors a and b is less than 0, 
		// the point (x,y) is on the left side of the segment and
		// outside the polygon (polygon should be created clock-wise).

		double vectorProduct = a.x * b.x + a.y * b.y;

		if ( ((vectorProduct < 0) && clockWise) || 
			 ((vectorProduct > 0) && !clockWise) 
		   )

			return 0;
		++i;
		++j;
	}

	return 1;
	*/
}

bool XPoly::IsClockWise()
{
	wxASSERT( mPoints.Count() > 1 );

	// calculating point which is insdide of polygon

	AngleT firstAng  = get_ox_angle( *mPoints[0], *mPoints[1] );
	AngleT secondAng = get_ox_angle( *mPoints[1], *mPoints[2] );

	AngleT diff = firstAng - secondAng;

	// normalize result
	if ( diff < 0 ) diff += 2*M_PI;

	return ( diff < M_PI );
}

XRect XPoly::GetBoundingRect()
{
	wxASSERT( mPoints.Count() > 1 );

	XRect bounds( *mPoints[0], XDim(0,0) );

	for( size_t i = 1; i != mPoints.Count(); ++i )
	
		bounds.Accumulate( *mPoints[i] );

	return bounds;
}

void XPoly::CreateFromRect( const XRect& rect )
{
	WX_CLEAR_ARRAY( mPoints )

	mPoints.Add( new XPoint( rect.UpperLeft()  ) );
	mPoints.Add( new XPoint( rect.UpperRight() ) );
	mPoints.Add( new XPoint( rect.LowerRight() ) );
	mPoints.Add( new XPoint( rect.LowerLeft()  ) );
}

void XPoly::Draw( wxDC& dc )
{
	for( size_t i = 1; i < mPoints.Count(); ++i )
	
		dc.DrawLine( mPoints[i-1]->x, mPoints[i-1]->y,
			         mPoints[i]->x,   mPoints[i]->y );
}

void XPoly::SolidDraw( wxDC& canvas)
{
	// FOR NOW::
	wxPoint points[32];

	wxASSERT( mPoints.Count() <= 32 );

	for( size_t i = 0; i != mPoints.Count(); ++i )
	{
		points[i].x = mPoints[i]->x;
		points[i].y = mPoints[i]->y;
	}

	canvas.DrawPolygon( mPoints.Count(), points );
}

void XPoly::Generate( const XPointPtrListT& pointString, int width )
{
	WX_CLEAR_ARRAY( mPoints )

	size_t i;
	int    half_width = width/2;

	for( i = 0; i != pointString.Count(); ++i )
	{
		XPoint& pnt = *new XPoint( *pointString[i] );

		AngleT oxAngle;

		if ( i == pointString.Count() - 1 )

			oxAngle = get_ox_angle( *pointString[i-1],
									*pointString[i]   );
		else
			oxAngle = get_ox_angle( *pointString[i],
									*pointString[i+1] );

		pnt.Move( half_width, oxAngle + M_PI_2 );
		
		mPoints.Add( &pnt );
	}

	i = pointString.Count() - 1;

	do {
		XPoint& pnt = *new XPoint( *pointString[i] );

		AngleT oxAngle;

		if ( i == 0 )

			oxAngle = get_ox_angle( *pointString[i+1],
									*pointString[i]   );
		else
			oxAngle = get_ox_angle( *pointString[i-1],
									*pointString[i]   );

		pnt.Move( half_width, oxAngle + M_PI_2 );
		
		mPoints.Add( &pnt );

		if ( i == 0 ) break;
		--i;

	} while(1);
}

bool XPoly::LiesNearBy( const XPoint& pnt, PosT maxDist )
{
	for( size_t i = 1; i != mPoints.Count(); ++i )		

		if ( XSegment( *mPoints[i-1], *mPoints[i] ).
				LiesNearBy( pnt, maxDist )
		   )
		   return 1;

	return 0;
}

XPointPtrListT& XPoly::GetPointString( int width )
{
	// TBD:::
	wxASSERT(0);
	return mPoints;
}

void XPoly::RemoveAll()
{ 
	WX_CLEAR_ARRAY( mPoints )
}

void XPoly::RotateAround( const XPoint& origin, 
				          AngleT        angle
						)
{
	for( size_t i = 0; i != mPoints.Count(); ++i )

		mPoints[i]->RotateAround( origin, angle );
}

void XPoly::Translate( const XPoint& disp )
{
	for( size_t i = 0; i != mPoints.Count(); ++i )
	{
		XPoint& pnt = *mPoints[i];

		pnt.x += disp.x;
		pnt.y += disp.y;
	}
}

bool XPoly::Contains( const XPoint& pnt, PosT maxDist )
{
	// TBD:::
	wxASSERT(0);
	return 0;
}

/***** Implementation for class XCanvasCache *****/

XCanvasCache::XCanvasCache()
	: mpBufDc ( NULL ),
	  mpBufBmp( NULL )
{}

void XCanvasCache::DestroyBuffer()
{
	if ( mpBufDc ) 
	{
		mpBufDc->SelectObject( wxNullBitmap );
		delete mpBufDc;
	}

	if ( mpBufBmp ) delete mpBufBmp;

	mpBufDc  = NULL;
	mpBufBmp = NULL;
}

void XCanvasCache::Create( XRect& region, XObject* pObj, wxDC& srcDc )
{
	mRegion = region;

	if ( !mpBufDc )
	{
		mpBufDc  = new wxMemoryDC( &pObj->GetCanvas() );
		mpBufBmp = new wxBitmap( region.width, region.height );
		//mpBufBmp = new wxBitmap( 800*78/80, 600*78/80, 16 );
		mpBufDc->SelectObject( *mpBufBmp );
		int depth = mpBufBmp->GetDepth();
		mpBufDc->SetFont( srcDc.GetFont() );
	}
	else
	{
		if ( mpBufBmp->GetWidth() < region.width ||
			 mpBufBmp->GetHeight() < region.height )
		{
			mpBufDc->SelectObject( wxNullBitmap );
			delete mpBufBmp;

			PosT w = ( ( region.width  / 100 ) * 100 ) + 100;
			PosT h = ( ( region.height / 100 ) * 100 ) + 100;

			mpBufBmp = new wxBitmap( w, h );

			mpBufDc->SelectObject( *mpBufBmp );
		}
	}

	mpBufDc->SetDeviceOrigin( -region.x,
		                      -region.y );

	// FIXME:: +/- 1pix problem (bacground object does not fill upperlower pixel)

	mRegion.width += 1;
	mRegion.height += 1;

	XObject::SetUpdateRegion( *mpBufDc, mRegion );

	/* OLD STUFF:: DBG
	XPen pen( X_RGB( 0,255,0 ) );
	mpCache->SelectPen( pen );
	mpCache->MoveTo( XPoint(0,0) );
	mpCache->LineTo( XPoint( dim.width,dim.height ) );
	*/
}

XCanvasCache::~XCanvasCache()
{
	DestroyBuffer();
}

wxDC& XCanvasCache::GetCacheCanvas()
{
	// cache should have been already created with Create(...)
	wxASSERT( mpBufDc );
	return *mpBufDc;
}

void XCanvasCache::PaintCacheTo( wxDC& destCanvas )
{
	wxASSERT( mpBufDc );

	bool sucess = destCanvas.Blit( mRegion.x, mRegion.y,
					               mRegion.width, mRegion.height,
					               mpBufDc,
					               mRegion.x, mRegion.y, wxCOPY 
				                );
}

/***** Implementation for class XSubject *****/

XSubject::XSubject()

	: mDestructionInProgress( FALSE )
{}

XSubject::~XSubject()
{
}

void XSubject::DestroySubject()
{
	wxASSERT( !mDestructionInProgress );

	XObjListT lastObservers = mObservers;

	mObservers.Empty();

	mDestructionInProgress = 1;

	// subject will automatically destroy it's observers (if any)

	while( lastObservers.Count() > 0 )
	{
		XObject* pObj = lastObservers[0];

		lastObservers.Remove( lastObservers[0] );

		if ( pObj->HasParent() )
		{
			pObj->SetSubject( 0 ); // doesn't see subject anymore

			XCollection* pCol = (XCollection*)&pObj->GetParent();

			pCol->Remove( pObj );
		}

		else 
			delete pObj;
	}

	delete this;
}

void XSubject::AddObserver( XObject* pObj )
{
	// guard against recursive notifications upon destruction
	if ( mDestructionInProgress ) return;

	mObservers.Add( pObj );

	pObj->SetSubject( this );
}

void XSubject::RemoveObserver( XObject* pObj )
{
	// guard against recursive notifications upon destruction
	if ( mDestructionInProgress ) return;

	for( size_t i = 0; i != mObservers.Count(); ++i )

		if ( mObservers[i] == pObj )
		{
			mObservers.Remove( i );

			// subject destroys itself if no more observers
			// left :

			if ( mObservers.Count() == 0 )

				delete this;

			return;
		}

	wxASSERT(0); // the given observer should exist in the subject's list
}

void XSubject::NotifyObserver( XObject* pObserver, bool isInitiator )
{
	// guard against recursive notifications upon destruction
	if ( mDestructionInProgress ) return;

	if ( pObserver->HasParent() )

		pObserver->GetParent().InvalidateRect( pObserver->GetInclusiveBounds() );

	pObserver->SubjectChanged( isInitiator );

	if ( pObserver->HasEditor() )

		pObserver->GetEditor().SubjectChanged( isInitiator );

	if ( pObserver->HasParent() )
	{
		pObserver->GetParent().InvalidateRect( pObserver->GetInclusiveBounds() );

		pObserver->GetParent().RefreshNow();
	}
}

void XSubject::NotifySubjectChanged( XObject* pInitiator )
{
	// guard against recursive notifications upon destruction
	if ( mDestructionInProgress ) return;

	for( size_t i = 0; i != mObservers.Count(); ++i )

		NotifyObserver( mObservers[i], 
			            mObservers[i] == pInitiator ); 
}

/***** Implementation for class XObject *****/

IMPLEMENT_DYNAMIC_CLASS( XObject, wxObject )

XObject::XObject()
	: mpParent ( NULL ),
	  mpEditor ( NULL ),
	  mpSubject( NULL )
{}

XObject::~XObject()
{
	if ( mpEditor ) delete mpEditor;
}

XSubject* XObject::GetSubject()
{
	return mpSubject;
}

void XObject::SetSubject( XSubject* pSubject )
{
	if ( mpSubject && pSubject != 0 )

		mpSubject->RemoveObserver( this );

	mpSubject = pSubject;
}

wxDC& XObject::GetCanvas()
{
	// if this method is not overriden, the instance
	// should have to have a parent then

	if ( !HasParent() )
	{
		// DBG:: GetCanvas() method should be inmplmented by any top-level object!

		wxASSERT( 0 );

		wxDC* pDummy = NULL; // make compiler happy
		return *pDummy;
	}

	return GetParent().GetCanvas();
}

wxWindow* XObject::GetWindow()
{
	if ( !HasParent() ) return NULL;

	return GetParent().GetWindow();
}

bool XObject::HasParent()
{
	return (mpParent != 0);
}

XObject& XObject::GetParent()
{
	// DBG:: the parent should exist if this method is invoked
	wxASSERT( mpParent );
	return *mpParent;
}

void XObject::SetParent( XObject* pParent )
{
	// NOTE:: no checking for previous value is done here
	mpParent = pParent;
}

bool XObject::HasEditor()
{
	return (mpEditor != 0);
}

XEditor& XObject::GetEditor()
{
	// DBG:: the editor should exist if this method is invoked
	wxASSERT( mpEditor );

	// NOTE:: no type checking is perfromed here
	return *((XEditor*)mpEditor);
}

void XObject::SetEditor( XEditor* pEditor )
{
	if ( mpEditor ) delete mpEditor;

	mpEditor = pEditor;

	if ( pEditor )
	{
		pEditor->SetParent( this );

		pEditor->Init();
	}
}

XEditor* XObject::UnlinkEditor()
{
	XEditor* tmp = mpEditor;

	mpEditor = 0;

	return tmp;
}

void XObject::SetMouseFocus( XObject* pForObject, bool captureMouse )
{ 
	if (HasParent())

		GetParent().SetMouseFocus( this, captureMouse );
}

void XObject::ResetMouseFocus( XObject* pForObject, bool releaseMouse )
{ 
	if (HasParent())

		GetParent().ResetMouseFocus( this, releaseMouse );
	
}

XObject* XObject::GetMouseFocus()
{
	if (HasParent())

		return GetParent().GetMouseFocus();
	else
		return NULL;
}

bool XObject::IsSelected( XObject* pObj )
{
	// FOR NOW:::
	wxASSERT( pObj == 0 || pObj == this );

	if ( HasParent() ) 

		return GetParent().IsSelected( this );
	else
		return FALSE;
}

void XObject::SetSelection( XObjListT& newSelection, bool refreshNow )
{
	if ( HasParent() )

		GetParent().SetSelection( newSelection, refreshNow );
}

void XObject::SetSelection( XObject& obj )
{
	XObjListT lst; lst.Add( &obj );
	SetSelection( lst );
}

bool XObject::Contains( const XPoint& point ) 
{
	return GetBounds().Contains( point );
}

void XObject::NotifyModified( XObject* pObj )
{
	// FOR NOW:::
	wxASSERT( pObj == 0 || pObj == this );

	if ( HasParent() )

		GetParent().NotifyModified( this );
}

void XObject::NotifyWillModify( XObject* pObj )
{
	// FOR NOW:::
	wxASSERT( pObj == 0 || pObj == this );

	if ( HasParent() )

		GetParent().NotifyWillModify( this );
}

void XObject::NotifyWillChange( XObject* pObj )
{
	if ( HasParent() )

		GetParent().NotifyWillChange( ( pObj ) ? pObj : this );
}

void XObject::NotifyWillRemove( XObject* pObj )
{
	if ( HasParent() )

		GetParent().NotifyWillRemove( ( pObj ) ? pObj : this );
}

void XObject::NotifyAdded( XObject* pObj )
{
	if ( HasParent() )

		GetParent().NotifyAdded( ( pObj ) ? pObj : this );
}

XEditor* XObject::CloneEditorCopy( bool identical )
{
	return ( HasEditor() ) 
		   ? (XEditor*)GetEditor().CloneCopy( identical ) 
		   : NULL;
}

void XObject::StartBatch()
{
	if ( HasParent() )

		GetParent().StartBatch();
}

void XObject::FinishBatch()
{
	if ( HasParent() )

		GetParent().FinishBatch();
}

void XObject::MoveToFront( XObject* pObj )
{
	if ( HasParent() )
	{
		if (pObj == 0)
			GetParent().MoveToFront( this );
		else
			GetParent().MoveToFront( pObj );
	}
}

void XObject::SetShape( XPoly& shape ) 
{
	SetBounds( shape.GetBoundingRect() );
}

void XObject::GetShape( XPoly& shape ) 
{
	shape.CreateFromRect( GetBounds() );
}

void XObject::SetFocus( XObject* pFocus, bool refreshNow )
{
	if ( mpParent )

		mpParent->SetFocus( pFocus, refreshNow );
}

XObject* XObject::GetFocus()
{
	if ( mpParent )

		mpParent->GetFocus();

	return NULL;
}

void XObject::RefreshNow()
{
	if ( HasParent() )

		GetParent().RefreshNow();
	else 
		Draw( GetCanvas() );
}

bool XObject::HasCanvas()
{ 
	if ( HasParent() ) 
		
		return GetParent().HasCanvas();
	else
		return 0; 
}

void XObject::GetMouseEventInfo( wxMouseEvent& evt )
{
	if ( HasParent() )

		GetParent().GetMouseEventInfo( evt );
}

void XObject::InvalidateRect( const XRect& area ) 
{
	if ( HasParent() )

		GetParent().InvalidateRect( area );
}

XRect XObject::GetInclusiveBounds()
{
	XRect incBounds = GetBounds();

	if ( HasEditor() )

		incBounds.Accumulate( GetEditor().GetBounds() );

	return incBounds;
}

void XObject::RefreshThisObject()
{
	InvalidateRect( GetInclusiveBounds() );

	RefreshNow();
}

XRect XObject::GetUpdateRegion( wxDC& canvas )
{
	long x,y,width,height;

	canvas.GetClippingBox( &x, &y, &width, & height );

	return XRect( x, y, width, height );
}

void XObject::SetUpdateRegion( wxDC& canvas, const XRect& region )
{
	canvas.DestroyClippingRegion();

	canvas.SetClippingRegion( (long)region.x,      (long)region.y, 
		                       (long)region.width, (long)region.height );
}

/***** Implementation for class XEditor *****/

XEditor::XEditor()
	: mAutoRepaint( TRUE ),
	  mAutoRise( TRUE )
{}

void XEditor::SetAutoRepaint( bool repaintIsOn )
{
	mAutoRepaint = repaintIsOn;
}

void XEditor::SetAutoRise( bool riseOnSelect )
{
	mAutoRise = riseOnSelect;
}

void XEditor::SetEditArea( const XRect& area )
{
	mEditArea = area;
}

XRect& XEditor::GetEditArea()
{
	return mEditArea;
}

bool XEditor::GetAutoRepaint()
{
	return mAutoRepaint;
}

bool XEditor::IsActive()
{
	return GetParent().IsSelected( &GetParent() );
}

XObject& XEditor::GetEditee()
{
	return *mpParent;
}

bool XEditor::Contains( const XPoint& point )
{
	return mpParent->Contains( point );
}

void XEditor::NotifyObjectChanged()
{
	InvalidateRect( GetBounds() );
	XPoly shape;

	GetParent().GetShape( shape );
	SetShape( shape );

	InvalidateRect( GetBounds() );
}

/***** Implementation for class XCollection *****/

XCollection::XCollection()
	: mpLocator   ( NULL ),
	  mpMouseFocus( NULL ),
	  mpBkObj     ( NULL ),
	  mCachingOn  ( TRUE ),

	  mpCanvas    ( NULL ),
	  mpWindow    ( NULL ),

	  mpFocus   ( NULL ),

	  mpCmdMgr    ( NULL ),
	  mpLastEventReceiver( NULL ),

	  mLastInsertionPos( 20, 20 ),
	  mInsertionOfs    ( 40, 40 )
{}

XPoint XCollection::GetLastInsertionPos()
{
	return mLastInsertionPos;
}

XPoint XCollection::GetInsetionOffset()
{
	return mInsertionOfs;
}

XPoint XCollection::GetNextInsertionPos()
{
	mLastInsertionPos.x += mInsertionOfs.x;
	mLastInsertionPos.y += mInsertionOfs.y;

	return mLastInsertionPos;
}

void XCollection::SetLastInsertionPos( const XPoint& pos )
{
	mLastInsertionPos = pos;
}

void XCollection::SetInsertionOfs( const XPoint& ofs )
{
	mInsertionOfs = ofs;
}

void XCollection::MarkBackgroundObj( XObject& obj )
{
	mpBkObj = &obj;
	
	// FIMXE:: this is workaround for infinite dimensions of bg objects

	mDirtyArea = XRect();
}

XObject* XCollection::GetBackgroundObj()
{
	return mpBkObj;
}

bool XCollection::IsBackgroundObj( XObject& obj )
{
	return ( &obj == mpBkObj );
}

void XCollection::CanvasChanged()
{
	for( size_t i = 0; i != mObjects.Count(); ++i )

		mObjects[i]->CanvasChanged();
}

void XCollection::SetImageCaching( bool isOn )
{
	mCachingOn = isOn;
}

XCollection::~XCollection()
{
	if ( mpLocator ) delete mpLocator;

	for( size_t i = 0; i != mObjects.Count(); ++i )

		delete mObjects[i];

	if ( mpCanvas ) delete mpCanvas;
}

/*** protected interface ***/

size_t XCollection::GetObjNr( XObject* pObj )
{
	size_t i;
    for( i = 0; i != mObjects.Count(); ++i )

		if ( pObj == mObjects[i] )

			return i;

	// the given object should exist in this collection
	wxASSERT(0);

	return i;
}

size_t XCollection::SetZOrderForObject( XObject* pObj, size_t zOrderNo )
{
	if ( GetObjNr( pObj ) == zOrderNo ) return zOrderNo;

	Detach( pObj );

	if ( zOrderNo >= mObjects.Count() )
	{
		mObjects.Add( pObj );

		return mObjects.Count() - 1;
	}

	mObjects.Insert( pObj, zOrderNo );

	return zOrderNo;
}

void XCollection::InvalidateGroup( XObjListT& lst )
{
	for( size_t i = 0; i != lst.Count(); ++i )

		InvalidateRect( lst[i]->GetInclusiveBounds() );
}

bool XCollection::ObjectPresent( XObject* obj )
{
	for( size_t i = 0; i != mObjects.Count(); ++ i )

		if ( mObjects[i] == obj ) return TRUE;

	return FALSE;
}

bool XCollection::DispatchTo( XObject& target, XEventInfo& evInfo )
{
	// memorize it (used for generation of OnMouseLeave() )

	switch (evInfo.mType)
	{
		case X_MOUSE_UP   : return target.OnMouseUp  ( evInfo.mPos );
		case X_MOUSE_DOWN : return target.OnMouseDown( evInfo.mPos );
		case X_MOUSE_MOVE : return target.OnMouseMove( evInfo.mPos );
		
		case X_DBL_CLICK  : return target.OnDblClick( evInfo.mPos );
		case X_RIGHT_CLICK: return target.OnRightClick( evInfo.mPos );

		case X_KEYBOARD_EVENT : return target.OnKeyDown( evInfo.mKeyInfo );

				// DBG:: bogus event type
		default: wxASSERT(0);
	}

	return FALSE;
}

/*** public interface ***/

void XCollection::FindObjectsInRect( const XRect& area, 
									 XObjListT&   objects,
									 bool         ignoreBackgroundObj
								  )
{
	if ( mpLocator )

		mpLocator->FindObjectsInRect( area, objects );
	else

		for( size_t i = 0; i != mObjects.Count(); ++i )

			if ( !mObjects[i]->GetInclusiveBounds().			
					InsersectionWith( area ).
						IsEmpty()
				)

			objects.Add( mObjects[i] );
	
	// skip background object is required

	if ( ignoreBackgroundObj )

		for( size_t i = 0; i != objects.Count(); ++i )

			if ( objects[i] == mpBkObj )
			{
				objects.Remove( i );
				return;
			}
}

XCanvasCache XCollection::mCache;

void XCollection::Draw( wxDC& canvas )
{
	XRect dirtyArea = XObject::GetUpdateRegion( canvas );

	wxDC* pCanvas = &canvas;

	/* // OLD VERSION:::
	if ( mCachingOn && !HasParent() )
	{
		cache.Create( dirtyArea, canvas );
		pCanvas = &cache.GetCacheCanvas();
	}*/

	if ( mCachingOn )
	{
		mCache.Create( dirtyArea, this, canvas );
		pCanvas = &mCache.GetCacheCanvas();
	}

	size_t i;

	if ( mpLocator )
	{
		XObjListT mLst;

		mpLocator->FindObjectsInRect( dirtyArea, mLst );

		for( i = 0; i != mLst.Count(); ++i )

			if ( mLst[i]->HasEditor() )

				mLst[i]->GetEditor().Draw( *pCanvas );
			else
				mLst[i]->Draw( *pCanvas );
	}
	else
		// otherwise, find and draw objects in area by iterating 
		// through the eintire collection

		for( i = 0; i != mObjects.Count(); ++i )

			if ( !mObjects[i]->GetInclusiveBounds().			
					InsersectionWith( dirtyArea ).
						IsEmpty()
				)
			{
				if ( mObjects[i]->HasEditor() )

					mObjects[i]->GetEditor().Draw( *pCanvas );
				else
					mObjects[i]->Draw( *pCanvas );
			}

	/* // OLD VERSION:::
	if ( mCachingOn && !HasParent() )
	{
		cache.PaintCacheTo( canvas );
	}*/

	if ( mCachingOn )
	{
		mCache.PaintCacheTo( canvas );
	}

}

void XCollection::SetMouseFocus( XObject* pForObject, bool captureMouse )
{
	// muose focus cannot be set twice;
	wxASSERT( mpMouseFocus == 0 );

	mpMouseFocus = pForObject;

	XObject::SetMouseFocus( this );

	if ( captureMouse ) GetWindow()->CaptureMouse();
}

void XCollection::ResetMouseFocus( XObject* pForObject, bool releaseMouse )
{
	// mouse focus should have been set previously

	
	// FOR NOW::: FIXME::
	//wxASSERT( mpMouseFocus != 0 );

	mpMouseFocus = 0;

	XObject::ResetMouseFocus( this );

	if ( releaseMouse ) GetWindow()->ReleaseMouse();
}

XObject* XCollection::GetMouseFocus()
{
	if ( mpMouseFocus )

		return mpMouseFocus;
	else
		return XObject::GetMouseFocus();
}

XObject* XCollection::FindObjectAt( const XPoint& location, 
									bool ignoreMouseFocus,
									bool ignoreBacgroundObj 
								  )
{
	if ( (!ignoreMouseFocus) && mpMouseFocus )

		return mpMouseFocus;

	XObjListT mLst;
	size_t    i;

	if ( mpLocator )

		mpLocator->FindObjectsInRect( XRect( location, XDim(0,0) ), mLst );
	else
		{

			// first make a rought esitmate testing the bounding box
			for( i = 0; i != mObjects.Count(); ++i )

				if ( mObjects[i]->GetInclusiveBounds().Contains( location ) )

					mLst.Add( mObjects[i] );
		}

	if ( mLst.Count() == 0 ) 
		return 0;

	// start from the top object
	i = mLst.Count() - 1;

	do
	{
		// now, check if the point of insterest
		// is preciselly inside the shape o the object

		XObject& obj = *mLst[i];

		if ( !IsBackgroundObj( obj ) )
		{
			if ( obj.HasEditor() )
			{
				if ( obj.GetEditor().Contains( location ) )

				 return &obj;
			}
			else
			if ( obj.Contains( location ) )

				return &obj;
		}

		if ( i == 0 ) break;

		--i;

	} while(1);

	// return the object, which resides on the top of z-ordered list

	if ( ignoreBacgroundObj && 
		 IsBackgroundObj( *mLst[ mLst.Count() - 1 ] )
	   )
				return 0;

	//return mLst[ mLst.Count() - 1 ];

	return NULL;
}

bool XCollection::DispatchEvent( XEventInfo& evInfo )
{
	if ( X_KEYBOARD_EVENT )
	{
		if ( mSelection.Count() != 1 )

			return OnKeyDown( evInfo.mKeyInfo );
		else
		{
			if ( !mSelection[0]->OnKeyDown( evInfo.mKeyInfo ) )

				return OnKeyDown( evInfo.mKeyInfo );
			else
				return TRUE;
		}
	}

	XObject* pTarget = FindObjectAt( evInfo.mPos
									 , FALSE // does not ignore mouse-focus
								   );

	// generate mouse-leave event if necessary

	if ( pTarget != mpLastEventReceiver && mpLastEventReceiver &&
		 ObjectPresent( mpLastEventReceiver ) )
	{
		if ( mpLastEventReceiver->HasEditor() &&
			 !mpLastEventReceiver->GetEditor().OnMouseLeave() )

			mpLastEventReceiver->OnMouseLeave();

		mpLastEventReceiver = pTarget;
	}

	// try dispatching event first to object's editor,
	// then to the object itself

	if ( pTarget ) 
	{
		mpLastEventReceiver = pTarget;

		bool hasEditor = pTarget->HasEditor();

		if ( ( hasEditor                                   ) &&
			 ( !DispatchTo( pTarget->GetEditor(), evInfo ) )
		   )
			return DispatchTo( *pTarget, evInfo );
		else
		{
			if ( !hasEditor )

				return DispatchTo( *pTarget, evInfo );
			else
				return TRUE;
		}
	}

	return FALSE;
}

bool XCollection::OnMouseDown( const XPoint& pos )
{
	XEventInfo event( X_MOUSE_DOWN );
	event.mPos = pos;

	return DispatchEvent( event );
}

bool XCollection::OnMouseUp  ( const XPoint& pos )
{
	XEventInfo event( X_MOUSE_UP );
	event.mPos = pos;

	return DispatchEvent( event );
}

bool XCollection::OnMouseMove( const XPoint& pos )
{
	XEventInfo event( X_MOUSE_MOVE );
	event.mPos = pos;

	return DispatchEvent( event );
}

bool XCollection::OnDblClick  ( const XPoint& pos )
{
	XEventInfo event( X_DBL_CLICK );
	event.mPos = pos;

	return DispatchEvent( event );
}

bool XCollection::OnRightClick( const XPoint& pos )
{
	XEventInfo event( X_RIGHT_CLICK );
	event.mPos = pos;

	return DispatchEvent( event );
}

bool XCollection::OnKeyDown  ( const wxKeyEvent& keyInfo )
{
	XEventInfo event( X_KEYBOARD_EVENT );
	event.mKeyInfo = keyInfo;

	return DispatchEvent( event );
}

void XCollection::Insert( XObject* pObject, bool refreshNow )
{
	mObjects.Add( pObject );
	
	pObject->SetParent( this );

	pObject->Init(); // send notification upon setting the parent

	NotifyModified( pObject );

	// update inserted object if it's now possible 

	InvalidateRect( pObject->GetInclusiveBounds() );
		
	if ( refreshNow && HasCanvas() )
	
		RefreshNow();
}

void XCollection::Remove( XObject* pObject, bool refreshNow )
{
	pObject->ResetMouseFocus( pObject );

	InvalidateRect( pObject->GetInclusiveBounds() );

	if ( mpFocus == pObject )

			mpFocus = NULL;
	
	// FOR NOW:: dirty

	GetWindow()->SetCursor( wxCURSOR_ARROW );

	// remove it from current selection if present

	for( size_t i = 0; i != mSelection.Count(); ++i )
	
		if ( mSelection[i] == pObject )
		{
			mSelection.Remove( i );
			break;
		}

	mObjects.Remove( pObject );			

	delete pObject;

	if ( HasCanvas() && refreshNow )

		RefreshNow();
}

void XCollection::Detach( XObject* pObject )
{
	mObjects.Remove( pObject );

	if ( mpFocus == pObject )

		mpFocus = NULL;
}

void XCollection::SetFocus( XObject* pFocus, bool refreshNow )
{
	if ( pFocus == mpFocus ) return;

	if ( mpFocus )
	
		InvalidateRect( mpFocus->GetInclusiveBounds() );

	mpFocus = pFocus;

	if ( mpFocus )

		InvalidateRect( mpFocus->GetInclusiveBounds() );

	if ( refreshNow )

		RefreshNow();
}

XObject* XCollection::GetFocus()
{
	return mpFocus;
}

void XCollection::SelectObject( XObject& obj, bool refreshNow )
{
	XObjListT lst; lst.Add( &obj );

	SetFocus( &obj, FALSE );

	SetSelection( lst, refreshNow );
}

void XCollection::SetSelection( XObjListT& newSelection, bool refreshNow )
{
	size_t i;

	XObjListT prevSelection = mSelection;

	for( i = 0; i != newSelection.Count(); ++i )
		NotifyWillModify( newSelection[i] );
	for( i = 0; i != prevSelection.Count(); ++i )
		NotifyWillModify( prevSelection[i] );

	InvalidateGroup( prevSelection );

	mSelection = newSelection;

	for( i = 0; i != mSelection.Count(); ++i )
		NotifyModified( mSelection[i] );
	for( i = 0; i != prevSelection.Count(); ++i )
		NotifyModified( prevSelection[i] );

	InvalidateGroup( newSelection );

	for( i = 0; i != mSelection.Count(); ++i )
	{
		mSelection[i]->OnSelected();

		/*
		// OLD STUF::

		if ( mSelection[i] == mpSelectionFocus && i != 0 )
		{
			// selection focus should be always at the start of the list ( at [0] )

			XObject* pObj = mSelection[i];

			mSelection.Remove( i );

			mSelection.Insert( pObj, 0 );

			break;
		}
		*/
	}

	if ( refreshNow )
	
		RefreshNow();
}

void XCollection::ResetSelection()
{
	size_t i;

	for( i = 0; i != mSelection.Count(); ++i )
		NotifyWillModify( mSelection[i] );

	XObjListT prevSelection = mSelection;

	InvalidateGroup( mSelection );

	mSelection.Empty();

	for( i = 0; i != prevSelection.Count(); ++i )
		NotifyModified( prevSelection[i] );

	RefreshNow();
}

XObjListT& XCollection::GetSelection()
{
	return mSelection;
}

bool XCollection::IsSelected( XObject* pObj )
{
	for( size_t i = 0; i != mSelection.Count(); ++i )

		if ( pObj == mSelection[i] )

			return 1;

	return 0;
}

void XCollection::SelectAll()
{
	mSelection.Empty();

	XObjListT newSelection;
	for( size_t i = 0; i != mObjects.Count(); ++i )
	
		if ( !IsBackgroundObj( *mObjects[i] ) && mObjects[i]->IsSelectable() )
		
			newSelection.Add( mObjects[i] );

	InvalidateGroup( newSelection );

	mSelection = newSelection;

	InvalidateGroup( mSelection );

	if ( HasEditor() ) GetEditor().NotifyObjectChanged();

	RefreshNow();
}

void XCollection::DeleteSelection()
{
	XObjListT lst = mSelection;

	StartBatch();

	for( size_t i = 0; i != lst.Count(); ++i )
	
		if ( lst[i]->CanRemove() )
		{
			NotifyWillRemove( lst[i] );

			Remove( lst[i], FALSE );
		}

	FinishBatch();

	RefreshNow();
}

void XCollection::BringSelectionToFront()
{
	XObjListT lst = mSelection;

	StartBatch();

	for( size_t i = 0; i != lst.Count(); ++i )
	{	
		XObject* pObj = lst[i];

		Detach( pObj );

		mObjects.Add( pObj );
	}

	FinishBatch();

	SetSelection( lst );

	RefreshNow();
}

void XCollection::SendSelectionBack()
{
	XObjListT lst = mSelection;

	StartBatch();

	for( size_t i = 0; i != lst.Count(); ++i )
	{	
		XObject* pObj = lst[i];

		Detach( pObj );

		mObjects.Insert( pObj, 0 );
	}

	FinishBatch();

	SetSelection( lst );

	RefreshNow();
}

void XCollection::SetLocator( XObjLocator* pLocator )
{
	if ( mpLocator ) 
		delete mpLocator;

	mpLocator = pLocator;
}

void XCollection::NotifyModified( XObject* pObj )
{
	if ( mpLocator )
		mpLocator->NotifyModified( *pObj );
}

void XCollection::NotifyWillModify( XObject* pObj )
{
	if ( mpLocator )
		mpLocator->NotifyWillModify( *pObj );
}

void XCollection::InvalidateRect( const XRect& area )
{
	if ( HasParent() )

		XObject::InvalidateRect( area );
	else
		mDirtyArea.Accumulate( area );
}

void XCollection::RefreshNow()
{
	/*
	wxString msg;
	msg.Printf( "DBG:: RefreshNow() called with area: "
				"x = %d y = %d w = %d h = %d\n",
				mDirtyArea.x, mDirtyArea.y,
				mDirtyArea.width, mDirtyArea.height );

	wxLogMessage( msg );
	*/

	if ( HasParent() )
	{
		XObject::RefreshNow();

		return;
	}

	if ( !mDirtyArea.IsEmpty() )
	{
		mDirtyArea.width  += 1;
		mDirtyArea.height += 1;

		XObject::SetUpdateRegion( GetCanvas(), mDirtyArea );
	}
	else
	{
		GetCanvas().DestroyClippingRegion();
		return;
	}

	// obtain top-level parent
	XObject* pObj;
    for( pObj = this; pObj->HasParent();  )
		pObj = &pObj->GetParent();

	// OLD VERSION:::
	//pObj->Draw( pObj->GetCanvas() );

	if ( HasEditor() )		
		GetEditor().Draw( pObj->GetCanvas() );
	else
		Draw( pObj->GetCanvas() );

	// reset the dirty area to be empty 

	mDirtyArea = XRect();
}

void XCollection::MoveToFront( XObject* pObj )
{
	size_t objNr = GetObjNr( pObj );

	// check if object is not already on the top of z-order
	if ( objNr == mObjects.Count() - 1 ) return;

	NotifyWillModify( pObj );

	mObjects.Remove( objNr );

	mObjects.Add( pObj );

	NotifyModified( pObj );
}

XRect XCollection::GetBounds()
{
	return XRect( XPoint( -32768,-32768 ), XDim( 65536, 65536 ) );

	// OLD VERSION::
	//return XRect( XPoint(0,0), GetCanvas().GetCanvasDim() );

	// OLDer VERSION::
	/*
	XRect bounds;

	for( size_t i = 0; i != mObjects.Count(); ++i )
	
		if ( !IsBackgroundObj( *mObjects[i] ) )

			bounds = bounds.Accumulate( 
						mObjects[i]->GetInclusiveBounds() );

	return bounds;
	*/
}

bool XCollection::HasCanvas()
{
	return mpWindow != NULL;
}

wxDC& XCollection::GetCanvas()
{
	if ( !HasParent() && mpWindow )
	{
		if ( !mpCanvas )
		{
			mpCanvas = new wxClientDC( mpWindow );

			mpCanvas->SetFont( mpWindow->GetFont() );
		}

		mpCanvas->SetDeviceOrigin( _gCurCanvasOrigin.x, _gCurCanvasOrigin.y );

		return *mpCanvas;
	}
	else
		return XObject::GetCanvas();
}

void XCollection::SetWindow( wxWindow* pWnd )
{
	if ( mpCanvas )

		delete mpCanvas;

	mpWindow = pWnd;
}

void XCollection::GetMouseEventInfo( wxMouseEvent& evt )
{
	if ( mpEvtInfo )
	
		evt = *((wxMouseEvent*)mpEvtInfo);
	else
		XObject::GetMouseEventInfo( evt );
}

wxWindow* XCollection::GetWindow()
{
	if ( HasParent() ) return GetParent().GetWindow();

	return mpWindow;
}

void XCollection::SetCommandManager( XCommandManager* pMgr )
{
	if ( mpCmdMgr ) delete mpCmdMgr;

	mpCmdMgr = pMgr;
}

void XCollection::NotifyWillChange( XObject* pObj )
{
	if ( !mpCmdMgr ) 
		
		XObject::NotifyWillChange( pObj );
	else
		mpCmdMgr->NotifyWillChange( ( pObj  ) ? pObj : this );
}

void XCollection::NotifyWillRemove( XObject* pObj )
{
	if ( !mpCmdMgr ) 
		
		XObject::NotifyWillRemove( pObj );
	else
		mpCmdMgr->NotifyWillRemove( ( pObj  ) ? pObj : this );
}

void XCollection::NotifyAdded( XObject* pObj )
{
	if ( !mpCmdMgr ) 
		
		XObject::NotifyAdded( pObj );
	else
		mpCmdMgr->NotifyAdded( ( pObj  ) ? pObj : this );
}

void XCollection::StartBatch()
{
	if ( !mpCmdMgr ) 
		
		XObject::StartBatch();
	else
		mpCmdMgr->StartBatch();
}

void XCollection::FinishBatch()
{
	if ( !mpCmdMgr ) 
		
		XObject::FinishBatch();
	else
		mpCmdMgr->FinishBatch();
}

/***** Implementation for class XCommandInfo *****/

XCommandInfo::XCommandInfo( int type, XObject* pRealObj, size_t batchLevel )

	: mType           ( type ),
	  mpObjCopy       ( NULL ),
	  mpRealObj       ( pRealObj ),

	  mRealObjIsDelted( FALSE ),
	  mFinishesBatch  ( FALSE ),
	  mpLatestState   ( NULL )
{
	if ( pRealObj )
	{
		mpScene = (XCollection*) &pRealObj->GetParent();

		mZOrder = mpScene->GetObjNr( pRealObj );

		mpObjCopy = pRealObj->CloneCopy( TRUE );
	}

	if ( batchLevel == 0 )

		mFinishesBatch = TRUE;
}

XCommandInfo::~XCommandInfo()
{
	if ( mpObjCopy ) delete mpObjCopy;

	if ( mpLatestState ) delete mpLatestState;
}

XCommandInfo* XCommandInfo::GetLatestState() 

	{ return mpLatestState; }

void XCommandInfo::CaptureLatestState()
{
	if ( mpLatestState ) delete mpLatestState;

	mpLatestState = new XCommandInfo( XCMD_CHANGE, mpRealObj, 0 );
}

/***** Implementation for class XCommandManager *****/

size_t XCommandManager::mIdGenerator = 1;

XCommandManager::XCommandManager( XCollection* pScene, size_t maxCommands )

	: mpScene( pScene ),
	  mBatchLevel( 0 ),

	  mMaxCommands( maxCommands ),
	  mNextCmd   ( 0 ),

	  mPasteBias( 20, 20 ),

	  mQueueLocked( FALSE )
{
	pScene->SetCommandManager( this );
}

XCommandManager::~XCommandManager()
{
	Clear();
}

void XCommandManager::CheckBuffer()
{
	while( mCommands.Count() > mMaxCommands )
	{
		delete mCommands[0];

		mCommands.Remove( (size_t)0 );
	}
}

void XCommandManager::InsertCommand( XCommandInfo* pCmd )
{
	wxASSERT( !mQueueLocked );
	// check if object was capable of cloning itself
	// if not, the operation cannot be undone

	if ( pCmd->mpObjCopy == NULL )
	{
		delete pCmd; 
		return;
	}

	while( mNextCmd != mCommands.Count() )
	{
		// discard pending redoable operations upon
		// the first new operation

		delete mCommands[mNextCmd];

		mCommands.Remove( mNextCmd );
	}

	mCommands.Add( pCmd );

	++mNextCmd;

	CheckBuffer();
}

void XCommandManager::NotifyWillChange( XObject* pObj )

	{ InsertCommand( new XCommandInfo( XCMD_CHANGE, pObj, mBatchLevel ) ); }

void XCommandManager::NotifyWillRemove( XObject* pObj )
{ 
	InsertCommand( new XCommandInfo( XCMD_REMOVE, pObj, mBatchLevel ) ); 

	EncodeDeletedObject( pObj );
}

void XCommandManager::NotifyAdded( XObject* pObj )

	{ InsertCommand( new XCommandInfo( XCMD_ADD, pObj, mBatchLevel ) ); }

void XCommandManager::StartBatch()

	{ ++mBatchLevel; }

void XCommandManager::FinishBatch()
{
	--mBatchLevel;

	wxASSERT( mBatchLevel >= 0 );

	if ( mBatchLevel == 0 && mCommands.Count() )

		mCommands[ mCommands.Count()-1 ]->mFinishesBatch = TRUE;
}

void XCommandManager::RelinkReferencesTo( XObject* pNewObj, XCommandInfo& cmd )
{
	XObject* pOldObj   = cmd.mpRealObj;
	bool     isDeleted = cmd.mRealObjIsDelted;

	for( size_t i = 0; i != mCommands.Count(); ++i )
	{
		XCommandInfo& cur = *mCommands[i];

		if ( cur.mRealObjIsDelted == isDeleted && cur.mpRealObj == pOldObj )
		{
			cur.mRealObjIsDelted = FALSE;
			cur.mpRealObj        = pNewObj;

			if ( cur.mpLatestState )
			{
				cur.mpLatestState->mRealObjIsDelted = FALSE;
				cur.mpLatestState->mpRealObj        = pNewObj;
			}
		}
	}
}

void XCommandManager::EncodeDeletedObject( XObject* pDeleted )
{
	size_t newId = mIdGenerator++;

	for( size_t i = 0; i != mCommands.Count(); ++i )
	{
		XCommandInfo& cur = *mCommands[i];

		if ( !cur.mRealObjIsDelted && cur.mpRealObj == pDeleted )
		{
			cur.mRealObjIsDelted = TRUE;
			cur.mpRealObj        = (XObject*) newId;

			if ( cur.mpLatestState )
			{
				cur.mpLatestState->mRealObjIsDelted = TRUE;
				cur.mpLatestState->mpRealObj        = (XObject*) newId;;
			}
		}
	}
}

void XCommandManager::RemoveObject( XCommandInfo& cmd )
{
	XObject* pDeleted = cmd.mpRealObj;

	cmd.mpScene->Remove( cmd.mpRealObj, FALSE );

	// encode refrences to delted object using unique id generated for that purpose

	EncodeDeletedObject( pDeleted );

}

void XCommandManager::RestoreObject( XCommandInfo& cmd )
{
	XObject* pNewObj = cmd.mpObjCopy->CloneCopy( TRUE );

	if ( !pNewObj ) return;

	cmd.mpScene->Remove( cmd.mpRealObj, FALSE );

	cmd.mpScene->Insert( pNewObj, FALSE );

	cmd.mpScene->SetZOrderForObject( pNewObj, cmd.mZOrder );

	RelinkReferencesTo( pNewObj, cmd );
}

void XCommandManager::AddObject( XCommandInfo& cmd )
{
	XObject* pNewObj = cmd.mpObjCopy->CloneCopy( TRUE );

	cmd.mpScene->Insert( pNewObj, FALSE );

	cmd.mpScene->SetZOrderForObject( pNewObj, cmd.mZOrder );

	RelinkReferencesTo( pNewObj, cmd );
}

bool XCommandManager::HasModsAhead( size_t cmdNo )
{
	XObject* pObj = mCommands[cmdNo]->mpRealObj;

	for( size_t n = cmdNo + 1;  n != mCommands.Count(); ++n )
	
		if ( mCommands[n]->mpRealObj == pObj ) return TRUE;

	return FALSE;
}

bool XCommandManager::CanUndo()

	{ return ( mNextCmd != 0 ); }

bool XCommandManager::CanRedo()

	{ return ( mNextCmd != mCommands.Count() ); }

void XCommandManager::Undo()
{
	mQueueLocked = TRUE;

	wxASSERT( CanUndo() ); // DBG:: user-code should check it

	for(;;)
	{
		--mNextCmd;

		XCommandInfo& cmd = *mCommands[mNextCmd];

		if ( cmd.GetType() == XCMD_CHANGE )
		{
			if ( !cmd.GetLatestState() && !HasModsAhead( mNextCmd ) )

				cmd.CaptureLatestState();

			RestoreObject( cmd );
		}
		else
		if ( cmd.GetType() == XCMD_ADD )
		{
			RemoveObject( cmd );
		}
		else
		if ( cmd.GetType() == XCMD_REMOVE )
		{
			AddObject( cmd );
		}

		if ( mNextCmd == 0 || mCommands[mNextCmd-1]->FinishesBatch() )

		   break;
	}

	if ( mpScene->HasEditor() ) mpScene->GetEditor().NotifyObjectChanged();

	mpScene->RefreshNow();

	mQueueLocked = FALSE;
}

void XCommandManager::Redo()
{
	mQueueLocked = TRUE;

	wxASSERT( CanRedo() ); // DBG:: user-code should check it

	for(;;)
	{
		XCommandInfo& cmd = *mCommands[mNextCmd];

		if ( cmd.GetType() == XCMD_CHANGE )
		{
			if ( cmd.GetLatestState() )

				RestoreObject( *cmd.GetLatestState() );
			else
			{
				// search for the stored-state before subsequent-modification of the given object

				XObject* pObj = cmd.mpRealObj;

				for( size_t n = mNextCmd + 1; n != mCommands.Count(); ++n )
				
					if ( mCommands[n]->mpRealObj == pObj )
					{
						RestoreObject( *mCommands[n] );
						break;
					}
			}
		}
		else
		if ( cmd.GetType() == XCMD_ADD )
		{
			AddObject( cmd );
		}
		else
		if ( cmd.GetType() == XCMD_REMOVE )
		{
			RemoveObject( cmd );
		}

		++mNextCmd;

		if ( mNextCmd == mCommands.Count() ||
			 mCommands[mNextCmd-1]->FinishesBatch() )

			 break;
	}

	if ( mpScene->HasEditor() ) mpScene->GetEditor().NotifyObjectChanged();

	mpScene->RefreshNow();

	mQueueLocked = FALSE;
}

void XCommandManager::Clear()
{
	WX_CLEAR_ARRAY( mCommands )

	DestroyClipboard();
}

void XCommandManager::SetScene( XCollection* pScene )
{
	Clear();

	mpScene = pScene;
}

/*** operations on X-fmw's internal clipboard ***/

void XCommandManager::DestroyClipboard()
{
	WX_CLEAR_ARRAY( mClipboard )
}

bool XCommandManager::CanPaste()
{
	return mClipboard.Count() != 0;
}

bool XCommandManager::CanDelete()
{
	return mpScene->GetSelection().Count() != 0;
}

void XCommandManager::Delete()
{
	mpScene->DeleteSelection();
}

void XCommandManager::Copy( XCollection* pScene )
{
	DestroyClipboard();

	XObjListT& lst = pScene->GetSelection();

	for( size_t i = 0; i != lst.Count(); ++i )
	{
		XObject* pCopy = lst[i]->CloneCopy( FALSE );

		if ( pCopy ) mClipboard.Add( pCopy );
	}
}

void XCommandManager::Paste( XCollection* pScene )
{
	pScene->StartBatch();

	XObjListT selection;

	for( size_t i = 0; i != mClipboard.Count(); ++i )
	{
		XObject* pCopy = mClipboard[i]->CloneCopy( FALSE );


		if ( pCopy ) 
		{
			pScene->Insert( pCopy, FALSE );

			XRect bounds = pCopy->GetBounds();

			bounds.x += mPasteBias.x;
			bounds.y += mPasteBias.y;

			pCopy->SetBounds( bounds );

			pScene->NotifyAdded( pCopy );

			selection.Add( pCopy );
		}
	}

	pScene->SetSelection( selection, FALSE );

	pScene->FinishBatch();

	if ( mpScene->HasEditor() ) mpScene->GetEditor().NotifyObjectChanged();

	mpScene->RefreshNow();
}

void XCommandManager::SetPasteBias( const XPoint& bias )
{
	mPasteBias = bias;
}

/***** Implementation for class XwxWindowHook *****/

BEGIN_EVENT_TABLE( XwxWindowHook, wxEvtHandler )

	EVT_MOUSE_EVENTS    ( XwxWindowHook::OnMouseEvent )
	//EVT_LEFT_DCLICK     ( XwxWindowHook::OnMouseEvent )
	EVT_KEY_DOWN        ( XwxWindowHook::OnKeyEvent   )
	EVT_PAINT           ( XwxWindowHook::OnPaint      )
	EVT_ERASE_BACKGROUND( XwxWindowHook::OnEraseBg    )

END_EVENT_TABLE()

XwxWindowHook::XwxWindowHook( wxWindow* pWnd, XCollection* pScene )

	: mpWnd( pWnd ),
	  mpScene( pScene )
{
	pWnd->PushEventHandler( this );

	pScene->SetWindow( pWnd );
}

XwxWindowHook::~XwxWindowHook()
{
	// FOR NOW::
	wxASSERT( mpWnd->GetEventHandler() == this );

	mpWnd->PopEventHandler();
}

void XwxWindowHook::WindowToCanvas( XPoint& pnt )
{
	pnt.x -= _gCurCanvasOrigin.x;
	pnt.y -= _gCurCanvasOrigin.y;
}

static XPoint lastPos(0,0);

static bool do_dispatch_evt( wxMouseEvent& event, const XPoint& pos, XObject& toObj )
{
	XPoint prevPos = lastPos;
	lastPos = pos;

	if ( event.LeftDClick() )

		return toObj.OnDblClick( pos );
	else
	if ( event.LeftDown() )

		return toObj.OnMouseDown( pos );
	else
	if ( event.LeftUp() )

		return toObj.OnMouseUp( pos );
	else
	if ( event.RightDown() )

		return toObj.OnRightClick( pos );
	else
	if ( event.RightUp() )

		return toObj.OnMouseUp( pos );
	else
	if ( event.Moving() && 
		 prevPos != pos  // NOTE:: do this stupid check to avoid fake mouse-move events
						 //		   which wxWin sends us sometimes
	   )

		return toObj.OnMouseMove( pos );
	else
		return FALSE; // make compiler happy
}

void XwxWindowHook::OnMouseEvent( wxMouseEvent& event )
{
	event.Skip();

	mLastMouseEvent = event;

	mpScene->SetEventInfo( &mLastMouseEvent );

	XPoint pos( event.m_x, event.m_y );
	
	WindowToCanvas( pos );

	if ( mpScene->HasEditor() && 
		 do_dispatch_evt( event, pos, mpScene->GetEditor() )
	   );
	else
		do_dispatch_evt( event, pos, *mpScene );

	mpScene->SetEventInfo( NULL );
}

void XwxWindowHook::OnKeyEvent( wxKeyEvent& event )
{
	// FIXME:: FOR NOW:: no acceleration
	//if ( !mpScene->OnKeyDown( event ) )

		event.Skip();
}

// FIXME:: something like Win32 ::GetUpdateRect() NEEDED, but portable...

#ifdef __WXMSW__

#include <windows.h>

#endif

void XwxWindowHook::OnPaint( wxPaintEvent& event )
{
	int w,h;

	mpWnd->GetClientSize( &w, &h );

	wxPaintDC canvas( mpWnd );
	canvas.SetFont( mpWnd->GetFont() );

	int rgnCount = 0;

#if 0
	wxRegionIterator upd( mpWnd->GetUpdateRegion()); // get the update rect list

     // FIXME:: stopped working...

	while (upd)
	{
		++rgnCount;

		int vX = upd.GetX();
		int vY = upd.GetY();
		int vW = upd.GetW();
		int vH = upd.GetH();

		XPoint pos(vX,vY);

		WindowToCanvas( pos );

		XRect area( pos, XDim( 2000, 2000 ) );

		//wxDC& canvas = mpScene->GetCanvas();

		XObject::SetUpdateRegion( canvas, area  );

		mpScene->Draw( canvas );

		upd ++ ;
	}
#endif

	{
		#if defined(__WXMSW__) && 0

			RECT r;
			int success = ::GetUpdateRect( (HWND)mpWnd->m_hWnd, &r, FALSE );

			XPoint pos(r.left - _gCurCanvasOrigin.x,r.top - _gCurCanvasOrigin.y);

			WindowToCanvas( pos );

			XRect area( pos, XDim( r.right-r.left, r.bottom - r.top ) );
		#else

			XPoint pos( -_gCurCanvasOrigin.x, -_gCurCanvasOrigin.y);

			//WindowToCanvas( pos );

			XRect area( pos, XDim( w,h ) );

		#endif

		// OLD-STUFF::
		// FIXME:: even on paint event we use client-dc

		//wxDC& canvas = mpScene->GetCanvas();

		wxDC& dc = mpScene->GetCanvas();
		XObject::SetUpdateRegion( dc, area  );
		XObject::SetUpdateRegion( dc, area  );

		mpScene->Draw( dc );
	}
}

void XwxWindowHook::OnEraseBg( wxEraseEvent& event )
{
	return; // flicker reduction
}

void XwxWindowHook::SetScene( XCollection* pScene )
{
	mpScene = pScene;
}
