/////////////////////////////////////////////////////////////////////////////
// Name:        No names yet.
// Purpose:     Contrib. demo
// Author:      Aleksandras Gluchovas
// Modified by:
// Created:     2000/02/08
// RCS-ID:      $Id: ui_lookrenderer.cpp,v 1.12 2001/04/23 19:08:05 georgetasker Exp $
// Copyright:   (c) Aleksandras Gluchovas
// Licence:   	GPL (version 2.0 or later)
/////////////////////////////////////////////////////////////////////////////

#ifdef __GNUG__
    #pragma implementation "ui_lookrenderer.cpp"
    #pragma interface "ui_lookrenderer.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/dcmemory.h"

#include <stdio.h> // sscanf()
#include <memory.h>

#include "ui_lookrenderer.h"
#include "txtrecset.h"

/***** Implementation for class wxUILookInfo *****/

wxUILookInfo::wxUILookInfo()

	: mName( "noname" ),
	  mTextExpr( "\\\"something\\\"" ),
	  mTextXExpr( "0" ),
	  mTextYExpr( "0" ),
	  mWidthExpr( "1" ),
	  mHeightExpr( "1" ),
	  mStyleFilterExpr( "1" ),

	  mBgColor( *wxWHITE ),
	  mFgColor( *wxBLACK ),
	  mBoldText( FALSE ),
	  mOpaqueText( FALSE ),
	  mIsOpaque( TRUE ),
	  mpImage( NULL ),
	  mpTextExprNode( NULL ),
	  mpTextXExprNode(NULL),
	  mpTextYExprNode(NULL),
	  mpWidthExprNode(NULL),
	  mpHeightExprNode(NULL),
	  mpStyleFilterExprNode(NULL)
{}

wxUILookInfo::~wxUILookInfo()
{
	if ( mpImage )
	{
		mpImage->Destroy();
		delete mpImage;
	}

	CleanUp();
}

void wxUILookInfo::Prepare()
{
	CleanUp();

	mpTextExprNode			= ::ParseExpr( mTextExpr );
	mpTextXExprNode			= ::ParseExpr( mTextXExpr );
	mpTextYExprNode			= ::ParseExpr( mTextYExpr );
	mpWidthExprNode			= ::ParseExpr( mWidthExpr );
	mpHeightExprNode		= ::ParseExpr( mHeightExpr );
	mpStyleFilterExprNode	= ::ParseExpr( mStyleFilterExpr );
}

void wxUILookInfo::CleanUp()
{
	if ( mpTextExprNode ) delete mpTextExprNode;
	if ( mpTextXExprNode ) delete mpTextXExprNode;
	if ( mpTextYExprNode ) delete mpTextYExprNode;
	if ( mpWidthExprNode ) delete mpWidthExprNode;
	if ( mpHeightExprNode ) delete mpHeightExprNode;
	if ( mpStyleFilterExprNode ) delete mpStyleFilterExprNode;

	mpTextExprNode = NULL;
	mpTextXExprNode = NULL;
	mpTextYExprNode = NULL;
	mpWidthExprNode = NULL;
	mpHeightExprNode = NULL;
	mpStyleFilterExprNode = NULL;
}

/***** Implementation for class wxUILookRenderer *****/

wxUILookRenderer::wxUILookRenderer()

	: mHintHash( wxKEY_STRING )
{}

wxUILookRenderer::~wxUILookRenderer()
{
	CleanUp();
}

wxFont wxUILookRenderer::GetFontForItem( wxUILookInfo& info, wxDC& dc )
{
	if ( info.mBoldText )
	{
		wxFont boldFnt = dc.GetFont();
		boldFnt.SetWeight( wxBOLD );

		return boldFnt;
	}
	else
		return dc.GetFont();
}

bool wxUILookRenderer::RenderItemLook( wxUILookInfo& info, 
									   const wxSize& size, wxWindow* wnd,
									   wxBitmap& toBmp, wxDC& dc,
									   const wxFont& font, const wxColour& bg, const wxColour& fg,
									   int align,
									   EqSolver* pSolver )
{
	if ( pSolver == NULL )

		pSolver = &mSolver;

	wxImage toImage( size.x, size.y );

	wxImage& src = *info.mpImage;

	wxColour realBg = bg.Ok() ? bg : info.mBgColor;
	FillImage( toImage, realBg );

	if ( info.mpImage )
	{
		RenderEdge( src, toImage, info.mEdges[ LEFT_LOOK_EDGE ], 
					0,0, 0,0, TRUE );

		RenderEdge( src, toImage, info.mEdges[ RIGHT_LOOK_EDGE ], 
					src.GetWidth()     - info.mEdges[ RIGHT_LOOK_EDGE ].mWidth, 0, 
					toImage.GetWidth() - info.mEdges[ RIGHT_LOOK_EDGE ].mWidth, 0, 
					TRUE );

		RenderEdge( src, toImage, info.mEdges[ TOP_LOOK_EDGE ], 
					0,0, 0,0, FALSE );

		RenderEdge( src, toImage, info.mEdges[ BOTTOM_LOOK_EDGE ], 
					0, src.GetHeight()     - info.mEdges[ BOTTOM_LOOK_EDGE ].mWidth,
					0, toImage.GetHeight() - info.mEdges[ BOTTOM_LOOK_EDGE ].mWidth, 
					FALSE );
	}

	if ( info.mBgSusbstColor.Ok() )
	
		SubstituteColour( toImage, info.mBgSusbstColor, realBg );

	toBmp = toImage.ConvertToBitmap();

	wxMemoryDC memDc;
	memDc.SelectObject( toBmp );

	wxFont fnt;

	if (font.Ok())
		fnt = font;
	else
		fnt = wnd->GetFont();

	if ( info.mBoldText )
	{
		fnt = wnd->GetFont();
		fnt.SetWeight( wxBOLD );
		memDc.SetFont( fnt );
	}
	else
		memDc.SetFont( fnt );
 
	memDc.SetTextBackground( realBg );
	memDc.SetTextForeground( fg.Ok() ? fg : info.mFgColor );

	// just to obtain text-literal

	int w = 0, h = 0, x = 0, y = 0; wxString text;
	CalcItemParams( info, size, memDc, text, x,y, w,h, fnt, info.mFgColor, info.mBgColor, pSolver );

	if ( text.Length() )
	{
		memDc.SetBackgroundMode( info.mOpaqueText ? wxSOLID : wxTRANSPARENT );

		w = size.x; h = size.y;

		DrawMultilineText( text, memDc, x, y, w,h, FALSE, align );
	}

	// TBD:: comments for this "hack"

	wxString border = pSolver->GetStringValue( "border_style" );

	if ( border == "wxRAISED_BORDER" )
	{
		wxRect r( 0,0, size.x, size.y );

		memDc.SetPen( *wxWHITE_PEN );
		memDc.DrawLine( r.x+1, r.y+1, r.x + r.width-2, r.y+1  );
		memDc.DrawLine( r.x+1, r.y+1, r.x+1, r.y + r.height-2 );

		memDc.SetPen( *wxBLACK_PEN );
		memDc.DrawLine( r.x, r.y + r.height-1, r.x + r.width, r.y + r.height-1 );
		memDc.DrawLine( r.x + r.width-1, r.y, r.x + r.width-1, r.y + r.height-1  );

		memDc.SetPen( *wxGREY_PEN );
		memDc.DrawLine( r.x+1, r.y + r.height-2, r.x + r.width-1, r.y + r.height-2 );
		memDc.DrawLine( r.x + r.width-2, r.y+1, r.x + r.width-2, r.y + r.height-2 );
	}
	else
	if ( border == "wxSUNKEN_BORDER" )
	{
		wxRect r( 0,0, size.x, size.y );

		memDc.SetPen( *wxGREY_PEN );
		memDc.DrawLine( r.x, r.y, r.x + r.width-1, r.y  );
		memDc.DrawLine( r.x, r.y, r.x, r.y + r.height-1 );

		memDc.SetPen( *wxWHITE_PEN );
		memDc.DrawLine( r.x + r.width - 1, r.y, r.x + r.width - 1, r.y + r.height );
		memDc.DrawLine( r.x, r.y + r.height - 1, r.x + r.width, r.y + r.height - 1 );
	}
	else
	if ( border == "wxSIMPLE_BORDER" )
	{
		memDc.SetPen( *wxBLACK_PEN );

		memDc.DrawLine( 0,0, size.x, 0 );
		memDc.DrawLine( size.x - 1, 0, size.x - 1, size.y );
		memDc.DrawLine( size.x - 1, size.y - 1, 0, size.y - 1 );
		memDc.DrawLine( 0, size.y - 1, 0, 0 );
	}

	memDc.SelectObject( wxNullBitmap );

	return TRUE;
}

void wxUILookRenderer::DrawMarkedTextLine( const wxString& text, wxDC& dc, int x, int y, 
										   int &w, int& h, bool calcExtent,
										   wxFont* pUnderlinedFnt )
{
	if ( calcExtent )
	{
		wxCoord cw, ch;

		if ( text.Find( '&' ) != - 1 )
		{
			wxString s = text;
			s.Replace( "&", "" );
			dc.GetTextExtent( s, &cw, &ch );
		}
		else
			dc.GetTextExtent( text, &cw, &ch );

		w = (int)cw; h = (int)ch;

		return;
	}

	if ( text.Find( '&' ) == -1 )
	
		dc.DrawText( text, x,y );
	else
	{
		int len = (int)text.Length();
		int cur = 0, start = 0;

		if (!dc.GetFont().Ok())
		{
			int avoidCompilerWarning = 0;
			wxASSERT_MSG(avoidCompilerWarning, "dc's font is invalid");
		}

		wxFont normalFnt( dc.GetFont() );
		wxFont ulineFnt;

#ifdef __WINDOWS__
			normalFnt.RealizeResource();
#endif

		if ( pUnderlinedFnt )
			ulineFnt = *pUnderlinedFnt;
		else
		{
			ulineFnt = wxFont( normalFnt.GetPointSize(),
							   normalFnt.GetFamily(),
							   normalFnt.GetStyle(),
							   normalFnt.GetWeight(), TRUE,
							   normalFnt.GetFaceName(),
							   normalFnt.GetDefaultEncoding() );
		
#ifdef __WINDOWS__
			ulineFnt.RealizeResource();
#endif
		}

		while( cur < len )
		{
			while( cur < len && text[cur] != '&' )
				++cur;

			if ( start != cur )
			{
				wxString sub = text.Mid( start, cur - start );

				dc.DrawText( sub, x,y );

				wxCoord cw, ch;
				dc.GetTextExtent( sub, &cw, &ch );
				x += cw;

				start = cur;
			}

			if ( cur + 1 < len && text[cur] == '&' )
			{
				++cur; wxString sub( text[cur] );


				wxCoord cw, ch;
				
				dc.SetFont( ulineFnt );
				
				dc.GetTextExtent( sub, &cw, &ch );
				dc.DrawText( sub, x,y );

				dc.SetFont( normalFnt );

				x += cw;

				++cur;
				start = cur;
			}

			++cur;
		}
	}
}

void wxUILookRenderer::DrawMultilineText( const wxString& text, wxDC& dc, int x, int y, 
										  int &w, int& h, bool calcExtent, int align )
{
	if ( text.Find("\\n") == -1 )
	{
		DrawMarkedTextLine( text, dc, x,y, w,h, calcExtent );
		return;
	}

	int maxW = 0, maxH = 0;

	int len = (int)text.Length();
	int cur = 0, start = 0;
	wxCoord cw, ch;

	int breaks[100];
	int nBreaks = 0;

	while( cur < len )
	{
		while( cur < len && !(text[cur] == '\\' && text[cur+1] == 'n') )
			++cur;

		breaks[nBreaks] = cur;
		++nBreaks;

		dc.GetTextExtent( text.Mid( start, cur - start ), &cw, &ch );

		maxW = wxMax( maxW, cw );
		maxH += ch;

		cur += 2; // skip "\\n";
		start = cur;
	}

	if ( calcExtent )
	{
		w = maxW;
		h = maxH;
		return;
	}

	start = 0; int n = 0;
	int x0 = x, y0 = y;

	while( n < nBreaks )
	{
		int cur = breaks[n];

		wxString sub = text.Mid( start, cur - start );

		dc.GetTextExtent( sub, &cw, &ch );

		x = x0;

		if ( align == wxALIGN_RIGHT )
		
			x = x0 + w - cw;
		else
		if ( align == wxALIGN_CENTER )

			x = x0 + ( w - cw ) / 2;

		wxFont underlinedFnt( dc.GetFont() );
		underlinedFnt.SetUnderlined( TRUE );

		DrawMarkedTextLine( sub, dc, x,y, w, h, FALSE, &underlinedFnt );

		start = breaks[n] + 2;
		y += ch; ++n;
	}
}

wxString wxUILookRenderer::ReduceItemText( wxUILookInfo& info, EqSolver* pSolver )
{
	wxString text = info.mAltText;
	
	if ( info.mpTextExprNode )
	{
		text = info.mpTextExprNode->EvaluateString( *pSolver );

		if ( text == "[[ERROR]]" )

			text = info.mAltText;
	}

	return text;
}

bool wxUILookRenderer::CalcItemParams( wxUILookInfo& info, const wxSize& size, 
									   wxDC& dc, wxString& text,
									   int& textX, int& textY,
									   int& itemWidth, int& itemHeight,
									   const wxFont& font, const wxColour& bg, const wxColour& fg,
									   EqSolver* pSolver )
{
	text = ReduceItemText( info, pSolver );

	wxFont original = dc.GetFont();

	dc.SetFont( font );
	
	int w = 0, h = 0;
	DrawMultilineText( text, dc, 0,0, w,h, TRUE );

	dc.SetFont( original );

	pSolver->SetDoubleVar( "TEXT_WIDTH",  (double)w );
	pSolver->SetDoubleVar( "TEXT_HEIGHT", (double)h );
	pSolver->SetDoubleVar( "WIDTH",  (double)size.x );
	pSolver->SetDoubleVar( "HEIGHT", (double)size.y );

	SetEdgeVars( info.mEdges[ LEFT_LOOK_EDGE   ], "LEFT_EDGE", pSolver );
	SetEdgeVars( info.mEdges[ RIGHT_LOOK_EDGE  ], "RIGHT_EDGE", pSolver );
	SetEdgeVars( info.mEdges[ TOP_LOOK_EDGE    ], "TOP_EDGE", pSolver );
	SetEdgeVars( info.mEdges[ BOTTOM_LOOK_EDGE ], "BOTTOM_EDGE", pSolver );

	textX      = info.mpTextXExprNode->EvaluateInt( *pSolver );
	textY      = info.mpTextYExprNode->EvaluateInt( *pSolver );
	itemWidth  = info.mpWidthExprNode->EvaluateInt( *pSolver );
	itemHeight = info.mpHeightExprNode->EvaluateInt( *pSolver );

	return TRUE;
}

void wxUILookRenderer::SetEdgeVars( EdgeLookInfo& edge, const wxString& edgeName, EqSolver* pSolver )
{
	pSolver->SetIntVar( edgeName + "_WIDTH",		   edge.mWidth );
	pSolver->SetIntVar( edgeName + "_LEFT_MARGIN",  edge.mLeftMargin );
	pSolver->SetIntVar( edgeName + "_RIGHT_MARGIN", edge.mRightMargin );
	pSolver->SetIntVar( edgeName + "_INSET",		   edge.mInset );
}

EqSolver& wxUILookRenderer::GetEqSolver()
{
	return mSolver;
}

UILookInfoListT& wxUILookRenderer::GetInfoItems()
{
	return mItems;
}

wxUILookInfo* wxUILookRenderer::FindLookByClassHint( const wxString& hint )
{
	return (wxUILookInfo*)mHintHash.Get( hint );
}

void wxUILookRenderer::StoreToRecord( wxUILookInfo& info, Record& rec )
{
	rec.RemoveAllFields();

	rec.AddStringField( "Name", info.mName );
	rec.AddStringField( "TextExpr", info.mTextExpr );
	rec.AddStringField( "TextXExpr", info.mTextXExpr );
	rec.AddStringField( "TextYExpr", info.mTextYExpr );
	rec.AddStringField( "WidthExpr", info.mWidthExpr );
	rec.AddStringField( "HeightExpr", info.mHeightExpr );
	rec.AddStringField( "StyleFilterExpr", info.mStyleFilterExpr );
	rec.AddStringField( "AltText", ColourToStr( info.mAltText ) );
	rec.AddStringField( "BgColor", ColourToStr( info.mBgColor ) );
	rec.AddStringField( "FgColor", ColourToStr( info.mFgColor ) );
	rec.AddStringField( "BgSubstColor", ColourToStr( info.mBgSusbstColor ) );
	rec.AddStringField( "BoldText", info.mBoldText ? "true" : "false" );
	rec.AddStringField( "OpaqueBg",   info.mIsOpaque ? "true" : "false" );

	StoreEdgeInfo( rec, "LeftEdge",		info.mEdges[ LEFT_LOOK_EDGE ] );
	StoreEdgeInfo( rec, "RightEdge",	info.mEdges[ RIGHT_LOOK_EDGE ] );
	StoreEdgeInfo( rec, "TopEdge",		info.mEdges[ TOP_LOOK_EDGE ] );
	StoreEdgeInfo( rec, "BottomEdge",	info.mEdges[ BOTTOM_LOOK_EDGE ] );

	if ( info.mpImage )
	{
		Record* pImgRec = ImageToRecord( *info.mpImage );

		pImgRec->SetName( "image" );

		rec.AddField( pImgRec );
	}
}

void wxUILookRenderer::DrawLook( wxDC& toDc, wxUILookInfo& look, wxBitmap& bmp, int x, int y )
{
	if ( look.mIsOpaque )

		toDc.DrawBitmap( bmp, x,y );
	else
	{
		wxMemoryDC memDc;
		memDc.SelectObject( bmp );
		int w = bmp.GetWidth(), h = bmp.GetHeight();

		// draw top-edge
		int tWidth = look.mEdges[ TOP_LOOK_EDGE ].mWidth;
		toDc.Blit( x,y, w, tWidth, &memDc, 0,0 );

		// draw bottom-edge
		int bWidth = look.mEdges[ BOTTOM_LOOK_EDGE ].mWidth;
		toDc.Blit( x, y + h - bWidth, w, bWidth, &memDc, 0,h - bWidth );

		// draw left-edge
		int lWidth = look.mEdges[ LEFT_LOOK_EDGE ].mWidth;
		toDc.Blit( x, y, lWidth, h, &memDc, 0,0 );

		// right left-edge
		int rWidth = look.mEdges[ RIGHT_LOOK_EDGE ].mWidth;
		toDc.Blit( x + w - rWidth, y, rWidth, h, &memDc, w - rWidth,0 );

		memDc.SelectObject( wxNullBitmap );
	}
}

bool wxUILookRenderer::ParseScriptFromFile( const wxString& fname )
{
	// load 

	wxString script = LoadFileContent( fname );

	// parse

	return ParseScript( script );
}

bool wxUILookRenderer::ParseScript( const wxString& script )
{
	CleanUp();

	// extract look-infos

	InMemoryRecordSet rset;
	
	rset.Open( script );

	rset.MoveFirst();

	while( !rset.IsEof() )
	{
		Record& r = rset.GetRecord();

		wxString t = r.GetTextContent();

		wxUILookInfo* pInfo = new wxUILookInfo();

		pInfo->mName	   = GetUnescapedString( r[ "Name" ] );
		pInfo->mTextExpr   = GetUnescapedString( r[ "TextExpr" ] );
		pInfo->mTextXExpr  = r[ "TextXExpr" ];
		pInfo->mTextYExpr  = r[ "TextYExpr" ];
		pInfo->mWidthExpr  = r[ "WidthExpr" ];
		pInfo->mHeightExpr = r[ "HeightExpr" ];
		pInfo->mAltText    = r[ "AltText" ];

		if ( r.FindField( "ClassHint" ) )
		{
			pInfo->mClassHint = r[ "ClassHint" ];

			mHintHash.Put( pInfo->mClassHint, (wxObject*)pInfo );
		}

		if ( r.FindField( "StyleFilterExpr" ) )

			pInfo->mStyleFilterExpr = GetUnescapedString( r[ "StyleFilterExpr" ] );
		else
			pInfo->mStyleFilterExpr = "0";

		pInfo->mBgColor = StrToColour( r[ "BgColor" ] );
		pInfo->mFgColor = StrToColour( r[ "FgColor" ] );
		pInfo->mBgSusbstColor = StrToColour( r[ "BgSubstColor" ] );

		if ( r.FindField( "BoldText" ) &&
			 r[ "BoldText" ] == "true" )

			pInfo->mBoldText = TRUE;

		if ( r.FindField( "OpaqueText" ) &&
			 r[ "OpaqueText" ] == "true" )

			pInfo->mOpaqueText = TRUE;

		if ( r.FindField( "OpaqueBg" ) &&
			 r[ "OpaqueBg" ] == "false" )

			pInfo->mIsOpaque = FALSE;

		ExtractEdgeInfo( r, "TopEdge",		pInfo->mEdges[ TOP_LOOK_EDGE ] );
		ExtractEdgeInfo( r, "BottomEdge",	pInfo->mEdges[ BOTTOM_LOOK_EDGE ] );
		ExtractEdgeInfo( r, "LeftEdge",	pInfo->mEdges[ LEFT_LOOK_EDGE ] );
		ExtractEdgeInfo( r, "RightEdge", pInfo->mEdges[ RIGHT_LOOK_EDGE ] );

		if ( r.FindField( "image" ) == NULL )

			pInfo->mpImage = NULL;
		else
			pInfo->mpImage = RecordToImage( &r.GetRecordByName( "image" ) );

		pInfo->Prepare();

		mItems.Add( pInfo );

		rset.MoveNext();
	}

	return TRUE;
}

/*** protected methods ***/

void wxUILookRenderer::ExtractEdgeInfo( Record& rec, const wxString& edgeName, 
									    EdgeLookInfo& edge )
{
	Record& edgeRec = rec.GetRecordByName( edgeName );

	edge.mWidth		  = edgeRec.GetIntValueByName( "Width" );
	edge.mLeftMargin  = edgeRec.GetIntValueByName( "LeftMargin" );
	edge.mRightMargin = edgeRec.GetIntValueByName( "RightMargin" );
	edge.mInset		  = edgeRec.GetIntValueByName( "Inset" );
}

void wxUILookRenderer::StoreEdgeInfo( Record& rec, const wxString& edgeName, 
									  EdgeLookInfo& edge )
{
	Record* pEdgeRec = new Record();
	pEdgeRec->SetName( edgeName );

	pEdgeRec->AddIntField( "Width",		  edge.mWidth );
	pEdgeRec->AddIntField( "LeftMargin",  edge.mLeftMargin );
	pEdgeRec->AddIntField( "RightMargin", edge.mRightMargin );
	pEdgeRec->AddIntField( "Inset",		  edge.mInset );
	
	rec.AddField( pEdgeRec );
}

void wxUILookRenderer::CleanUp()
{
	mHintHash.Clear();

	WX_CLEAR_ARRAY( mItems );
}

bool wxUILookRenderer::StoreLookInfos( const wxString& fname )
{
	wxFAIL_MSG( "Not implemented" ); // TBD::

	return TRUE;
}

wxColour wxUILookRenderer::StrToColour( const wxString& rgbStr )
{
	int rgb = 0;

	if ( rgbStr.Length() && rgbStr[0] == 'S' )
	{
		if ( rgbStr == "SYS_WND_BG" )
			return wxSystemSettings::GetSystemColour( wxSYS_COLOUR_3DFACE );
		else
		if ( rgbStr == "SYS_WND_FG" )
			return wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOWTEXT );
		else
		if ( rgbStr == "SYS_WND_ENTRY_BG" )
			return wxSystemSettings::GetSystemColour( wxSYS_COLOUR_WINDOW );
		else
			return wxColour();
	}

	if ( rgbStr.Length() == 0 )

		return wxColour();

	sscanf( (const char*)rgbStr, "%X", &rgb );

	int b = rgb & 0xFF;
	int g = ( rgb >> 8 )  & 0xFF;
	int r = ( rgb >> 16 ) & 0xFF;

	return wxColour( r,g,b );
}

wxString wxUILookRenderer::ColourToStr( const wxColour& col )
{
	wxString result;

	int rgb = col.Blue() + col.Green() * 256 + col.Red() *  65536;

	result.Printf( "%.6X", rgb );

	return result;
}

// some helpers

#define IS_SPACE( x ) ( x == ' ' || x == '\t' || x == 10 || x == 13 )
#define IS_EOL( x ) ( x == 10 || x == 13 )
#define HEX_TO_INT( c ) (( c > '9' ) ? c - 'A' + 10 : c - '0')

static inline int MyGetHex( char* at )
{
	return HEX_TO_INT( *at ) * 16 + HEX_TO_INT( *(at+1) );
}

static void MySetHex( char* buf, unsigned char val )
{
	int hi = ( val >> 4 ) & 0xF, lo = ( val & 0xF );

	buf[0] = hi >= 10 ? ( 'A' + hi - 10 ) : ( '0' + hi );
	buf[1] = lo >= 10 ? ( 'A' + lo - 10 ) : ( '0' + lo );
}

wxString wxUILookRenderer::ImageToStr( wxImage& img )
{
	if ( !img.Ok() ) return "";

	int rowLen = img.GetWidth(), nRows = img.GetHeight();

	wxString txt;

	char buf[] = "RRGGBB";

	unsigned char* data = img.GetData();

	for( int y = 0; y != nRows; ++y )
	{
		for( int x = 0; x != rowLen; ++x )
		{
			MySetHex( buf+0, data[0] );
			MySetHex( buf+2, data[1] );
			MySetHex( buf+4, data[2] );

			data += 3;

			txt += buf;
		}

		txt += '\n';
	}

	return txt;
}

wxBitmap wxUILookRenderer::RecordToBitmap( Record* pRecord )
{
	wxImage* pImage = RecordToImage( pRecord );

	if ( !pImage ) return wxBitmap();

	wxBitmap bmp;
	
	bool ok = bmp.Ok();

	bmp = pImage->ConvertToBitmap();

	ok = bmp.Ok();

	delete pImage;

	ok = bmp.Ok();

	return bmp;
}

wxImage* wxUILookRenderer::StrToImage( const wxString& imgStr )
{
	char* cur = (char*)(const char*)imgStr;
	char* end = cur + imgStr.Length();

	// first measure # of pixels in one row

	while( *cur && IS_SPACE( *cur ) ) ++cur;

	char* start = cur;

	while( *cur && !IS_SPACE( *cur ) ) ++cur;

	int rowLen = int(cur-start) / 6; // six characters per pixel

	// calc # of rows

	int nRows = 0;

	while( *cur )
	{
		if ( IS_EOL( *cur ) )
		{
			if ( IS_EOL( *(cur+1) ) )
				++cur;

			++nRows;
		}

		++cur;
	}

	if ( !IS_EOL( *(cur-1) ) )

		++nRows;

	// parse image data (RGB tripplets)

	unsigned char* data = (unsigned char*)malloc( rowLen*3*nRows );
	unsigned char* curData = data;

	cur = start;

	while( *cur )
	{
		while( *cur && !IS_SPACE( *cur ) )
		{
			int r = MyGetHex( cur+0 );
			int g = MyGetHex( cur+2 );
			int b = MyGetHex( cur+4 );

			curData[0] = (unsigned char)r;
			curData[1] = (unsigned char)g;
			curData[2] = (unsigned char)b;

			curData += 3;
			cur += 6;
		}

		while( *cur && IS_SPACE( *cur ) ) 
			++cur;
	}
	
	wxImage* pImg = new wxImage( rowLen, nRows );
	pImg->SetData( data );

	return pImg;
}

Record* wxUILookRenderer::ImageToRecord( wxImage& img )
{
	if ( !img.Ok() ) return NULL;

	wxHashTable colorHash;
	wxArrayPtrVoid colors;

	int rowLen = img.GetWidth(), nRows = img.GetHeight();

	unsigned char* data = img.GetData();

	// construct hash-table of dictinct colors present in the image

	int x = 0, y = 0;
	int idx = 0; int blackIdx = 0;

	for( y = 0; y != nRows; ++y )
	{
		for( x = 0; x != rowLen; ++x )
		{
			int rgb = MakeRGBInt( data[0], data[1], data[2] );

			if ( rgb == 0 ) 
				
				rgb = 1; // very slight misplacement of pallete for absolute back

			if ( colorHash.Get( (long)rgb ) == NULL )
			{
				colors.Add( (void*)rgb );

				if ( rgb == 1 ) 
					
					blackIdx = idx;

				colorHash.Put( (long)rgb, (wxObject*)(idx + 1) );

				++idx;
			}

			data += 3;
		}
	}

	Record* pImgRec = new Record();
	pImgRec->SetName( "image" );

	wxString colorsTxt = "";

	for( size_t i = 0; i != colors.Count(); ++i )
	{
		int rgb = (int)colors[i];

		if ( i != 0 ) colorsTxt += ' ';

		colorsTxt += RGBIntToStr(rgb);
	}

	// construct pixels field

	wxString pixelsTxt = "\n";

	data = img.GetData();

	for( y = 0; y != nRows; ++y )
	{
		for( x = 0; x != rowLen; ++x )
		{
			int rgb = MakeRGBInt( data[0], data[1], data[2] );

			idx = (int)colorHash.Get( (long)rgb ) - 1;

			if ( idx == -1 ) 
				
				idx = blackIdx;

			char ch = '0' + idx; // color aliasies start from '0' char

			pixelsTxt += ch;

			data += 3;
		}

		pixelsTxt += '\n';
	}

	pImgRec->AddStringField( "colors", colorsTxt );
	pImgRec->AddStringField( "pixels", pixelsTxt );

	return pImgRec;
}

wxImage* wxUILookRenderer::RecordToImage( Record* pRecord )
{
	wxHashTable colorHash;
	wxArrayPtrVoid colors;
	wxArrayPtrVoid pixels;

	char buf[] = "RRGGBB";

	wxString colTxt = (*pRecord)[ "colors" ];

	const char* cur = colTxt.c_str();

	for(;;)
	{
		while( *cur && *cur == ' ' ) ++cur;

		if ( !*cur ) break;

		int pos = 0;

		while( *cur && *cur != ' ' )
		{
			buf[pos++] = *cur;
			++cur;
		}

		int rgb = StrToRGbInt( buf );

		colors.Add( (void*)rgb );
	}

	wxString pixTxt = (*pRecord)[ "pixels" ];

	cur = pixTxt.c_str();
	int nRows = 0; int nCols = 0;

	for(;;)
	{
		while( *cur && (*cur == ' ' || *cur == '\r' || *cur == '\n' || *cur == '\t' ) ) 
			
			++cur;

		if ( !*cur ) break;

		nCols = 0;

		while( *cur && !(*cur == ' ' || *cur == '\r' || *cur == '\n' || *cur == '\t') )
		{
			int idx = *cur - '0';

			pixels.Add( colors[idx] );

			++nCols;
			++cur;
		}

		++nRows;
	}

	unsigned char* data = (unsigned char*)malloc( pixels.GetCount()*3 );
	unsigned char* curData = data;

	for( size_t i = 0; i != pixels.Count(); ++i )
	{
		int rgb = (int)pixels[i];
		int r = ( rgb >> 16 ) & 0xFF;
		int g = ( rgb >>  8 ) & 0xFF;
		int b = rgb & 0xFF;

		curData[0] = (unsigned char)r;
		curData[1] = (unsigned char)g;
		curData[2] = (unsigned char)b;

		curData += 3;
	}

	wxImage* pImg = new wxImage( nCols, nRows );
	pImg->SetData( data );

	return pImg;
}

void wxUILookRenderer::CopyRegion( wxImage& src, wxImage& dest, 
								   int destX, int destY, int destWidth, int destHeight,
								   int srcX, int srcY )
{
	if ( destX < 0 || destY < 0 || destWidth <= 0 || destHeight <= 0 ) 

		return;

	if ( srcX < 0 || srcY < 0 )

		return;

	if ( !src.Ok() || !dest.Ok() ) return;

	if ( destX + destWidth > dest.GetWidth() )

		destWidth = dest.GetWidth() - destX;

	if ( destY + destHeight > dest.GetHeight() )

		destHeight = dest.GetHeight() - destY;

	if ( destWidth <= 0 || destHeight <= 0 )

		return;

	unsigned char* srcData  = src.GetData()  + ( srcX*3  + src.GetWidth() *srcY *3 );
	unsigned char* destData = dest.GetData() + ( destX*3 + dest.GetWidth()*destY*3 );

	int scanLen  = destWidth*3;
	int srcScan  = src.GetWidth()*3;
	int destScan = dest.GetWidth()*3;

	for( int i = 0; i != destHeight; ++i )
	{
		memcpy( destData, srcData, scanLen );

		srcData  += srcScan;
		destData += destScan;
	}
}

void wxUILookRenderer::TilePattern( wxImage& src, wxImage& dest, 
								    int destX, int destY, int destWidth, int destHeight,
								    int srcX, int srcY, int srcWidth, int srcHeight )
{
	if ( destX < 0 || destY < 0 || destWidth <= 0 || destHeight <= 0 ) 

		return;
		
	if ( srcX < 0 || srcY < 0 || srcWidth <= 0 || srcHeight <= 0 )

		return;

	if ( !src.Ok() || !dest.Ok() ) return;

	int y = destY;

	int x1 = destX + destWidth;
	int y1 = destY + destHeight;

	while( y < destY + destHeight )
	{
		int x = destX;

		while( x < destX + destWidth )
		{
			int w = srcWidth, h = srcHeight;

			if ( x + w > x1 ) w = x1 - x;
			if ( y + h > y1 ) h = y1 - y;

			if ( !( w <= 0 || h <= 0 ) )

				CopyRegion( src, dest, x,y, w,h, srcX, srcY );

			x += srcWidth;
		}

		y += srcHeight;
	}
}

void wxUILookRenderer::FillImage( wxImage& img, const wxColour& col )
{
	if ( !img.Ok() ) return;

	int cnt = img.GetWidth()*img.GetHeight();

	unsigned char* data = img.GetData();

	unsigned char r = col.Red();
	unsigned char g = col.Green();
	unsigned char b = col.Blue();

	while( cnt-- )
	{
		data[0] = r;
		data[1] = g;
		data[2] = b;

		data += 3;
	}
}

void wxUILookRenderer::RenderEdge( wxImage& src, wxImage& dest, EdgeLookInfo& edge, 
								   int srcX, int srcY, int destX, int destY,
								   bool isVertical )
{
	if ( !src.Ok() || !dest.Ok() ) return;

	if ( isVertical )
	{
		CopyRegion( src, dest, 
					destX, destY, 
					edge.mWidth, edge.mLeftMargin, 
					srcX, srcY );

        CopyRegion( src, dest, 
					destX, destY + dest.GetHeight() - edge.mRightMargin, 
					edge.mWidth, edge.mRightMargin, 
					srcX, srcY + src.GetHeight() - edge.mRightMargin );

		TilePattern( src, dest,
					 destX, destY + edge.mLeftMargin, 
					 edge.mWidth, dest.GetHeight() - edge.mLeftMargin - edge.mRightMargin, 
					 srcX, srcY + edge.mLeftMargin, 
					 edge.mWidth, src.GetHeight() - edge.mLeftMargin - edge.mRightMargin );
	}
	else
	{
		CopyRegion( src, dest, 
					destX, destY, 
					edge.mLeftMargin, edge.mWidth, 
					srcX, srcY );

        CopyRegion( src, dest, 
					destX + dest.GetWidth() - edge.mRightMargin, destY, 
					edge.mRightMargin, edge.mWidth, 
					srcX + src.GetWidth() - edge.mRightMargin, srcY );

		TilePattern( src, dest,
					 destX + edge.mLeftMargin, destY,
					 dest.GetWidth() - edge.mLeftMargin - edge.mRightMargin, edge.mWidth,
					 srcX + edge.mLeftMargin, srcY,
					 src.GetWidth() - edge.mLeftMargin - edge.mRightMargin, edge.mWidth );
	}
}

void wxUILookRenderer::SubstituteColour( wxImage& img, 
										 const wxColour& match, const wxColour& subst )
{
	long m = match.GetPixel();
	long s = subst.GetPixel();

	unsigned char* sm = (unsigned char*)&m;
	unsigned char* ss = (unsigned char*)&s;

	unsigned char m1=sm[0];
	unsigned char m2=sm[1];
	unsigned char m3=sm[2];

	unsigned char s1=ss[0];
	unsigned char s2=ss[1];
	unsigned char s3=ss[2];

	int len = img.GetWidth() * img.GetHeight() * 3;

	unsigned char* data = (unsigned char*)img.GetData();

	for( int i = 0; i != len; i += 3 )
	{
		if ( data[i]   == m1 &&
			 data[i+1] == m2 &&
			 data[i+2] == m3 )
		{
			data[i]   = s1;
			data[i+1] = s2;
			data[i+2] = s3;
		}
	}
}
