/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/10
// RCS-ID:      
// Copyright:   (c) Aleksandras Gluchovas
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "matrixg.cpp"
    #pragma interface "matrixg.h"
#endif

// 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 "wx/wspp/matrixg.h"

#include "wx/hash.h"
#include "wx/gauge.h"
#include "wx/radiobut.h"

/***** Implementation for class wxMatrixLayoutGuard *****/

IMPLEMENT_CLASS( wxMatrixLayoutGuard, wxEvtHandler )

BEGIN_EVENT_TABLE( wxMatrixLayoutGuard, wxEvtHandler )

	EVT_SIZE( wxMatrixLayoutGuard::OnSize )

END_EVENT_TABLE()

wxMatrixLayoutGuard::wxMatrixLayoutGuard( wxWindow* window, bool fitBestSize )

	: mpWnd( window )
{
	window->PushEventHandler( this );

	if ( fitBestSize )

		FitBestSize();
}

wxMatrixLayoutGuard::~wxMatrixLayoutGuard()
{
	WX_CLEAR_ARRAY( mItems );
}

void wxMatrixLayoutGuard::FitBestSize()
{
	RecalcSizes();

	mpWnd->SetClientSize( mPrefSize.x, mPrefSize.y );
}

void wxMatrixLayoutGuard::OnSize( wxSizeEvent& event )
{
	RecalcSizes();
	event.Skip();
}

/*** protected methods ***/

void wxMatrixLayoutGuard::RecalcSizes()
{
	wxASSERT( mpWnd ); // DBG::

	CalcMin();

	ApplySizes();
}

wxSize wxMatrixLayoutGuard::CalcMin()
{
	mPrefSize.x = 0;
	mPrefSize.y = 0;
	DoRecalcSizes( mPrefSize );

	return mPrefSize;
}

void wxMatrixLayoutGuard::DoRecalcSizes( wxSize& minSize )
{
	CollectItems();

	int minX = 0, minY = 0;

	for( size_t i = 0; i != mItems.Count(); ++i )
	{
		MatrixItem& item = *mItems[i];

		item.mFinalBounds = item.mBounds;
	}

	// layoiut hoiz. and vert. positions/sizes independently

	DoSizeMatrix( FALSE, minX ); 
	DoSizeMatrix( TRUE,  minY ); 

	minSize.x = minX;
	minSize.y = minY;
}

void wxMatrixLayoutGuard::ApplySizes()
{
	int w = 0, h = 0;
	mpWnd->GetClientSize( &w, &h );

	double xr = (double)w/(double)mPrefSize.x;
	double yr = (double)h/(double)mPrefSize.y;

	for( size_t n = 0; n != mItems.Count(); ++n )
	{
		MatrixItem& item = *mItems[n];

		if ( item.mpWnd )
		{
			if ( xr != 1.0 )
			{			
				item.mFinalBounds.x     = (int)( (double)item.mFinalBounds.x * xr );
				item.mFinalBounds.width = (int)( (double)item.mFinalBounds.width * xr );
			}

			// FOR NOW:: proportional resizing vertically is disabled

			#if 1
				if ( yr != 1.0 )
				{
					item.mFinalBounds.y		 = (int)( (double)item.mFinalBounds.y * yr );
					item.mFinalBounds.height = (int)( (double)item.mFinalBounds.height * yr );
				}
			#endif

			item.mpWnd->SetSize( item.mFinalBounds.x,	  item.mFinalBounds.y,
								 item.mFinalBounds.width, item.mFinalBounds.height );
		}
	}
}

void wxMatrixLayoutGuard::CollectItems()
{
	// here we only collect sizes and positions of items
	// which were set initially (from the user-code or in resoruce file),
	// as well as their minimal-prefered sizes

	wxSize extent(0,0);

	if ( mItems.Count() > 0 ) return;

	wxNode* node = mpWnd->GetChildren().First();

	while( node )
	{
		wxWindow* pChild = (wxWindow*)node->GetData();

		int w = 0, h = 0, x = 0, y = 0;
		pChild->GetSize( &w, &h );
		pChild->GetPosition( &x, &y );

		MatrixItem* pItem = new MatrixItem();

		mItems.Add( pItem );

		pItem->mBounds.x = x;
		pItem->mBounds.y = y;
		pItem->mBounds.width = w;
		pItem->mBounds.height = h;
		pItem->mpWnd = pChild;

		pItem->mMinSize = pChild->GetBestSize();

		/** some AI widget-wise **/

		if ( pChild->IsKindOf( CLASSINFO(wxRadioButton) ) )
		{
			// DoGetBastSize() wxRadioButton is as good as no impl.

			int w = 0, h = 0, charWidth = 0;
			pItem->mpWnd->GetTextExtent( ((wxRadioButton*)pItem->mpWnd)->GetLabel(), &w, &h );
			pItem->mpWnd->GetTextExtent( "X", &charWidth, NULL );

			pItem->mMinSize.x = w + 10 + 2*charWidth;
			pItem->mMinSize.y = h + 5;
		}
		else
		if ( pChild->IsKindOf( CLASSINFO(wxChoice) ) )
		{
			// DoGetBastSize() wxRadioButton is as good as no impl.

			int w = 0, h = 0, charWidth = 0;
			pItem->mpWnd->GetTextExtent( "ZZ", &w, &h );
			pItem->mMinSize.x = w + 25;
			pItem->mMinSize.y = h + 5;
		}
		else
		if ( pChild->IsKindOf( CLASSINFO(wxStaticBox) ) )
		{
			// special AI, to regard label-height of static-box,
			// so that enclosed items do not slip under the box-label part

			MatrixItem* pSpacerItem = new MatrixItem();

			pSpacerItem->mBounds = pItem->mBounds;
			pSpacerItem->mBounds.width  = 1;
			pSpacerItem->mBounds.height = 10;

			pSpacerItem->mMinSize = wxSize( 1, pItem->mpWnd->GetCharHeight() );
			pSpacerItem->mpWnd = NULL; // mask as "fake item"

			mItems.Add( pSpacerItem );
		}
		else
		if ( pChild->IsKindOf( CLASSINFO(wxListBox) ) )
		{
			// detault get DoGetBestSize() of ListBox returns
			// height of at least 7-items, which is too high for a minimal size,
			// make it smaller...

			int charHeight = pChild->GetCharHeight();
			pItem->mMinSize.y = wxMin( charHeight * 3, ((wxListBox*)pChild)->Number() * charHeight );
		}
		else
		if ( pChild->IsKindOf( CLASSINFO(wxGauge) ) )
		
			// DoGetBastSize() of wxGauge is no good..

			pItem->mMinSize.y = pChild->GetCharHeight();
		

		pItem->mFinalBounds = pItem->mBounds;

		// additional ydo this because mpWnd->GetSize()
		// may have returned fake value (eg. if not loading from resource)

		extent.x = wxMax( extent.x, pItem->mBounds.x + pItem->mBounds.width  );
		extent.y = wxMax( extent.y, pItem->mBounds.y + pItem->mBounds.height );

		node = node->GetNext();
	}

	mExtraSize = wxSize(0,0);

	wxSize wndSize = mpWnd->GetClientSize();

	if ( wndSize.x > extent.x ) 
		mExtraSize.x = wndSize.x - extent.x;

	if ( wndSize.y > extent.y ) 
		mExtraSize.y = wndSize.y - extent.y;
}

static bool _gVertMode = TRUE;

int compare_matrix_items_horiz( MatrixItem** ppItem1, MatrixItem** ppItem2 )
{
	int end1 = (*ppItem1)->mFinalBounds.x + (*ppItem1)->mFinalBounds.width;
	int end2 = (*ppItem2)->mFinalBounds.x + (*ppItem2)->mFinalBounds.width;

	if ( end1 < end2 ) return -1;
	else
	if ( end1 == end2 ) return 0;
	else
		return 1;
};

int compare_matrix_items_vert( MatrixItem** ppItem1, MatrixItem** ppItem2 )
{
	int end1 = (*ppItem1)->mFinalBounds.y + (*ppItem1)->mFinalBounds.height;
	int end2 = (*ppItem2)->mFinalBounds.y + (*ppItem2)->mFinalBounds.height;

	if ( end1 < end2 ) return -1;
	else
	if ( end1 == end2 ) return 0;
	else
		return 1;
};

inline int& _size( wxRect& r ) { return _gVertMode ? r.height : r.width; }
inline int& _size( wxSize& s ) { return _gVertMode ? s.y      : s.x; }
inline int& _pos( wxRect& r )  { return _gVertMode ? r.y      : r.x; }

void wxMatrixLayoutGuard::DoSizeMatrix( bool vertical, int& minSize )
{
	/** the actual of Matrix(tm)-algorithm (no AIs here) **/

	_gVertMode = vertical;
	int rightMost = 0;

	// sort children by increasing position of their right-most/bottom-most edges

	mItems.Sort( vertical ? compare_matrix_items_vert : compare_matrix_items_horiz );

	// process items in left-to-right order (position-wise)

	size_t cnt = mItems.Count();
	for( size_t i = 0; i != cnt; ++i )
	{
		MatrixItem& curItem = *mItems[i];

		if ( _size(curItem.mFinalBounds) < _size(curItem.mMinSize) )
		{
			int delta = _size(curItem.mMinSize) - _size(curItem.mFinalBounds);

			int curRightEdgePos = _pos(curItem.mFinalBounds) + _size(curItem.mFinalBounds);

			// find the left-most item with the right-most-edge >= then that of the curItem
			// ( search starts form current to the left, because mItems is sorted)

			int n = i - 1;

			while( n >= 0 )
			{
				MatrixItem& item = *mItems[n];

				if ( curRightEdgePos > _pos(item.mFinalBounds) + _size(item.mFinalBounds) )

					break;

				--n;
			}

			++n;

			// shift all right edges (or position) of items which appeare to
			// the right of the current one

			for(; n < (int)cnt; ++n)
			{
				MatrixItem& item = *mItems[n];

				if ( _pos(item.mFinalBounds) >= curRightEdgePos )
				
					_pos(item.mFinalBounds) += delta;  // shift position
				else
					_size(item.mFinalBounds) += delta; // shit left-edge only if
													   // bounds intersects with the current
			}
		}

		rightMost = wxMax( rightMost, _pos(curItem.mFinalBounds) + _size(curItem.mFinalBounds) );
	}

	minSize = rightMost + _size(mExtraSize);
}