#include "HexWindow.h"
#include "Events.h"
#include <stdio.h>
#include <string.h>

extern globals g;
extern prefs p;

/*** CREATOR ***/
HexWindow::HexWindow( WindowRef newWindow )
{
	// set contents to zero
	memset( this, 0, sizeof(HexWindow) );
	
	// initalise the bits that need initalising
	window = newWindow;
#if USE_NIBS
	// load stuff from nib file
#elif TARGET_API_MAC_CARBON
	// create root control
	ControlRef root;
	CreateRootControl( window, &root );
	
	// create header
	header = null;
	left = null;
	right = null;
	
	// create scroll bar
	Rect windowBounds;
	ControlActionUPP liveTrackingProc = NewControlActionUPP( TrackScrollbar );
	GetWindowPortBounds( window, &windowBounds );
	windowBounds.top	+= kHeaderHeight -1;
	windowBounds.bottom	-= kScrollBarWidth -2;
	windowBounds.left	= windowBounds.right - kScrollBarWidth +1;
	windowBounds.right	+= 1;
	CreateScrollBarControl( window, &windowBounds, 0, 0, 0, 0, true, liveTrackingProc, &scrollbar );
	
	ControlID id = { kScrollbarSignature, 0 };
	SetControlID( scrollbar, &id );
#else
	// only create a scroll bar
	scrollbar = GetNewControl( kSystem7ScrollBarControl, window );
#endif
}

/*** DESTRUCTOR ***/
HexWindow::~HexWindow( void )
{
//	Host_ReleaseResourceData( hexInfo->data );	// decreases refCount
	RemoveEventLoopTimer( timer );
	DisposeControl( scrollbar );
}

  /**********/
 /* EVENTS */
/**********/

/*** BOUNDS CHANGING ***/
OSStatus HexWindow::BoundsChanging( EventRef event )
{
	OSStatus error = noErr;
#if TARGET_API_MAC_CARBON
	// check that window is not just being dragged
	UInt32 attributes;
	error = GetEventParameter( event, kEventParamAttributes, typeUInt32, null, sizeof(UInt32), null, &attributes );
	if( error || attributes & kWindowBoundsChangeUserDrag ) return eventNotHandledErr;
	
	// get new bounds
	Rect windowBounds;
	error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds );
	if( error ) return eventNotHandledErr;
	
	// constrain window width
	UInt8 modulo = (windowBounds.right - windowBounds.left - 13*kHexCharWidth - kScrollBarWidth) % (4*kDataBlockWidth);
	if( modulo < (2*kDataBlockWidth) )	windowBounds.right -= modulo;
	else								windowBounds.right += (4*kDataBlockWidth) - modulo;
	
	// constrain window height
	modulo = (windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) % kHexLineHeight;
	if( modulo < (kHexLineHeight/2) )	windowBounds.bottom -= modulo;
	else								windowBounds.bottom += kHexLineHeight - modulo;
	
	// update window rect to constrained version
	if( (windowBounds.bottom - windowBounds.top) < kMinimumWindowHeight )	windowBounds.bottom = windowBounds.top + kMinimumWindowHeight;
	if( (windowBounds.right - windowBounds.left) < kMinimumWindowWidth )	windowBounds.right = windowBounds.left + kMinimumWindowWidth;
	error = SetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, sizeof(Rect), &windowBounds );
	if( error ) error = eventNotHandledErr;
	if( g.systemVersion < kMacOSX ) return noErr;
	error = BoundsChanged( event );
#endif
	return error;
}

/*** BOUNDS CHANGED ***/
OSStatus HexWindow::BoundsChanged( EventRef event )
{
	Rect windowBounds;
	OSStatus error = GetEventParameter( event, kEventParamCurrentBounds, typeQDRectangle, null, sizeof(Rect), null, &windowBounds );
	if( error ) return eventNotHandledErr;
	
#if TARGET_API_MAC_CARBON
	// resize header
	SizeControl( header, (windowBounds.right - windowBounds.left) +2, kHeaderHeight +1 );
	SizeControl( left, (windowBounds.right - windowBounds.left) /2 -4, kHeaderHeight -4 );
	SizeControl( right, (windowBounds.right - windowBounds.left) /2 -4, kHeaderHeight -4 );
	MoveControl( right, (windowBounds.right - windowBounds.left) /2, 2 );
#endif
	
	// resize scrollbar
	MoveControl( scrollbar, (windowBounds.right - windowBounds.left) - kScrollBarWidth +1, kHeaderHeight -1 );
	SizeControl( scrollbar, kScrollBarWidth, (windowBounds.bottom - windowBounds.top) - kHeaderHeight - kScrollBarWidth +3 );
	
	// calculate new scrollbar values & redraw window
	UpdateHexInfo();
	InvalidateWindowRect( window, &windowBounds );
	
	EventRef updateEvent;
	error = CreateEvent( null, kEventClassWindow, kEventWindowUpdate, GetCurrentEventTime(), 0, &updateEvent );
	if( error == noErr )
	{
		SetEventParameter( updateEvent, kEventParamDirectObject, typeWindowRef, sizeof(WindowRef), &window );
		SendEventToWindow( updateEvent, window );
		ReleaseEvent( updateEvent );
	}
	return noErr;
}

/*** CONTENT CLICK ***/
OSStatus HexWindow::ContentClick( EventRef event )
{
	#pragma unused( event )
	return eventNotHandledErr;
}

/*** DRAW CONTENT ***/
OSStatus HexWindow::DrawContent( EventRef event )
{
	#pragma unused( event )	// could be null if I called this directly, therefore I ignore the param
	
	// set window
	GrafPtr oldPort;
	GetPort( &oldPort );
	SetPortWindowPort( window );
	
	// initalise rects
	RGBColour colour;
	Rect windowBounds, drawingBounds;
	GetWindowPortBounds( window, &windowBounds );
	SetRect( &drawingBounds, windowBounds.left,  windowBounds.top + kHeaderHeight, windowBounds.right - kScrollBarWidth +1, windowBounds.bottom );
	SetRect( &hexRect, kHexCharWidth *11 -3, kTextMargin + kHeaderHeight, kHexCharWidth *58 +3, windowBounds.bottom - kTextMargin );
	SetRect( &asciiRect, kHexCharWidth *60 -3, kTextMargin + kHeaderHeight, kHexCharWidth *76 +3, windowBounds.bottom - kTextMargin );
	
#if Use_GWorlds
	CGrafPtr		origPort;
	GDHandle		origDev;
	GWorldPtr		theGWorld;
	PixMapHandle	thePixMap;
	
	// create GWorld
	GetGWorld( &origPort, &origDev );
		//	( storage, bit depth, rect, colour table, GDevice, option flags )		// bit depth 0 = screen depth
	NewGWorld( &theGWorld, p.GWorldDepth, &drawingBounds, null, null, 0 );
	if( !theGWorld )
	{
		Host_DebugError( "\pNewGWorld() failed", 0 );
		return eventNotHandledErr;
	}
	SetGWorld( theGWorld, null );
	thePixMap = GetGWorldPixMap( theGWorld );
	
	// lock the pixelmap in place
	Boolean pixelsLocked = LockPixels( thePixMap );
	if( !pixelsLocked )
	{
		UpdateGWorld( &theGWorld, 0, &drawingBounds, null, null, 0 );
		pixelsLocked = LockPixels( thePixMap );
		if( !pixelsLocked )		// if I still can't lock the damn pixels, give up! (don't even attempt writing to a moving target :)
		{
			SetGWorld( origPort, origDev );
			DisposeGWorld( theGWorld );
			Host_DebugError( "\pUpdateGWorld() failed", 0 );
			return eventNotHandledErr;
		}
	}
#endif
	
	// set font
	short font;
	GetFNum( "\pCourier", &font );
	TextFont( font );
	TextFace( 0 );
	TextSize( 12 );
	PenNormal();
	
	// clear the background
	RGBBackColor( &g.bgColour );
	EraseRect( &drawingBounds );
	
	// set text colour
	if( activeWindow )
			colour = g.black;
	else	colour = g.textColour;
	RGBForeColor( &colour );
	
	// draw box around hex
	RGBBackColor( &g.white );
	EraseRect( &hexRect );
	FrameRect( &hexRect );
	if( activeWindow && p.GWorldDepth > 1 )
	{
		RGBForeColor( &g.white );
		MoveTo( hexRect.left,		hexRect.bottom );
		LineTo( hexRect.right,		hexRect.bottom );
		LineTo( hexRect.right,		hexRect.top );
		RGBForeColor( &g.bevelColour );
		MoveTo( hexRect.right,		hexRect.top -1 );
		LineTo( hexRect.left -1,	hexRect.top -1 );
		LineTo( hexRect.left -1,	hexRect.bottom );
	}
	
	// draw box around ascii
	RGBForeColor( &colour );
	RGBBackColor( &g.white );
	EraseRect( &asciiRect );
	FrameRect( &asciiRect );
	if( activeWindow && p.GWorldDepth > 1 )
	{
		RGBForeColor( &g.white );
		MoveTo( asciiRect.left,	asciiRect.bottom );
		LineTo( asciiRect.right,	asciiRect.bottom );
		LineTo( asciiRect.right,	asciiRect.top );
		RGBForeColor( &g.bevelColour );
		MoveTo( asciiRect.right,	asciiRect.top -1 );
		LineTo( asciiRect.left -1,	asciiRect.top -1 );
		LineTo( asciiRect.left -1,	asciiRect.bottom );
	}
	
	// hilight where selected text will be
	if( selStart != selEnd )
	{
		// get regions of selected text
		RgnHandle hexRgn = NewRgn(), asciiRgn = NewRgn(), insetRgn = NewRgn();
		FindSelectedRegions( window, hexRgn, asciiRgn );
		
		// set foreground colour to hilight colour
		RGBColour hilightColour;
		GetPortHilightColour( GetWindowPort(window), &hilightColour );
		RGBForeColor( &hilightColour );
		
		// draw what needs to be drawn
		PenSize( 2, 2 );
		if( editingHex )	PaintRgn( hexRgn );
		else				FrameRgn( hexRgn );
		if( !editingHex )	PaintRgn( asciiRgn );
		else				FrameRgn( asciiRgn );
		PenNormal();
		
		DisposeRgn( hexRgn );
		DisposeRgn( asciiRgn );
		DisposeRgn( insetRgn );
	}
		
	// reset text colour
	if( activeWindow )
			colour = g.black;
	else	colour = g.textColour;
	RGBForeColor( &colour );
	
	// get useful data
	Plug_WindowRef plugWindow = Host_GetPlugWindowFromWindowRef( window );
	Plug_ResourceRef resource = Host_GetTargetResource( plugWindow );
	
	// get resource length & lock data handle
	UInt32 length = Host_GetResourceSize( resource );	// which of these two would be faster?
//	UInt32 length = GetHandleSize( data );				//	the host callback returns a value from a struct, GetHandleSize probably does the same
	HLock( data );
	if( Host_GetResourceSize( resource ) != GetHandleSize( data ) )
	{
		SetGWorld( origPort, origDev );
		Host_DebugError( "\presource size != handle size", Host_GetResourceSize( resource ) );	// would be nice if I could pass both values! (any one know how to convert a number to > 1?  -  i.e. use "12.34" to pass 12 and 34)
		SetGWorld( theGWorld, null );
	}
	
	// init variables
	char	buffer[16*4 +11];
	long	addr;						// offset of byte in current res chunk
	UInt32	line,						// current line number
			currentByte	= topline *16;	// offset of byte in resource
	short	hexPos, asciiPos;
	unsigned char	ascii, hex1, hex2;	// HexEdit uses 'register short' not 'unsigned char' ?!?!
	
	// start line count at scroll value
	for( line = topline; line <= (lastline < bottomline? lastline:bottomline); line++ )	
	{
		hexPos = 10;
		asciiPos = 59;
		sprintf( buffer, "%08lX: ", currentByte );
		
		// draw bytes
		for( addr = 0; addr < 16; addr++ )
		{
			if( currentByte < length )
			{
			//	BlockMoveData( *data + currentByte, &ascii, 1 );
			//	hex1 = *(*data + currentByte);
			//	hex2 = *(*data + currentByte);
				ascii = hex1 = hex2 = *(*data + currentByte);
				hex1 >>= 4;
				hex1 &= 0x0F;
				hex2 &= 0x0F;
				hex1 += (hex1 < 10)? 0x30 : 0x37;
				hex2 += (hex2 < 10)? 0x30 : 0x37;
				
				buffer[hexPos++] = hex1;
				buffer[hexPos++] = hex2;
				buffer[hexPos++] = 0x20;
				if( ascii >= p.lowChar && ascii < p.highChar )
					 buffer[asciiPos++] = ascii;
				else buffer[asciiPos++] = 0x2E;	// full stop								
			}
			else
			{
				// without this the ascii would be printed right next to the hex on the last line
				buffer[hexPos++] = 0x20;
				buffer[hexPos++] = 0x20;
				buffer[hexPos++] = 0x20;
				buffer[asciiPos++] = 0x20;
			}
			
			// fill hex/ascii gap with a space to make it print
			buffer[58] = 0x20;
			
			// advance current byte
			currentByte++;
		}
		
		MoveTo( kHexCharWidth, kHeaderHeight + kTextMargin + kHexLineHeight*(line - topline +1) );
		DrawText( buffer, 0, 75 );	// buffer, first byte, byte count
	}
	
	// unlock handle and record window heights
	HUnlock( data );
	
#if Use_GWorlds
	SetGWorld( origPort, origDev );
	RGBBackColor( &g.white );
	RGBForeColor( &g.black );
	CopyBits( GetPortBitMapForCopyBits(theGWorld), GetPortBitMapForCopyBits(GetWindowPort(window)), &drawingBounds /*source rect*/, &drawingBounds /*dest rect*/, srcCopy, null );
	UnlockPixels( thePixMap );		// line redundant as GWorld is disposed of next!
	DisposeGWorld( theGWorld );
#endif
	
	// restore old port
	SetPort( oldPort );
	return eventNotHandledErr;
}

  /****************/
 /* MAINTAINANCE */
/****************/

/*** UPDATE HEX INFO ***/
OSStatus HexWindow::UpdateHexInfo( void )
{
	// hex and ascii rects
	Rect windowBounds;
	GetWindowPortBounds( window, &windowBounds );
	SetRect( &hexRect, kHexCharWidth *11 -3, kTextMargin + kHeaderHeight, kHexCharWidth *58 +3, windowBounds.bottom - kTextMargin );
	SetRect( &asciiRect, kHexCharWidth *60 -3, kTextMargin + kHeaderHeight, kHexCharWidth *76 +3, windowBounds.bottom - kTextMargin );
	
	// scrollbar globals
	firstline	= 0;
	topline		= GetControlValue( scrollbar );
	bottomline	= ((windowBounds.bottom - windowBounds.top - kHeaderHeight - 2*kTextMargin -4) / kHexLineHeight) + topline -1;
	lastline	= (GetHandleSize( data ) /16) - (GetHandleSize( data ) %16? 0:1);
	
	// scrollbar values
	SInt32 number = lastline - (bottomline - topline);
	if( number <= 0 ) SetControlValue( scrollbar, 0 );
	SetControlMaximum( scrollbar, (number > 0)? number:0 );
	SetControlViewSize( scrollbar, bottomline - topline );
	
	return noErr;
}

/*** INSERT BYTES ***/
Boolean HexWindow::InsertBytes( void *newData, signed long length, unsigned long offset )
{
	signed long size = (signed long) GetHandleSize( data );
	if( size + length <= 0 ) length = -size;	// don't dispose of resource, just set it to zero length
	
	SInt8 state = HGetState( data );
	HLock( data );
	if( length > 0 )		// writing things
	{
		SetHandleSize( data, size + length );
		if( MemError() ) return false;
		BlockMoveData( *data + offset, *data + offset + length, size - offset );
		BlockMoveData( newData, *data + offset, length );
	}
	else if( length < 0 )	// deleting things
	{
		BlockMoveData( *data + offset, *data + offset + length, size - offset );
		SetHandleSize( data, size + length );
		if( MemError() ) return false;
	}
	HSetState( data, state );
	return true;
}