/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
//////////////////////////////////////////////////////////////////////////////
//																			//
//	pfJournalBook Class 													//
//	A generic, high-level, abstract method of creating various Myst-like	//
//	books within the game with very little effort, while ensuring that they	//
//	all remain consistent in appearance and operability.					//
//																			//
//////////////////////////////////////////////////////////////////////////////

#include "pfJournalBook.h"
#include "hsUtils.h"
#include "hsStlUtils.h"
#include "hsResMgr.h"
#include "pcSmallRect.h"
#include "plgDispatch.h"
#include "pfGameGUIMgr/pfGUIDialogMod.h"
#include "pfGameGUIMgr/pfGUIControlMod.h"
#include "pfGameGUIMgr/pfGUICheckBoxCtrl.h"
#include "pfGameGUIMgr/pfGUIDialogHandlers.h"
#include "pfGameGUIMgr/pfGUIDynDisplayCtrl.h"
#include "pfGameGUIMgr/pfGUIClickMapCtrl.h"
#include "pfGameGUIMgr/pfGUIButtonMod.h"
#include "pfGameGUIMgr/pfGUIProgressCtrl.h"
#include "pfGameGUIMgr/pfGUIMultiLineEditCtrl.h"

#include "pfMessage/pfGUINotifyMsg.h"
#include "plGImage/plMipmap.h"
#include "plGImage/plDynamicTextMap.h"
#include "plPipeline/hsGDeviceRef.h"
#include "plMessage/plAnimCmdMsg.h"
#include "pnKeyedObject/plFixedKey.h"
#include "pnMessage/plRefMsg.h"
#include "pnMessage/plTimeMsg.h"
#include "plMessage/plLayRefMsg.h"
#include "plMessage/plMatRefMsg.h"
#include "plSurface/plLayerInterface.h"
#include "plSurface/plLayer.h"
#include "plSurface/hsGMaterial.h"
#include "plAgeLoader/plAgeLoader.h"
#include "pfSurface/plLayerBink.h"

// So we can do image searches in our local age
#include "plNetClient/plNetClientMgr.h"
#include "plResMgr/plKeyFinder.h"

// For notify sends
#include "pnMessage/plNotifyMsg.h"
#include "pnTimer/plTimerCallbackManager.h"
#include "plMessage/plTimerCallbackMsg.h"

// For custom cursors
#include "plInputCore/plInputInterface.h"

// For measuring text
#include "plGImage/plFont.h"

// For SFX
#include "hsTimer.h"



//////////////////////////////////////////////////////////////////////////////
//// pfEsHTMLChunk Class /////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

class pfEsHTMLChunk
{
	public:

		std::wstring fText;	// Paragraph text, or face name
		plKey	fImageKey;	// Key of image
		UInt8	fFontSize;
		UInt32	fFlags;
		UInt8	fType;
		UInt32	fEventID;

		pcSmallRect	fLinkRect;		// Used only for image chunks, and only when stored in the fVisibleLinks array

		hsColorRGBA	fColor;

		UInt16		fAbsoluteX, fAbsoluteY;

		hsScalar	fCurrOpacity;	// For SFX images
		hsScalar	fSFXTime;		// For SFX images
		hsScalar	fMinOpacity, fMaxOpacity;	

		hsColorRGBA	fCurrColor;
		hsColorRGBA	fOffColor, fOnColor;

		bool	fNoResizeImg;
		Int16	fLineSpacing;
		bool	fTintDecal;
		bool	fLoopMovie;
		bool	fOnCover; // if true, the movie is on the cover
		UInt8	fMovieIndex; // the index of the movie in the source code, used for identification

		enum Flags
		{
			kFontBold	= 0x00000001,
			kFontItalic = 0x00000002,
			kFontRegular= 0x00000004,		// 'cause 0 means "style not defined"
			kFontMask	= kFontBold | kFontItalic | kFontRegular,

			kFontColor	= 0x00000008,
			kFontSpacing= 0x00000010,

			kCenter		= 0x00000001,
			kLeft		= 0x00000002,
			kRight		= 0x00000003,
			kAlignMask	= 0x00000003,

			kBlendAlpha	= 0x00000004,
			kCanLink	= 0x00000008,
			kFloating	= 0x00000010,
			kGlowing	= 0x00000020,
			kActAsCB	= 0x00000040,	// Cause the image to act in a checkbox-like fashion. 
										// Min opacity turns into "off opacity" and max opacity
										// is "on opacity"
			kChecked	= 0x00000080,	// Only for kActAsCB, set if it's currently "checked"
			kTranslucent= 0x00000100	// is the image translucent? if so, use fCurrOpacity
		};

		enum Types
		{
			kEmpty = 0,
			kParagraph,
			kImage,
			kPageBreak,
			kFontChange,
			kMargin,
			kCover,			// Just a placeholder, never actually used after compile time
			kBook,			// another placeholder
			kDecal,
			kMovie,
			kEditable		// placeholder, ver 3.0
		};

		// Paragraph constructor
		pfEsHTMLChunk( const wchar_t *text )
		{
			fType = kParagraph;
			if (text)
				fText = text;
			else
				fText = L"";
			fFlags = kLeft;
			fFontSize = 0;
			fImageKey = nil;
			fEventID = 0;
			fColor.Set( 0.f, 0.f, 0.f, 1.f );
			fAbsoluteX = fAbsoluteY = 0;
			fCurrOpacity = 1.f;
			fMinOpacity = 0.f;
			fMaxOpacity = 1.f;
			fNoResizeImg = false;
			fLineSpacing = 0;
			fTintDecal = false;
			fLoopMovie = true;
			fOnCover = false;
			fMovieIndex = -1;
		}

		// Image constructor (used for decals and movies too)
		pfEsHTMLChunk( plKey imageKey, UInt32 alignFlags )
		{
			fType = kImage;
			fText = L"";
			fFlags = alignFlags;
			fFontSize = 0;
			fImageKey = imageKey;
			fEventID = 0;
			fColor.Set( 0.f, 0.f, 0.f, 1.f );
			fAbsoluteX = fAbsoluteY = 0;
			fCurrOpacity = 1.f;
			fMinOpacity = 0.f;
			fMaxOpacity = 1.f;
			fNoResizeImg = false;
			fLineSpacing = 0;
			fTintDecal = false;
			fLoopMovie = true;
			fOnCover = false;
			fMovieIndex = -1;
		}

		// Page break constructor
		pfEsHTMLChunk()
		{
			fType = kPageBreak;
			fText = L"";
			fImageKey = nil;
			fFontSize = 0;
			fFlags = 0;
			fEventID = 0;
			fColor.Set( 0.f, 0.f, 0.f, 1.f );
			fAbsoluteX = fAbsoluteY = 0;
			fCurrOpacity = 1.f;
			fMinOpacity = 0.f;
			fMaxOpacity = 1.f;
			fNoResizeImg = false;
			fLineSpacing = 0;
			fTintDecal = false;
			fLoopMovie = true;
			fOnCover = false;
			fMovieIndex = -1;
		}

		// Font change constructor
		pfEsHTMLChunk( const wchar_t *face, UInt8 size, UInt32 fontFlags )
		{
			fType = kFontChange;
			if (face)
				fText = face;
			else
				fText = L"";
			fFontSize = size;
			fFlags = fontFlags;
			fImageKey = nil;
			fEventID = 0;
			fColor.Set( 0.f, 0.f, 0.f, 1.f );
			fAbsoluteX = fAbsoluteY = 0;
			fCurrOpacity = 1.f;
			fMinOpacity = 0.f;
			fMaxOpacity = 1.f;
			fNoResizeImg = false;
			fLineSpacing = 0;
			fTintDecal = false;
			fLoopMovie = true;
			fOnCover = false;
			fMovieIndex = -1;
		}

		~pfEsHTMLChunk() {}
};

//////////////////////////////////////////////////////////////////////////////
//// Our Template Dialog Handler /////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////

class pfJournalDlgProc : public pfGUIDialogProc
{
	protected:

		pfBookData	*fBook;

	public:

		enum TagIDs
		{
			kTagBookCover = 100,
			kTagLeftDTMap = 101,
			kTagRightDTMap = 102,
			kTagTurnFrontDTMap = 103,
			kTagTurnBackDTMap = 104,
			kTagTurnPageCtrl = 105,
			kTagLeftPageBtn = 106,
			kTagRightPageBtn = 107,
			kTagOutsideBookBtn = 108,
			kTagCoverLayer = 109,
			kTagLeftCornerBtn = 110,
			kTagRightCornerBtn = 111,
			kTagWidthCBDummy = 112,
			kTagHeightCBDummy = 113,
			kTagLeftEditCtrl = 120,
			kTagRightEditCtrl = 121,
			kTagTurnFrontEditCtrl = 122,
			kTagTurnBackEditCtrl = 123
		};

		pfJournalDlgProc( pfBookData *book ) : fBook( book )
		{
		}

		virtual ~pfJournalDlgProc()
		{
		}

		virtual void	DoSomething( pfGUIControlMod *ctrl )
		{
			if ( fBook && fBook->fCurrBook )
			{
				if( ctrl->GetTagID() == kTagBookCover )
				{
					fBook->fCurrBook->Open();
				}
				else if( ctrl->GetTagID() == kTagLeftPageBtn )
				{
					// only turn pages if the book is actually open
					if( fBook->fCurrentlyOpen )
						fBook->fCurrBook->IHandleLeftSideClick();
				}
				else if( ctrl->GetTagID() == kTagRightPageBtn )
				{
					// only turn pages if the book is actually open
					if( fBook->fCurrentlyOpen )
						fBook->fCurrBook->IHandleRightSideClick();
				}
				else if( ctrl->GetTagID() == kTagOutsideBookBtn )
				{
					if( fBook->fCurrentlyOpen )
						fBook->fCurrBook->CloseAndHide();
					else
						fBook->fCurrBook->Hide();
				}
			}
		}

		// Called on dialog init (i.e. first showing, before OnShow() is called), only ever called once
		virtual void	OnInit( void )
		{
		}

		// Called before the dialog is shown, always after OnInit()
		virtual void	OnShow( void )
		{
		}

		// Called before the dialog is hidden
		virtual void	OnHide( void )
		{
		}

		// Called on the dialog's destructor, before it's unregistered with the game GUI manager
		virtual void	OnDestroy( void )
		{
		}

		// Called when the dialog's focused control changes
		virtual void	OnCtrlFocusChange( pfGUIControlMod *oldCtrl, pfGUIControlMod *newCtrl )
		{
		}

		// Called when the key bound to a GUI event is pressed. Only called on the top modal dialog
		virtual void	OnControlEvent( ControlEvt event )
		{
			if( event == kExitMode )
			{
				if( fBook->fCurrentlyOpen )
					fBook->fCurrBook->CloseAndHide();
				else
					fBook->fCurrBook->Hide();
			}
		}

		virtual void	HandleExtendedEvent( pfGUIControlMod *ctrl, UInt32 event )
		{
			if (fBook)
			{
				if( ctrl == fBook->fLeftPageMap )
				{
					if( event == pfGUIClickMapCtrl::kMouseHovered )
					{
						if (fBook->fCurrBook)
						{
							// Update our custom cursor on the map
							Int32 idx = fBook->fCurrBook->IFindCurrVisibleLink( false, true );
							if( idx != -1 )
								fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorPoised/*Hand*/ );
							else if(( fBook->fCurrBook->fCurrentPage > 1 )&&( fBook->fCurrBook->fAllowTurning ))
								fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorLeft );
							else if ((fBook->fCurrBook->fAreEditing) && !(fBook->fLeftEditCtrl->ShowingBeginningOfBuffer())) // if we have more buffer to show
								fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorLeft );
							else
								fBook->fLeftPageMap->SetCustomCursor( plInputInterface::kCursorUp );
						}
					}
				}
				else if( ctrl == fBook->fRightPageMap )
				{
					if( event == pfGUIClickMapCtrl::kMouseHovered )
					{
						if (fBook->fCurrBook)
						{
							// Update our custom cursor on the map
							Int32 idx = fBook->fCurrBook->IFindCurrVisibleLink( true, true );
							if( idx != -1 )
								fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorPoised/*Hand*/ );
							else if((fBook->fCurrBook->fAreWeShowing) && ( fBook->fCurrBook->fCurrentPage + 2 <= fBook->fCurrBook->fLastPage )&&( fBook->fCurrBook->fAllowTurning ))
								fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorRight );
							else if((fBook->fCurrBook->fAreEditing) && !(fBook->fRightEditCtrl->ShowingEndOfBuffer())) // if we have more buffer to show
								fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorRight );
							else
								fBook->fRightPageMap->SetCustomCursor( plInputInterface::kCursorUp );
						}
					}
				}
			}
		}
};

//// Multiline edit handler class ////////////////////////////////////////////

class pfBookMultiLineEditProc : public pfGUIMultiLineEditProc
{
private:
	pfBookData *bookData;
public:
	pfBookMultiLineEditProc(pfBookData *owner) { bookData = owner; }
	virtual ~pfBookMultiLineEditProc() {}

	virtual void OnEndOfControlList(Int32 cursorPos) { bookData->HitEndOfControlList(cursorPos); }
	virtual void OnBeginningOfControlList(Int32 cursorPos) { bookData->HitBeginningOfControlList(cursorPos); }
};

//// Book data class /////////////////////////////////////////////////////////

pfBookData::pfBookData(const char *guiName /* = nil */)
{
	fCurrBook = nil;
	fDialog = nil;
	fCoverButton = fTurnPageButton = nil;
	fLeftPageMap = fRightPageMap = nil;
	fCoverLayer = nil;
	fCoverMaterial = nil;
	UInt16 i;
	for (i=0; i<4; i++)
		fPageMaterials[i] = nil;
	fLeftCorner = fRightCorner = nil;
	fWidthCtrl = fHeightCtrl = nil;
	fCurrSFXPages = kNoSides;
	fBaseSFXTime = 0.f;
	fResetSFXFlag = false;
	fSFXUpdateFlip = false;
	fCurrentlyTurning = false;

	fRightEditCtrl = fLeftEditCtrl = nil;
	fTurnFrontEditCtrl = fTurnBackEditCtrl = nil;
	fEditable = false;
	fAdjustCursorTo = -1;
	
	if (guiName)
		fGUIName = guiName;
	else
		fGUIName = "BkBook";
}

pfBookData::~pfBookData()
{
	RegisterForSFX( kNoSides );
}

void pfBookData::LoadGUI()
{
	// has the dialog been loaded yet?
	if (!pfGameGUIMgr::GetInstance()->IsDialogLoaded(fGUIName.c_str()))
		// no then load and set handler
		pfGameGUIMgr::GetInstance()->LoadDialog(fGUIName.c_str(), GetKey(), "GUI");
	else
		// yes then just set the handler
		pfGameGUIMgr::GetInstance()->SetDialogToNotify(fGUIName.c_str(), GetKey());
}

hsBool pfBookData::MsgReceive(plMessage *pMsg)
{
	plGenRefMsg *ref = plGenRefMsg::ConvertNoRef(pMsg);
	if(ref != nil)
	{
		if(ref->fType == kRefDialog)
		{
			if(ref->GetContext() & (plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace))
			{
				pfGUIDialogMod *temp = pfGUIDialogMod::ConvertNoRef(ref->GetRef());
				if (temp != nil) // sanity check
					fDialog = temp;
			}
			/*else
			{
				fDialog = nil;
				fCoverButton = nil;
			}*/
			return true;
		}
		else if(ref->fType == kRefDefaultCover)
		{
			if(ref->GetContext() & (plRefMsg::kOnCreate | plRefMsg::kOnRequest | plRefMsg::kOnReplace))
				fDefaultCover = plMipmap::ConvertNoRef(ref->GetRef());
			else
				fDefaultCover = nil;
			return true;
		}
	}

	plEventCallbackMsg *callback = plEventCallbackMsg::ConvertNoRef( pMsg );
	if( callback != nil )
	{
		// Our callback message to tell us the page is done flipping
		if( callback->fUser & 0x08 )
		{
			// make sure that we still have a current book
			if (fCurrBook)
			{
				// Or actually maybe it's that we're done closing and should hide now,
				// produced from a CloseAndHide()
				if( callback->fEvent == kStop )
					fCurrBook->Hide();
				else
					fCurrBook->IFinishShow( ( callback->fUser & 0x01 ) ? true : false );
			}
		}
		else if( fCurrentlyTurning )
		{
			if( callback->fUser & 0x04 )
				IFillUncoveringPage( (hsBool)( callback->fUser & 0x01 ) ? true : false ); 
			else if( callback->fUser & 0x02 )
				StartTriggeredFlip( (hsBool)( callback->fUser & 0x01 ) ? true : false );
			else
				IFinishTriggeredFlip( (hsBool)( callback->fUser & 0x01 ) ? true : false );
		}
		return true;
	}

	pfGUINotifyMsg *notify = pfGUINotifyMsg::ConvertNoRef(pMsg);
	if(notify != nil)
	{
		// The only time we should get this is when the dialog loads; after that, we hijack
		// the dialog proc with our own
		IInitTemplate(pfGUIDialogMod::ConvertNoRef(notify->GetSender()->ObjectIsLoaded()));
		return true;
	}

	plTimeMsg *time = plTimeMsg::ConvertNoRef( pMsg );
	if( time != nil && fCurrSFXPages != kNoSides && !fCurrentlyTurning && fCurrentlyOpen )
	{
		IHandleSFX( (hsScalar)time->DSeconds() );
		return true;		
	}

	plTimerCallbackMsg* timerMsg = plTimerCallbackMsg::ConvertNoRef(pMsg);
	if (timerMsg)
	{
		if (timerMsg->fID == 99) // the flip animation is about to end, hide the page to prevent flickering
		{
			// the right side was uncovered, so the left side needs to be hidden
			fLeftEditCtrl->SetVisible(false);
		}
		else if (timerMsg->fID == 98)
		{
			// the left side was uncovered, so the right side needs to be hidden
			fRightEditCtrl->SetVisible(false);
		}
	}

	return hsKeyedObject::MsgReceive(pMsg);
}

void pfBookData::IInitTemplate(pfGUIDialogMod *templateDlg)
{
	hsAssert(templateDlg != nil, "Nil template in pfBookData::IInitTemplate()!");

	// Init and ref our fDialog pointer
	hsgResMgr::ResMgr()->SendRef(templateDlg->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDialog), plRefFlags::kPassiveRef);  

	// Hijack the dialog proc with our own
	templateDlg->SetHandlerForAll(TRACKED_NEW pfJournalDlgProc(this));

	// Find our animation keys
																
	// And other interesting pointers
	fCoverButton = pfGUICheckBoxCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagBookCover));
	fTurnPageButton = pfGUICheckBoxCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnPageCtrl));
	fLeftPageMap = pfGUIClickMapCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftPageBtn));
	fRightPageMap = pfGUIClickMapCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightPageBtn));
	fLeftCorner = pfGUIButtonMod::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftCornerBtn));
	fRightCorner = pfGUIButtonMod::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightCornerBtn));
	fWidthCtrl = pfGUIProgressCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagWidthCBDummy));
	fHeightCtrl = pfGUIProgressCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagHeightCBDummy));

	fTurnPageButton->SetEnabled(false);
	fCoverButton->DontPlaySounds();	// dont let checkbox play sounds, journal will take care of that.

	fCoverLayer = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagCoverLayer))->GetLayer(0);
	fCoverMaterial = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagCoverLayer))->GetMaterial(0);

	fPageMaterials[kLeftPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftDTMap))->GetMaterial(0);
	fPageMaterials[kRightPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightDTMap))->GetMaterial(0);
	fPageMaterials[kTurnFrontPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnFrontDTMap))->GetMaterial(0);
	fPageMaterials[kTurnBackPage] = pfGUIDynDisplayCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnBackDTMap))->GetMaterial(0);

	// Grab and ref the default cover mipmap
	plLayer *lay = plLayer::ConvertNoRef(fCoverLayer);
	if((lay != nil)&&(lay->GetTexture() != nil))
		hsgResMgr::ResMgr()->AddViaNotify(lay->GetTexture()->GetKey(), TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, -1, kRefDefaultCover), plRefFlags::kPassiveRef);  

	fLeftPageMap->SetFlag(pfGUIClickMapCtrl::kReportHovering);
	fRightPageMap->SetFlag(pfGUIClickMapCtrl::kReportHovering);

	fLeftEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagLeftEditCtrl));
	if (fLeftEditCtrl)
	{
		fLeftEditCtrl->SetEnabled(false); // disable the edit controls initially, we can turn them on later
		fLeftEditCtrl->SetVisible(false);
	}
	fRightEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagRightEditCtrl));
	if (fRightEditCtrl)
	{
		fRightEditCtrl->SetEnabled(false);
		fRightEditCtrl->SetVisible(false);
	}

	fTurnFrontEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnFrontEditCtrl));
	if (fTurnFrontEditCtrl)
	{
		fTurnFrontEditCtrl->SetEnabled(false);
		fTurnFrontEditCtrl->SetVisible(false);
	}

	fTurnBackEditCtrl = pfGUIMultiLineEditCtrl::ConvertNoRef(templateDlg->GetControlFromTag(pfJournalDlgProc::kTagTurnBackEditCtrl));
	if (fTurnBackEditCtrl)
	{
		fTurnBackEditCtrl->SetEnabled(false);
		fTurnBackEditCtrl->SetVisible(false);
	}
	// if all the edit controls are here, we are editable, so set up the initial link
	if (fLeftEditCtrl && fRightEditCtrl && fTurnFrontEditCtrl && fTurnBackEditCtrl)
	{
		fEditable = true;
		fLeftEditCtrl->SetNext(fRightEditCtrl);
		fLeftEditCtrl->SetEventProc(TRACKED_NEW pfBookMultiLineEditProc(this));
		fTurnFrontEditCtrl->SetNext(fTurnBackEditCtrl); // these are always back to back
	}
}

//// IGetDTMap ///////////////////////////////////////////////////////////////
// Just a quick helper 

plDynamicTextMap *pfBookData::GetDTMap(UInt32 which)
{
	pfGUIDynDisplayCtrl *display = pfGUIDynDisplayCtrl::ConvertNoRef(fDialog->GetControlFromTag(which));
	return display->GetMap(0);
}

//// GetEditCtrl /////////////////////////////////////////////////////////////

pfGUIMultiLineEditCtrl *pfBookData::GetEditCtrl(UInt32 which)
{
	switch (which)
	{
	case pfJournalDlgProc::kTagLeftEditCtrl:
		return fLeftEditCtrl;
	case pfJournalDlgProc::kTagRightEditCtrl:
		return fRightEditCtrl;
	case pfJournalDlgProc::kTagTurnFrontEditCtrl:
		return fTurnFrontEditCtrl;
	case pfJournalDlgProc::kTagTurnBackEditCtrl:
		return fTurnBackEditCtrl;
	default:
		return nil;
	}
}

//// IRegisterForSFX /////////////////////////////////////////////////////////
// Registers (or unregisters) for time messages so we can process special FX 
// if we need to

void pfBookData::RegisterForSFX(WhichSide whichPages)
{
	if( whichPages == fCurrSFXPages)
		return;

	if(whichPages != kNoSides)
	{
		plgDispatch::Dispatch()->RegisterForExactType(plTimeMsg::Index(), GetKey());
	}
	else
		plgDispatch::Dispatch()->UnRegisterForExactType(plTimeMsg::Index(), GetKey());
	
	fCurrSFXPages = whichPages;
}

//// IHandleSFX //////////////////////////////////////////////////////////////
// Process SFX for this frame. Note: if ANYTHING is wrong (page starts not
// calced, pointers bad, etc) just bail, since the SFX are just for visual
// flair and not really needed.

void pfBookData::IHandleSFX(hsScalar currTime, WhichSide whichSide /*= kNoSides*/)
{
	if(fCurrBook == nil)
		return;
	if(whichSide == kNoSides)
	{
		if(fResetSFXFlag)
		{
			fBaseSFXTime=currTime;
			fResetSFXFlag=false;
		}

		fSFXUpdateFlip = !fSFXUpdateFlip;

		// Slightly recursive here to help us out a bit
		if(fSFXUpdateFlip&&(fCurrSFXPages & kLeftSide))
			IHandleSFX(currTime, kLeftSide);
		else if(!fSFXUpdateFlip &&(fCurrSFXPages & kRightSide))
			IHandleSFX(currTime, kRightSide);
		return;
	}

	// Update all SFX images for this page first
	hsScalar deltaT = currTime - fBaseSFXTime;

	UInt32 idx, inc = (whichSide == kLeftSide) ? 0 : 1;
	if(fCurrBook->fPageStarts.GetCount() <= fCurrBook->fCurrentPage + inc + 1)
		return;

	bool stillWant = false;
	for(idx = fCurrBook->fPageStarts[fCurrBook->fCurrentPage + inc]; idx < fCurrBook->fPageStarts[fCurrBook->fCurrentPage + inc + 1]; idx++)
	{
		pfEsHTMLChunk *chunk = fCurrBook->fHTMLSource[idx];
		
		if(chunk->fFlags & pfEsHTMLChunk::kGlowing)
		{
			// Glow SFX: animate opacity based on time offset
			UInt8 isOdd = 0;
			hsScalar newDelta = deltaT;
			while(newDelta > chunk->fSFXTime)
			{
				isOdd = ~isOdd;
				newDelta -= chunk->fSFXTime;
			}

			// If we're not odd, then we're decreasing in opacity, else we're increasing
			if(isOdd)
				newDelta = chunk->fSFXTime - newDelta;

			chunk->fCurrOpacity = chunk->fMaxOpacity - ((chunk->fMaxOpacity - chunk->fMinOpacity)*(newDelta / chunk->fSFXTime));
			stillWant = true;
		}
		else if(chunk->fFlags & pfEsHTMLChunk::kActAsCB)
		{
			// If our opacity doesn't match our checked state, slowly fade to it
			hsColorRGBA	inc;
			inc.Set(0.1f, 0.1f, 0.1f, 0.1f);

			hsColorRGBA &want = (chunk->fFlags & pfEsHTMLChunk::kChecked) ? chunk->fOnColor : chunk->fOffColor;
			if(want != chunk->fCurrColor)
			{
#define COMPARE_ME( wnt, curr ) \
				if( wnt > curr + 0.1f )					\
				{										\
					curr += 0.1f;	stillWant = true;	\
				}										\
				else if( wnt < curr - 0.1f )			\
				{										\
					curr -= 0.1f;	stillWant = true;	\
				}										\
				else									\
					curr = wnt;

				COMPARE_ME( want.r, chunk->fCurrColor.r )
				COMPARE_ME( want.g, chunk->fCurrColor.g )
				COMPARE_ME( want.b, chunk->fCurrColor.b )
				COMPARE_ME( want.a, chunk->fCurrColor.a )
			}
		}
	}

	// All done updating that page. Now render it!
	fCurrBook->IRenderPage( fCurrBook->fCurrentPage + inc, ( whichSide == kLeftSide ) ? pfJournalDlgProc::kTagLeftDTMap : pfJournalDlgProc::kTagRightDTMap );

	if( !stillWant )
	{
		// Done with FX for this page, so unregister for FX on this page now
		RegisterForSFX((WhichSide)(fCurrSFXPages & ~whichSide));
	}
}

//// IFillUncoveringPage /////////////////////////////////////////////////////
// Yet another step in the page flip, to make SURE we're already showing the
// turning page before we fill in the page behind it

void pfBookData::IFillUncoveringPage(hsBool rightSide)
{
	// only show the turning page if the book is open
	if ( CurrentlyOpen() )
		fTurnPageButton->SetVisible(true);
	// make sure there is a current book
	if (fCurrBook)
	{
		if (fCurrBook->fAreEditing)
		{
			int id;
			UpdatePageCorners(rightSide ? kRightSide : kLeftSide);
			if (rightSide)
			{
				fTurnBackEditCtrl->ForceUpdate(); // force everything that is changing to update
				fTurnBackEditCtrl->SetVisible(true); // and make sure everything is showing
				fTurnFrontEditCtrl->ForceUpdate();
				fTurnFrontEditCtrl->SetVisible(true);
				fRightEditCtrl->ForceUpdate();
				fRightEditCtrl->SetVisible(true);
				// The left edit ctrl doesn't update until the page flip animation is done
				id = 99;
			}
			else
			{
				fTurnFrontEditCtrl->ForceUpdate();
				fTurnFrontEditCtrl->SetVisible(true);
				fTurnBackEditCtrl->ForceUpdate();
				fTurnBackEditCtrl->SetVisible(true);
				fLeftEditCtrl->ForceUpdate();
				fLeftEditCtrl->SetVisible(true);
				// The right edit ctrl doesn't update until the page flip animation is done
				id = 98;
			}
			
			// create a timer so we can hide the old left or right turn page right before the animation finishes to prevent flicker
			plTimerCallbackMsg* pTimerMsg = TRACKED_NEW plTimerCallbackMsg(GetKey(),id);
			plgTimerCallbackMgr::NewTimer( .5, pTimerMsg ); // .5 found by trial and error
			return; // the gui controls render everything for us, so ignoring this request
		}
		if(rightSide)
			fCurrBook->IRenderPage(fCurrBook->fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap);
		else
			fCurrBook->IRenderPage(fCurrBook->fCurrentPage, pfJournalDlgProc::kTagLeftDTMap);
	}

	// Update the page corner we're flipping away from
	UpdatePageCorners(rightSide ? kRightSide : kLeftSide);
}

//// ITriggerPageFlip ////////////////////////////////////////////////////////
// Triggers the start of the page-flipping animation, as well as sets up the callback for when it's finished

void pfBookData::ITriggerPageFlip(hsBool flipBackwards, hsBool immediate)
{
	// Hack here: since we don't have an official interface to select these directly
	// in MAX, we just use a GUI check box to grab them for us, even though we never
	// actually use the functionality of the checkbox itself
	const hsTArray<plKey> &keys = fTurnPageButton->GetAnimationKeys();
	const char *animName = fTurnPageButton->GetAnimationName();

	plAnimCmdMsg *msg = TRACKED_NEW plAnimCmdMsg();
	if (immediate)
	{
		msg->SetCmd(plAnimCmdMsg::kGoToEnd);
	}
	else
	{
		msg->SetCmd(plAnimCmdMsg::kContinue);
		msg->SetCmd(plAnimCmdMsg::kSetForewards);
	}
	msg->SetAnimName(flipBackwards ? "backward" : "forward");
	msg->AddReceivers(keys);
	
	// Here's the whole reason why we're not just checking the checkbox: so we can attach a callback
	// so we know when the animation completes. Pretty sad, huh? Poor checkbox.
	plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg;
	eventMsg->AddReceiver(GetKey());
	eventMsg->fRepeats = 0;
	if (immediate)
	{
		eventMsg->fUser = ((!flipBackwards) ? 0x01 : 0x00) | 0x02;
		eventMsg->fEvent = kSingleFrameAdjust;
	}
	else
	{
		eventMsg->fUser = (flipBackwards ? 0x01 : 0x00);
		eventMsg->fEvent = kStop;
	}
	msg->SetCmd(plAnimCmdMsg::kAddCallbacks);
	msg->AddCallback(eventMsg);
	hsRefCnt_SafeUnRef(eventMsg);
	if (!immediate)
	{
		// We want a second callback to tell us when, indeed, the page has started turning
		// and is thus visible and thus we can actually, really, honestly, safely fill in the
		// page behind it
		eventMsg = TRACKED_NEW plEventCallbackMsg;
		eventMsg->AddReceiver(GetKey());
		eventMsg->fRepeats = 0;
		eventMsg->fUser = !flipBackwards ? (0x04 | 0x01) : 0x04;
		eventMsg->fEvent = kBegin;	// Should cause it to be triggered once it seeks at the start of the command
		msg->AddCallback(eventMsg);
		hsRefCnt_SafeUnRef(eventMsg);
	}
	fCurrentlyTurning = true;

	msg->Send();
}

//// StartTriggeredFlip /////////////////////////////////////////////////////
// Finishes the start of the triggered page flip (once we're sure the 
// animation is at the new frame)

void pfBookData::StartTriggeredFlip(hsBool flipBackwards)
{
	if(flipBackwards)
	{
		ITriggerPageFlip(true, false);
	}
	else
	{
		ITriggerPageFlip(false, false);
	}
}

//// Kill the page flipping cause, we're closing the book

void pfBookData::KillPageFlip()
{
	if ( fCurrentlyTurning )
	{
		//ITriggerPageFlip(false, true);
		fTurnPageButton->SetVisible(false);
	}
}

//// IFinishTriggeredFlip ////////////////////////////////////////////////////
// Finishes the triggered page flip, on callback

void pfBookData::IFinishTriggeredFlip(hsBool wasBackwards)
{
	if (fCurrBook && fCurrBook->fAreEditing) // this is handled differently when we are editing
	{
		fLeftEditCtrl->SetNext(fRightEditCtrl); // relink the original path
		if (!wasBackwards)
		{
			// adjust the starting point of the control (not needed if we weren't backwards since that was done when we started turning
			Int32 newStart = fRightEditCtrl->GetLastVisibleLine();
			fLeftEditCtrl->SetGlobalStartLine(newStart);
		}
		if (fAdjustCursorTo >= 0)
		{
			if (wasBackwards)
			{
				fRightEditCtrl->SetCursorToLoc(fAdjustCursorTo);
				fRightEditCtrl->GetOwnerDlg()->SetFocus(fRightEditCtrl);
			}
			else
			{
				fLeftEditCtrl->SetCursorToLoc(fAdjustCursorTo);
				fLeftEditCtrl->GetOwnerDlg()->SetFocus(fLeftEditCtrl);
			}
			fAdjustCursorTo = -1;
		}

		fTurnFrontEditCtrl->SetVisible(false); // hide the controls
		fTurnBackEditCtrl->SetVisible(false);

		fLeftEditCtrl->SetVisible(true);
		fRightEditCtrl->SetVisible(true);

		fLeftEditCtrl->ForceUpdate();
		fRightEditCtrl->ForceUpdate();
	}
	else if(wasBackwards)
	{
		// Grab the DTMaps for the front of the flip page and the right page, so we can
		// copy the front into the right page
		plDynamicTextMap *turnFront = GetDTMap(pfJournalDlgProc::kTagTurnFrontDTMap);
		plDynamicTextMap *right = GetDTMap(pfJournalDlgProc::kTagRightDTMap);
//		right->Swap( turnFront );
		if ( turnFront->IsValid() && right->IsValid() )
		{
			memcpy(right->GetImage(), turnFront->GetImage(), right->GetLevelSize(0));
			if(right->GetDeviceRef() != nil)
				right->GetDeviceRef()->SetDirty(true);
		}
		// we are going to attempt to re-render the left-hand page
		// sometimes, the book stutters on a page flip and screws up the book
		if (fCurrBook)
		{
			fCurrBook->IRenderPage(fCurrBook->fCurrentPage, pfJournalDlgProc::kTagLeftDTMap);
			// move the videos over
			fCurrBook->IMoveMovies(PageMaterial(kTurnFrontPage),PageMaterial(kRightPage));
		}
	}
	else
	{
		// Grab the DTMaps for the back of the flip page and the left page, so we can
		// copy the back into the left page
		plDynamicTextMap *turnBack = GetDTMap(pfJournalDlgProc::kTagTurnBackDTMap);
		plDynamicTextMap *left = GetDTMap(pfJournalDlgProc::kTagLeftDTMap);
	//		right->Swap( turnFront );
		if ( turnBack->IsValid() && left->IsValid() )
		{
			memcpy(left->GetImage(), turnBack->GetImage(), left->GetLevelSize(0));
			if(left->GetDeviceRef() != nil)
				left->GetDeviceRef()->SetDirty(true);
		}
		// we are going to attempt to re-render the right-hand page
		// sometimes, the book stutters on a page flip and screws up the book
		if (fCurrBook)
		{
			fCurrBook->IRenderPage(fCurrBook->fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap);
			// move the videos over
			fCurrBook->IMoveMovies(PageMaterial(kTurnBackPage),PageMaterial(kLeftPage));
		}
	}

	// Hide our page flipping button/checkbox/whatever
	fTurnPageButton->SetVisible(false);

	// Update page corners
	UpdatePageCorners(kBothSides);

	fCurrentlyTurning = false;

	// Start our FX once the page is done turning
	fResetSFXFlag = true;
}

//// UpdatePageCorners //////////////////////////////////////////////////////
// Enables or disables the left and right page corners, to indicate current turnage state

void pfBookData::UpdatePageCorners(WhichSide which)
{
	// make sure there is a book to update!
	if (fCurrBook)
	{
		if (!fCurrBook->fAllowTurning || !fCurrBook->fAreWeShowing)
		{
			fLeftCorner->SetVisible(false);
			fLeftCorner->SetEnabled(false);
			fRightCorner->SetVisible(false);
			fRightCorner->SetEnabled(false);
			return;
		}
		if((which == kLeftSide)||(which == kBothSides))
		{
			if (fCurrBook->fAreEditing)
				fLeftCorner->SetVisible(!fLeftEditCtrl->ShowingBeginningOfBuffer()); // only show if the control is not viewing the beginning of the buffer
			else
				fLeftCorner->SetVisible((fCurrBook->fCurrentPage >= 2) ? true : false);
			// Note: always disabled (we just go on the page click itself)
			fLeftCorner->SetEnabled(false);
		}
		if((which == kRightSide)||(which == kBothSides))
		{
			if (fCurrBook->fAreEditing)
				fRightCorner->SetVisible(!fRightEditCtrl->ShowingEndOfBuffer()); // only show if the control is not viewing the end of the buffer
			else
				fRightCorner->SetVisible((fCurrBook->fCurrentPage + 2 <= fCurrBook->fLastPage) ? true : false);
			fRightCorner->SetEnabled(false);
		}
	}
}

//// SetCurrSize ////////////////////////////////////////////////////////////
// Seeks the width and height animations to set the desired book size. Sizes are in % across the animation

void pfBookData::SetCurrSize(hsScalar w, hsScalar h)
{
	fWidthCtrl->SetCurrValue(w);
	fHeightCtrl->SetCurrValue(h);
}

//// PlayBookCloseAnim //////////////////////////////////////////////////////
//	Triggers our animation for closing or opening the book.

void pfBookData::PlayBookCloseAnim(hsBool closeIt /*= true*/, hsBool immediate /*= false*/)
{
	// Disable the book cover button if we're opening, enable otherwise
	fCoverButton->SetEnabled(closeIt);

	// Tell our cover button to check or uncheck
	fCoverButton->SetChecked(!closeIt, immediate);

	// Trigger the open (or close) sound
	if(!immediate)
		fCoverButton->PlaySound(closeIt ? pfGUICheckBoxCtrl::kMouseUp : pfGUICheckBoxCtrl::kMouseDown);

	fCurrentlyOpen = !closeIt;
}

//// Event routines from a linked multi-line edit control ////////////////////

void pfBookData::HitEndOfControlList(Int32 cursorPos)
{
	fAdjustCursorTo = cursorPos;
	if (fCurrBook)
		fCurrBook->NextPage();
}

void pfBookData::HitBeginningOfControlList(Int32 cursorPos)
{
	fAdjustCursorTo = cursorPos;
	if (fCurrBook)
		fCurrBook->PreviousPage();
}

void pfBookData::EnableEditGUI(hsBool enable/* =true */)
{
	if (fEditable)
	{
		fLeftEditCtrl->SetEnabled(enable);
		fLeftEditCtrl->SetVisible(enable);
		fRightEditCtrl->SetEnabled(enable);
		fRightEditCtrl->SetVisible(enable);
		// we don't make these editable because they are temps used for the turning page
		fTurnFrontEditCtrl->SetVisible(false); // we don't want these visible initially either
		fTurnBackEditCtrl->SetVisible(false);
	}
}

//// Our Singleton Stuff /////////////////////////////////////////////////////

//pfJournalBook	*pfJournalBook::fInstance = nil;
std::map<std::string,pfBookData*> pfJournalBook::fBookGUIs;

void	pfJournalBook::SingletonInit( void )
{
	fBookGUIs["BkBook"] = TRACKED_NEW pfBookData(); // load the default book data object
	hsgResMgr::ResMgr()->NewKey("BkBook",fBookGUIs["BkBook"],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation());
	fBookGUIs["BkBook"]->LoadGUI();
}

void	pfJournalBook::SingletonShutdown( void )
{
	std::map<std::string,pfBookData*>::iterator i = fBookGUIs.begin();
	while (i != fBookGUIs.end())
	{
		pfBookData *bookData = i->second;
		bookData->GetKey()->UnRefObject();
		i->second = nil;
		i++;
	}
	fBookGUIs.clear();
}

void	pfJournalBook::LoadGUI( const char *guiName )
{
	if (fBookGUIs.find(guiName) == fBookGUIs.end()) // is it already loaded?
	{ // nope, load it
		fBookGUIs[guiName] = TRACKED_NEW pfBookData(guiName);
		hsgResMgr::ResMgr()->NewKey(guiName,fBookGUIs[guiName],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation());
		fBookGUIs[guiName]->LoadGUI();
	}
}

void	pfJournalBook::UnloadGUI( const char *guiName )
{
	if (strcmp(guiName,"BkBook")==0)
		return; // do not allow people to unload the default book gui
	std::map<std::string,pfBookData*>::iterator loc = fBookGUIs.find(guiName);
	if (loc != fBookGUIs.end()) // make sure it's loaded
	{
		fBookGUIs[guiName]->GetKey()->UnRefObject();
		fBookGUIs[guiName] = nil;
		fBookGUIs.erase(loc);
	}
}

void	pfJournalBook::UnloadAllGUIs()
{
	std::map<std::string,pfBookData*>::iterator i = fBookGUIs.begin();
	std::vector<std::string> names;
	while (i != fBookGUIs.end())
	{
		std::string name = i->first;
		names.push_back(name); // store a list of keys
		i++;
	}
	int idx;
	for (idx = 0; idx < names.size(); idx++)
		UnloadGUI(names[idx].c_str()); // UnloadGUI won't unload BkBook
}

//// Constructor /////////////////////////////////////////////////////////////
// The constructor takes in the esHTML source for the journal, along with
// the name of the mipmap to use as the cover of the book. The callback
// key is the keyed object to send event messages to (see <img> tag).

pfJournalBook::pfJournalBook( const char *esHTMLSource, plKey coverImageKey, plKey callbackKey /*= nil*/, 
								const plLocation &hintLoc /* = plLocation::kGlobalFixedLoc */, const char *guiName /* = nil */ )
{
	if (guiName && (strcmp(guiName,"") != 0))
		fCurBookGUI = guiName;
	else
		fCurBookGUI = "BkBook";
	if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end())
	{
		fBookGUIs[fCurBookGUI] = TRACKED_NEW pfBookData(fCurBookGUI.c_str());
		hsgResMgr::ResMgr()->NewKey(fCurBookGUI.c_str(),fBookGUIs[fCurBookGUI],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation());
		fBookGUIs[fCurBookGUI]->LoadGUI();
	}
	
	fCurrentPage = 0;
	fLastPage = -1;
	fCoverMipKey = coverImageKey;
	fCoverFromHTML = false;
	fCallbackKey = callbackKey;
	fWidthScale = fHeightScale = 0.f;
	fPageTMargin = fPageLMargin = fPageBMargin = fPageRMargin = 16;
	fAllowTurning = true;
	fAreWeShowing = false;
	fCoverTint.Set( 0.f, 0.f, 0.f, 1.f );
	fTintFirst = true;
	fTintCover = false;
	fAreEditing = false;
	fWantEditing = false;
	fDefLoc = hintLoc;

	wchar_t *wESHTMLSource = hsStringToWString(esHTMLSource);
	fUncompiledSource = wESHTMLSource;
	ICompileSource( wESHTMLSource, hintLoc );
	delete [] wESHTMLSource;
}

pfJournalBook::pfJournalBook( const wchar_t *esHTMLSource, plKey coverImageKey, plKey callbackKey /*= nil*/, 
								const plLocation &hintLoc /* = plLocation::kGlobalFixedLoc */, const char *guiName /* = nil */ )
{
	if (guiName && (strcmp(guiName,"") != 0))
		fCurBookGUI = guiName;
	else
		fCurBookGUI = "BkBook";
	if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end())
	{
		fBookGUIs[fCurBookGUI] = TRACKED_NEW pfBookData(fCurBookGUI.c_str());
		hsgResMgr::ResMgr()->NewKey(fCurBookGUI.c_str(),fBookGUIs[fCurBookGUI],pfGameGUIMgr::GetInstance()->GetKey()->GetUoid().GetLocation());
		fBookGUIs[fCurBookGUI]->LoadGUI();
	}
	
	fCurrentPage = 0;
	fLastPage = -1;
	fCoverMipKey = coverImageKey;
	fCoverFromHTML = false;
	fCallbackKey = callbackKey;
	fWidthScale = fHeightScale = 0.f;
	fPageTMargin = fPageLMargin = fPageBMargin = fPageRMargin = 16;
	fAllowTurning = true;
	fAreWeShowing = false;
	fCoverTint.Set( 1.f, 1.f, 1.f, 1.f );
	fTintFirst = true;
	fTintCover = false;
	fAreEditing = false;
	fWantEditing = false;
	fDefLoc = hintLoc;
	fUncompiledSource = esHTMLSource;

	ICompileSource( esHTMLSource, hintLoc );
}

pfJournalBook::~pfJournalBook()
{
	if (fBookGUIs.find(fCurBookGUI) != fBookGUIs.end()) // it might have been deleted before we got here
		if( fBookGUIs[fCurBookGUI] && fBookGUIs[fCurBookGUI]->CurBook() == this )
			Hide();

	IFreeSource();
}

//// MsgReceive //////////////////////////////////////////////////////////////

hsBool	pfJournalBook::MsgReceive( plMessage *pMsg )
{
	return hsKeyedObject::MsgReceive( pMsg );
}

void	pfJournalBook::SetGUI( const char *guiName )
{
	if (guiName && (strcmp(guiName,"") != 0))
		fCurBookGUI = guiName;
	if (fBookGUIs.find(fCurBookGUI) == fBookGUIs.end())
		fCurBookGUI = "BkBook"; // requested GUI isn't loaded, so use default GUI
	SetEditable(fWantEditing); // make sure that if we want editing, to set it
	ICompileSource(fUncompiledSource.c_str(), fDefLoc); // recompile the source to be safe
}

//// Show ////////////////////////////////////////////////////////////////////
// Shows the book, optionally starting open or closed

void	pfJournalBook::Show( hsBool startOpened /*= false */)
{
	fBookGUIs[fCurBookGUI]->StartedOpen(startOpened);
	fBookGUIs[fCurBookGUI]->CurBook(this);
	fBookGUIs[fCurBookGUI]->SetCurrSize(fWidthScale, fHeightScale);
	ILoadAllImages( false );

	hsGMaterial *cover = fBookGUIs[fCurBookGUI]->CoverMaterial();
	if( cover != nil )
	{
		hsTArray<plLayerInterface*> layers;
		plMipmap *mip = fCoverMipKey ? plMipmap::ConvertNoRef( fCoverMipKey->ObjectIsLoaded() ) : nil;
		if( mip != nil )
		{
			layers.Append(IMakeBaseLayer(mip));

			int i;
			for (i=0; i<fCoverDecals.GetCount(); i++)
			{
				if (fCoverDecals[i]->fType == pfEsHTMLChunk::kDecal)
				{
					plMipmap *decal = plMipmap::ConvertNoRef( fCoverDecals[i]->fImageKey != nil ? fCoverDecals[i]->fImageKey->ObjectIsLoaded() : nil );
					if (decal != nil)
						layers.Append(IMakeDecalLayer(fCoverDecals[i],decal,mip));
				}
				else
				{
					// it's a cover movie, not a decal, so we make a layer, thinking it's at 0,0 and a left map (which gives us the results we want)
					plLayerBink *movieLayer = IMakeMovieLayer(fCoverDecals[i],0,0,mip,pfJournalDlgProc::kTagLeftDTMap,false);
					loadedMovie *movie = TRACKED_NEW loadedMovie;
					movie->movieLayer = movieLayer;
					movie->movieChunk = fCoverDecals[i];
					fLoadedMovies.Append(movie);
					layers.Append(plLayerInterface::ConvertNoRef(movieLayer));
					fVisibleLinks.Reset(); // remove any links that the make movie layer might have added, since a cover movie can't link
				}
			}
			ISetDecalLayers(cover,layers);
		}
		else
		{
			layers.Append(IMakeBaseLayer(fBookGUIs[fCurBookGUI]->DefaultCover()));
			ISetDecalLayers(cover,layers);
		}
		// release our ref on the cover layers since the material will take care of them now
		int i;
		for (i=0; i<layers.GetCount(); i++)
			GetKey()->Release(layers[i]->GetKey());
	}

//	fInstance->IPlayBookCloseAnim( !startOpened, true );
	fBookGUIs[fCurBookGUI]->TurnPageButton()->SetVisible( false );
	ITriggerCloseWithNotify( !startOpened, true );
}

//// IFinishShow /////////////////////////////////////////////////////////////
// Finish showing the book, due to the animation being done seeking

void	pfJournalBook::IFinishShow( hsBool startOpened )
{
	fBookGUIs[fCurBookGUI]->Dialog()->Show();

	fAreWeShowing = true;

	if( startOpened )
	{
		// Render initial pages
		fCurrentPage = 0;
		fVisibleLinks.Reset();
		IRenderPage( 0, pfJournalDlgProc::kTagLeftDTMap );
		IRenderPage( 1, pfJournalDlgProc::kTagRightDTMap );

		fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides );
	}

	ISendNotify( kNotifyShow );
}

//// Hide ////////////////////////////////////////////////////////////////////

void	pfJournalBook::Hide( void )
{
	if (fBookGUIs[fCurBookGUI])
	{
		// we can only hide our own dialog
		if (fBookGUIs[fCurBookGUI]->CurBook() == this )
		{
			if (fBookGUIs[fCurBookGUI]->Dialog())
				fBookGUIs[fCurBookGUI]->Dialog()->Hide();
			fBookGUIs[fCurBookGUI]->CurBook(nil);
			ISendNotify( kNotifyHide );
			ILoadAllImages( true );
			// purge the dynaTextMaps, we're done with them for now
			IPurgeDynaTextMaps();
			// nuke the movies so they don't stay in memory (they're big!)
			int i;
			for( i = 0; i < fLoadedMovies.GetCount(); i++ )
			{
				plLayerBink *movie = fLoadedMovies[ i ]->movieLayer;
				movie->GetKey()->UnRefObject();
				delete fLoadedMovies[ i ];
			}
			fLoadedMovies.Reset();
		}
	}

}

//// Open ////////////////////////////////////////////////////////////////////
// Opens the book, optionally to the given page

void	pfJournalBook::Open( UInt32 startingPage /*= 0 */)
{
	if( !fBookGUIs[fCurBookGUI]->CurrentlyOpen() )
	{
		fBookGUIs[fCurBookGUI]->PlayBookCloseAnim( false );

		// Render initial pages
		fCurrentPage = startingPage;
		fVisibleLinks.Reset();
		IRenderPage( startingPage, pfJournalDlgProc::kTagLeftDTMap );
		IRenderPage( startingPage + 1, pfJournalDlgProc::kTagRightDTMap );

		fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides );
	}
}

//// Close ///////////////////////////////////////////////////////////////////
// Closes the book.

void	pfJournalBook::Close( void )
{
	// don't allow them to close the book if the book started open
	if( !fBookGUIs[fCurBookGUI]->StartedOpen() && fBookGUIs[fCurBookGUI]->CurrentlyOpen() )
	{
		ISendNotify( kNotifyClose );
		fBookGUIs[fCurBookGUI]->PlayBookCloseAnim( true );
	}
}

//// CloseAndHide ////////////////////////////////////////////////////////////
// Closes the book, then calls Hide() once it's done closing

void	pfJournalBook::CloseAndHide( void )
{
	// if they start with the book open, then don't allow them to close it
	if( !fBookGUIs[fCurBookGUI]->StartedOpen() && fBookGUIs[fCurBookGUI]->CurrentlyOpen() )
	{
		// if we are flipping a book then kill the page flipping animation
		if ( fBookGUIs[fCurBookGUI]->CurrentlyTurning() )
			fBookGUIs[fCurBookGUI]->KillPageFlip();
		ISendNotify( kNotifyClose );

		ITriggerCloseWithNotify( true, false );

		// Don't hide until we get the callback!
	}
	else
		// Already closed, just hide
		Hide();
}

//// ITriggerCloseWithNotify /////////////////////////////////////////////////
// Close with a notify

void	pfJournalBook::ITriggerCloseWithNotify( hsBool closeNotOpen, hsBool immediate )
{
	// Disable the book cover button if we're opening, enable otherwise
	fBookGUIs[fCurBookGUI]->CoverButton()->SetEnabled( closeNotOpen );

	// Do the animation manually so we can get a callback
	fBookGUIs[fCurBookGUI]->CurrentlyOpen(!closeNotOpen);

	const hsTArray<plKey> &keys = fBookGUIs[fCurBookGUI]->CoverButton()->GetAnimationKeys();
	const char *animName = fBookGUIs[fCurBookGUI]->CoverButton()->GetAnimationName();

	plAnimCmdMsg *msg = TRACKED_NEW plAnimCmdMsg();
	if( !immediate )
	{
		msg->SetCmd( plAnimCmdMsg::kContinue );
		msg->SetCmd( closeNotOpen ? plAnimCmdMsg::kSetBackwards : plAnimCmdMsg::kSetForewards );
	}
	else
	{
		msg->SetCmd( plAnimCmdMsg::kStop );
		msg->SetCmd( closeNotOpen ? plAnimCmdMsg::kGoToBegin : plAnimCmdMsg::kGoToEnd );
	}
	msg->SetAnimName( animName );
	msg->AddReceivers( keys );
	
	plEventCallbackMsg *eventMsg = TRACKED_NEW plEventCallbackMsg;
	eventMsg->AddReceiver( fBookGUIs[fCurBookGUI]->GetKey() );
	eventMsg->fRepeats = 0;
	eventMsg->fUser = 0x08 | ( closeNotOpen ? 0x00 : 0x01 );	// So we know which this is for
	eventMsg->fEvent = immediate ? ( kSingleFrameEval ) : kStop;
	msg->SetCmd( plAnimCmdMsg::kAddCallbacks );
	msg->AddCallback( eventMsg );
	hsRefCnt_SafeUnRef( eventMsg );

	msg->Send();

	// Trigger the open (or close) sound
	if( !immediate )
		fBookGUIs[fCurBookGUI]->CoverButton()->PlaySound(closeNotOpen ? pfGUICheckBoxCtrl::kMouseUp : pfGUICheckBoxCtrl::kMouseDown);
}

//// NextPage ////////////////////////////////////////////////////////////////
// Advances forward one page

void	pfJournalBook::NextPage( void )
{
	if( (fBookGUIs[fCurBookGUI]->CurrentlyTurning()) || (!fAllowTurning) || (!fAreWeShowing) )
		return;

	if ((fAreEditing)&&!(fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->ShowingEndOfBuffer())) // we're editing the book, so page turning is different here
	{
		fCurrentPage += 2; // we just go to the next page, an editable book has "infinite" pages anyway

		pfGUIMultiLineEditCtrl *right = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl);
		pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl);
		pfGUIMultiLineEditCtrl *turnFront = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl);
		pfGUIMultiLineEditCtrl *turnBack = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl);
		// re-link the controls with the turn page in the middle
		left->SetNext(turnFront);
		turnBack->SetNext(right);

		// At this point, only the left and right pages are visible, we don't want to actually update anything until the animation
		// starts so that nothing flashes or overdraws.

		fBookGUIs[fCurBookGUI]->StartTriggeredFlip( false );
		fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp );
		ISendNotify(kNotifyNextPage);
	}
	else if( fCurrentPage + 2 <= fLastPage )
	{
		fCurrentPage += 2;
		fVisibleLinks.Reset();

		// Swap the right DT map into the turn page front DTMap, then render
		// the new current page into turn page back and currPage+1 into 
		// the right DTMap
		plDynamicTextMap *turnFront = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnFrontDTMap );
		plDynamicTextMap *right = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagRightDTMap );
		if ( turnFront->IsValid() && right->IsValid() )
		{
			memcpy( turnFront->GetImage(), right->GetImage(), right->GetLevelSize( 0 ) );
			if( turnFront->GetDeviceRef() != nil )
				turnFront->GetDeviceRef()->SetDirty( true );
		}
		// copy the videos over
		IMoveMovies( fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kRightPage), fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnFrontPage) );
		IRenderPage( fCurrentPage, pfJournalDlgProc::kTagTurnBackDTMap );

		// This will fire a callback when it's done that'll let us continue the setup
		fBookGUIs[fCurBookGUI]->StartTriggeredFlip( false );

		// Play us a sound too, if defined on our button
		fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp );

		ISendNotify( kNotifyNextPage );
	}
}

//// PreviousPage ////////////////////////////////////////////////////////////
// Same, only back

void	pfJournalBook::PreviousPage( void )
{
	if(( fBookGUIs[fCurBookGUI]->CurrentlyTurning() )||( !fAllowTurning ))
		return;

	if (fAreEditing) // we're editing the book, so page turning is different here
	{
		if (!(fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->ShowingBeginningOfBuffer())) // make sure we don't flip past the beginning
		{
			if (fCurrentPage >= 2) // this variable can get out of whack if we open the book to a page in the middle
				fCurrentPage -= 2; // just making sure that this doesn't go below zero (and therefore wrap around)
			
			pfGUIMultiLineEditCtrl *right = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl);
			pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl);
			pfGUIMultiLineEditCtrl *turnFront = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl);
			pfGUIMultiLineEditCtrl *turnBack = fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl);
			// adjust the starting position of the left control to the new start
			Int32 newStartLine = left->GetFirstVisibleLine() - ((left->GetNumVisibleLines()-1)*2);
			left->SetGlobalStartLine(newStartLine);
			// re-link the controls with the turn page in the middle
			left->SetNext(turnFront);
			turnBack->SetNext(right);

			// At this point, only the left and right pages are visible, we don't want to actually update anything until the animation
			// starts so that nothing flashes or overdraws.
			
			fBookGUIs[fCurBookGUI]->StartTriggeredFlip( true );
			fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp );
			ISendNotify(kNotifyPreviousPage);
		}
		else
		{
			Close();
		}
	}
	else if( fCurrentPage > 1 )
	{
		fCurrentPage -= 2;
		fVisibleLinks.Reset();

		// Swap the left DT map into the turn page back DTMap, then render
		// the new current page into the left and currPage+1 into 
		// the turn page front DTMap
		plDynamicTextMap *turnBack = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnBackDTMap );
		plDynamicTextMap *left = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap );
		if ( turnBack->IsValid() && left->IsValid() )
		{
			memcpy( turnBack->GetImage(), left->GetImage(), left->GetLevelSize( 0 ) );
			if( turnBack->GetDeviceRef() != nil )
				turnBack->GetDeviceRef()->SetDirty( true );
		}
		// copy the videos over
		IMoveMovies( fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kLeftPage), fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnBackPage) );
		IRenderPage( fCurrentPage + 1, pfJournalDlgProc::kTagTurnFrontDTMap );

		// This will fire a callback when it's done that'll let us continue the setup
		fBookGUIs[fCurBookGUI]->StartTriggeredFlip( true );

		// Play us a sound too, if defined on our button
		fBookGUIs[fCurBookGUI]->TurnPageButton()->PlaySound( pfGUICheckBoxCtrl::kMouseUp );

		ISendNotify( kNotifyPreviousPage );
	}
	else
	{
		Close();
	}
}

//// IFindCurrVisibleLink ////////////////////////////////////////////////////
// Find the current moused link, if any

Int32	pfJournalBook::IFindCurrVisibleLink( hsBool rightNotLeft, hsBool hoverNotUp )
{
	pfGUIClickMapCtrl *ctrl = ( rightNotLeft ) ? fBookGUIs[fCurBookGUI]->RightPageMap() : fBookGUIs[fCurBookGUI]->LeftPageMap();

	hsPoint3 pt = hoverNotUp ? ctrl->GetLastMousePt() : ctrl->GetLastMouseUpPt();

	// This should be 0-1 in the context of the control, so scale to our DTMap size
	plDynamicTextMap *dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( rightNotLeft ? pfJournalDlgProc::kTagRightDTMap : pfJournalDlgProc::kTagLeftDTMap );
	pt.fX *= (hsScalar)dtMap->GetWidth();
	pt.fY *= (hsScalar)dtMap->GetHeight();
	if( rightNotLeft )
	{
		// Clicks on the right side are offsetted in x by the left side's width
		dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap );
		pt.fX += dtMap->GetWidth();
	}

	// Search through the list of visible hotspots
	UInt32 i;
	for( i = 0; i < fVisibleLinks.GetCount(); i++ )
	{
		if( fVisibleLinks[ i ]->fLinkRect.Contains( (Int16)pt.fX, (Int16)pt.fY ) )
		{
			// Found a visible link
			return (Int32)i;
		}
	}

	return -1;
}

//// IHandleLeftSideClick ////////////////////////////////////////////////////

void	pfJournalBook::IHandleLeftSideClick( void )
{
	if( fBookGUIs[fCurBookGUI]->CurrentlyTurning() )
		return;

	Int32 idx = IFindCurrVisibleLink( false, false );
	if( idx != -1 )
	{
		if( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kActAsCB )
			IHandleCheckClick( idx, pfBookData::kLeftSide );
		else
			ISendNotify( kNotifyImageLink, fVisibleLinks[ idx ]->fEventID );
		return;
	}

	// No link found that we're inside of, so just do the default behavior of turning the page
	PreviousPage();
}

void	pfJournalBook::IHandleRightSideClick( void )
{
	if( fBookGUIs[fCurBookGUI]->CurrentlyTurning() )
		return;

	Int32 idx = IFindCurrVisibleLink( true, false );
	if( idx != -1 )
	{
		if( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kActAsCB )
			IHandleCheckClick( idx, pfBookData::kRightSide );
		else
			ISendNotify( kNotifyImageLink, fVisibleLinks[ idx ]->fEventID );
		return;
	}

	// No link found that we're inside of, so just do the default behavior of turning the page
	NextPage();
}

//// IHandleCheckClick ///////////////////////////////////////////////////////
// Process a click on the given "check box" image

void	pfJournalBook::IHandleCheckClick( UInt32 idx, pfBookData::WhichSide which )
{
	// Special processing for checkboxes--toggle our state, switch our opacity
	// and then send a notify about our new state
	hsBool check = ( fVisibleLinks[ idx ]->fFlags & pfEsHTMLChunk::kChecked ) ? false : true;
	if( check )
	{
		fVisibleLinks[ idx ]->fFlags |= pfEsHTMLChunk::kChecked;
//		fVisibleLinks[ idx ]->fCurrOpacity = fVisibleLinks[ idx ]->fMaxOpacity;
	}
	else
	{
		fVisibleLinks[ idx ]->fFlags &= ~pfEsHTMLChunk::kChecked;
//		fVisibleLinks[ idx ]->fCurrOpacity = fVisibleLinks[ idx ]->fMinOpacity;
	}

	// Re-render the page we're on, to show the change in state
	IRenderPage( fCurrentPage + ( ( which == pfBookData::kLeftSide ) ? 0 : 1 ), ( which == pfBookData::kLeftSide ) ? pfJournalDlgProc::kTagLeftDTMap: pfJournalDlgProc::kTagRightDTMap );

	ISendNotify( check ? kNotifyImageLink : kNotifyCheckUnchecked, fVisibleLinks[ idx ]->fEventID );

	// Register for FX processing, so we can fade the checkbox in
	fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() | which ) );
}

//// GoToPage ////////////////////////////////////////////////////////////////
// For completeness...

void	pfJournalBook::GoToPage( UInt32 pageNumber )
{
	// Put us here, but only on an even page (odd pages go on the right, y'know)
	if (pageNumber < fPageStarts.Count())
		fCurrentPage = pageNumber & ~0x00000001;
	else
		fCurrentPage = 0;
	fVisibleLinks.Reset();
	IRenderPage( fCurrentPage, pfJournalDlgProc::kTagLeftDTMap );
	IRenderPage( fCurrentPage + 1, pfJournalDlgProc::kTagRightDTMap );
	fBookGUIs[fCurBookGUI]->UpdatePageCorners( pfBookData::kBothSides );
}

//// SetEditable /////////////////////////////////////////////////////////////

void	pfJournalBook::SetEditable(hsBool editable)
{
	if (fBookGUIs[fCurBookGUI]->IsEditable()) // make sure this GUI supports editing
	{
		fBookGUIs[fCurBookGUI]->EnableEditGUI(editable);
		fAreEditing = editable; // we may be editing the book, so change rendering/page flipping methods
		if (editable)
			fLastPage = 0; // setting this to 0 since editable books don't know what the last page is (it always changes)
	}
	else
		fWantEditing = editable; // we want to edit, but the gui doesn't support it, check again if the GUI changes
};

//// ForceCacheCalculations //////////////////////////////////////////////////
// Just forces a full calc of the cached info

void	pfJournalBook::ForceCacheCalculations( void )
{
	// Make sure our page starts are up-to-snuff, at least to this point
	IRecalcPageStarts( -1 );
}

// Tiny helper to convert hex values the *right* way
static UInt32	IConvertHex( const wchar_t *str )
{
	UInt32 value = 0;
	while( *str != 0 )
	{
		value <<= 4;
		switch( *str )
		{
			case L'0':			value |= 0x0;	break;
			case L'1':			value |= 0x1;	break;
			case L'2':			value |= 0x2;	break;
			case L'3':			value |= 0x3;	break;
			case L'4':			value |= 0x4;	break;
			case L'5':			value |= 0x5;	break;
			case L'6':			value |= 0x6;	break;
			case L'7':			value |= 0x7;	break;
			case L'8':			value |= 0x8;	break;
			case L'9':			value |= 0x9;	break;
			case L'a': case L'A':	value |= 0xa;	break;
			case L'b': case L'B':	value |= 0xb;	break;
			case L'c': case L'C':	value |= 0xc;	break;
			case L'd': case L'D':	value |= 0xd;	break;
			case L'e': case L'E':	value |= 0xe;	break;
			case L'f': case L'F':	value |= 0xf;	break;
		}
		str++;
	}

	return value;
}

//// ICompileSource //////////////////////////////////////////////////////////
// Compiles the given string of esHTML source into our compiled chunk list

hsBool	pfJournalBook::ICompileSource( const wchar_t *source, const plLocation &hintLoc )
{
	IFreeSource();


	pfEsHTMLChunk *chunk, *lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
	const wchar_t	*c, *start;
	wchar_t	name[ 128 ], option[ 256 ];
	float bookWidth=1.0, bookHeight=1.0;
	UInt8 movieIndex = 0; // the index of a movie in the source (used for id purposes)

	plKey anotherKey;


	// Parse our source!
	for( start = c = source; *c != 0; )
	{
		// Are we on a tag?
		UInt8 type = IGetTagType( c );
		if( type != pfEsHTMLChunk::kEmpty )
		{
			// First, end the current paragraph chunk, which is a special case 'cause its 
			// text is defined outside the tag
			if( start == c )
			{
				// No actual text, just delete
				delete lastParChunk;
				lastParChunk = nil;
			}
			else if( lastParChunk != nil )
			{
				UInt32 count = ((UInt32)c - (UInt32)start)/2; // wchar_t is 2 bytes
				
				wchar_t *temp = TRACKED_NEW wchar_t[ count + 1 ];
				wcsncpy( temp, start, count );
				temp[count] = L'\0';
				lastParChunk->fText = temp;
				delete [] temp;

				// Special case to remove any last trailing carriage return
//				if( count > 1 && lastParChunk->fText[ count - 1 ] == '\n' )
//					lastParChunk->fText[ count - 1 ] = 0;

				fHTMLSource.Append( lastParChunk );
			}

			// What chunk are we making now?
			switch( type )
			{
				case pfEsHTMLChunk::kParagraph:
					c += 2;
					chunk = TRACKED_NEW pfEsHTMLChunk( nil );
					chunk->fFlags = IFindLastAlignment();
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"align" ) == 0 )
						{
							if( wcsicmp( option, L"left" ) == 0 )
								chunk->fFlags = pfEsHTMLChunk::kLeft;
							else if( wcsicmp( option, L"center" ) == 0 )
								chunk->fFlags = pfEsHTMLChunk::kCenter;
							else if( wcsicmp( option, L"right" ) == 0 )
								chunk->fFlags = pfEsHTMLChunk::kRight;
						}
					}
					// Append text to this one (don't add to source just yet)
					lastParChunk = chunk;
					break;

				case pfEsHTMLChunk::kImage:
					c += 4;
					chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 );
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"align" ) == 0 )
						{
							chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask;
							if( wcsicmp( option, L"left" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kLeft;
							else if( wcsicmp( option, L"center" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kCenter;
							else if( wcsicmp( option, L"right" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kRight;
						}
						else if( wcsicmp( name, L"src" ) == 0 )
						{
							// Name of mipmap source
							chunk->fImageKey = IGetMipmapKey( option, hintLoc );
						}
						else if( wcsicmp( name, L"link" ) == 0 )
						{
							chunk->fEventID = _wtoi( option );
							chunk->fFlags |= pfEsHTMLChunk::kCanLink;
						}
						else if( wcsicmp( name, L"blend" ) == 0 )
						{
							if( wcsicmp( option, L"alpha" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kBlendAlpha;
						}
						else if( wcsicmp( name, L"pos" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kFloating;

							wchar_t *comma = wcschr( option, L',' );
							if( comma != nil )
							{

								chunk->fAbsoluteY = _wtoi( comma + 1 );
								*comma = 0;
							}
							chunk->fAbsoluteX = _wtoi( option );
						}
						else if( wcsicmp( name, L"glow" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kGlowing;
							chunk->fFlags &= ~pfEsHTMLChunk::kActAsCB;

							char *cOption = hsWStringToString(option);
							char *comma = strchr( cOption, ',' );
							if( comma != nil )
							{
								char *comma2 = strchr( comma + 1, ',' );
								if( comma2 != nil )
								{
									chunk->fMaxOpacity = (hsScalar)atof( comma2 + 1 );
									*comma2 = 0;
								}
								chunk->fMinOpacity = (hsScalar)atof( comma + 1 );
								*comma = 0;
							}
							chunk->fSFXTime = (hsScalar)atof( cOption );
							delete [] cOption;
						}
						else if( wcsicmp( name, L"opacity" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kTranslucent;
							char *cOption = hsWStringToString(option);
							chunk->fCurrOpacity = (hsScalar)atof( cOption );
							delete [] cOption;
						}
						else if( wcsicmp( name, L"check" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kActAsCB;
							chunk->fFlags &= ~pfEsHTMLChunk::kGlowing;

							wchar_t *comma = wcschr( option, L',' );
							if( comma != nil )
							{
								wchar_t *comma2 = wcschr( comma + 1, L',' );
								if( comma2 != nil )
								{
									if( _wtoi( comma2 + 1 ) != 0 )
										chunk->fFlags |= pfEsHTMLChunk::kChecked;
									*comma2 = 0;
								}
								UInt32 c = IConvertHex( comma + 1 );
								if( wcslen( comma + 1 ) <= 6 )
									c |= 0xff000000;	// Add in full alpha if none specified
								chunk->fOffColor.FromARGB32( c );
								*comma = 0;
							}
							UInt32 c = IConvertHex( option );
							if( wcslen( option ) <= 6 )
								c |= 0xff000000;	// Add in full alpha if none specified
							chunk->fOnColor.FromARGB32( c );

							if( chunk->fFlags & pfEsHTMLChunk::kChecked )
								chunk->fCurrColor = chunk->fOnColor;
							else
								chunk->fCurrColor = chunk->fOffColor;
						}
						else if (wcsicmp(name,L"resize")==0)
						{
							if (wcsicmp(option,L"no")==0)
								chunk->fNoResizeImg = true;
						}
					}
					if( chunk->fImageKey != nil )
						fHTMLSource.Append( chunk );
					else
						delete chunk;
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kCover:
					// Don't create an actual chunk for this one, just use the "src" and 
					// grab the mipmap key for our cover
					c += 6;
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"src" ) == 0 )
						{
							// Name of mipmap source
							anotherKey = IGetMipmapKey( option, hintLoc );
							if( anotherKey != nil )
							{
								fCoverMipKey = anotherKey;
								fCoverFromHTML = true;
							}
						}
						if( wcsicmp( name, L"tint" ) == 0 )
						{
							fTintCover = true;
							fCoverTint.FromARGB32( wcstol( option, nil, 16 ) | 0xff000000 );
						}
						if( wcsicmp( name, L"tintfirst" ) == 0 )
						{
							if (wcsicmp(option,L"no")==0)
								fTintFirst = false;
						}
					}
					// Still gotta create a new par chunk
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kPageBreak:
					c += 3;
					chunk = TRACKED_NEW pfEsHTMLChunk();
					while( IGetNextOption( c, name, option ) )
					{
					}
					fHTMLSource.Append( chunk );
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kFontChange:
					c += 5;
					chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0, 0 );
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"style" ) == 0 )
						{
							UInt8 guiFlags = 0;
							if( wcsicmp( option, L"b" ) == 0 )
							{
								chunk->fFlags = pfEsHTMLChunk::kFontBold;
								guiFlags = plDynamicTextMap::kFontBold;
							}
							else if( wcsicmp( option, L"i" ) == 0 )
							{
								chunk->fFlags = pfEsHTMLChunk::kFontItalic;
								guiFlags = plDynamicTextMap::kFontItalic;
							}
							else if( wcsicmp( option, L"bi" ) == 0 )
							{
								chunk->fFlags = pfEsHTMLChunk::kFontBold | pfEsHTMLChunk::kFontItalic;
								guiFlags = plDynamicTextMap::kFontBold | plDynamicTextMap::kFontItalic;
							}
							else
								chunk->fFlags = pfEsHTMLChunk::kFontRegular;
							if (fBookGUIs[fCurBookGUI]->IsEditable())
							{
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontStyle(guiFlags);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontStyle(guiFlags);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontStyle(guiFlags);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontStyle(guiFlags);
							}
						}
						else if( wcsicmp( name, L"face" ) == 0 )
						{
							// Name of mipmap source
							chunk->fText = option;
							if (fBookGUIs[fCurBookGUI]->IsEditable())
							{
								char *fontFace = hsWStringToString(option);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontFace(fontFace);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontFace(fontFace);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontFace(fontFace);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontFace(fontFace);
								delete [] fontFace;
							}
						}
						else if( wcsicmp( name, L"size" ) == 0 )
						{
							chunk->fFontSize = _wtoi( option );
							if (fBookGUIs[fCurBookGUI]->IsEditable())
							{
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontSize(chunk->fFontSize);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontSize(chunk->fFontSize);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontSize(chunk->fFontSize);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontSize(chunk->fFontSize);
							}
						}
						else if( wcsicmp( name, L"color" ) == 0 )
						{
							chunk->fColor.FromARGB32( wcstol( option, nil, 16 ) | 0xff000000 );
							chunk->fFlags |= pfEsHTMLChunk::kFontColor;
							if (fBookGUIs[fCurBookGUI]->IsEditable())
							{
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetFontColor(chunk->fColor);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetFontColor(chunk->fColor);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetFontColor(chunk->fColor);
								fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetFontColor(chunk->fColor);
							}
						}
						else if( wcsicmp( name, L"spacing" ) == 0 )
						{
							chunk->fLineSpacing = _wtoi(option);
							chunk->fFlags |= pfEsHTMLChunk::kFontSpacing;
						}
					}
					fHTMLSource.Append( chunk );
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kMargin:
					c += 7;
					while(IGetNextOption(c,name,option))
					{
						if (wcsicmp(name,L"top") == 0)
							fPageTMargin = _wtoi(option);
						else if (wcsicmp(name,L"left") == 0)
							fPageLMargin = _wtoi(option);
						else if (wcsicmp(name,L"bottom") == 0)
							fPageBMargin = _wtoi(option);
						else if (wcsicmp(name,L"right") == 0)
							fPageRMargin = _wtoi(option);
					}
					// set the edit controls to the margins we just set
					if (fBookGUIs[fCurBookGUI]->IsEditable())
					{
						fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagRightEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin);
						fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagLeftEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin);
						fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnFrontEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin);
						fBookGUIs[fCurBookGUI]->GetEditCtrl(pfJournalDlgProc::kTagTurnBackEditCtrl)->SetMargins(fPageTMargin,fPageLMargin,fPageBMargin,fPageRMargin);
					}
					// Start a new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk(nil);
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kBook:
					c += 5;
					// don't actually create a chunk, just set the book size
					while (IGetNextOption(c,name,option))
					{
						if (wcsicmp(name,L"height") == 0)
						{
							char *temp = hsWStringToString(option);
							bookHeight = (float)atof(temp);
							delete [] temp;
						}
						else if (wcsicmp(name,L"width") == 0)
						{
							char *temp = hsWStringToString(option);
							bookWidth = (float)atof(temp);
							delete [] temp;
						}
					}
					fHeightScale = 1.f - bookHeight;
					fWidthScale = 1.f - bookWidth;
					
					// Still gotta create a new par chunk
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kDecal:
					c += 6;
					chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 );
					chunk->fType = pfEsHTMLChunk::kDecal;
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"align" ) == 0 )
						{
							chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask;
							if( wcsicmp( option, L"left" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kLeft;
							else if( wcsicmp( option, L"center" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kCenter;
							else if( wcsicmp( option, L"right" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kRight;
						}
						else if( wcsicmp( name, L"src" ) == 0 )
						{
							// Name of mipmap source
							chunk->fImageKey = IGetMipmapKey( option, hintLoc );
						}
						else if( wcsicmp( name, L"pos" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kFloating;

							wchar_t *comma = wcschr( option, L',' );
							if( comma != nil )
							{

								chunk->fAbsoluteY = _wtoi( comma + 1 );
								*comma = 0;
							}
							chunk->fAbsoluteX = _wtoi( option );
						}
						else if (wcsicmp(name,L"resize")==0)
						{
							if (wcsicmp(option,L"no")==0)
								chunk->fNoResizeImg = true;
						}
						else if (wcsicmp(name,L"tint")==0)
						{
							if (wcsicmp(option,L"yes")==0)
								chunk->fTintDecal = true;
						}
					}
					// add it to our cover decals list (this is tag is essentially thrown away as far as the parser cares)
					if( chunk->fImageKey != nil )
						fCoverDecals.Append( chunk );
					else
						delete chunk;
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;

				case pfEsHTMLChunk::kMovie:
					c += 6;
					chunk = TRACKED_NEW pfEsHTMLChunk( nil, 0 );
					chunk->fType = pfEsHTMLChunk::kMovie;
					while( IGetNextOption( c, name, option ) )
					{
						if( wcsicmp( name, L"align" ) == 0 )
						{
							chunk->fFlags &= ~pfEsHTMLChunk::kAlignMask;
							if( wcsicmp( option, L"left" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kLeft;
							else if( wcsicmp( option, L"center" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kCenter;
							else if( wcsicmp( option, L"right" ) == 0 )
								chunk->fFlags |= pfEsHTMLChunk::kRight;
						}
						else if( wcsicmp( name, L"src" ) == 0 )
						{
							chunk->fText = option;
						}
						else if( wcsicmp( name, L"link" ) == 0 )
						{
							chunk->fEventID = _wtoi( option );
							chunk->fFlags |= pfEsHTMLChunk::kCanLink;
						}
						else if( wcsicmp( name, L"pos" ) == 0 )
						{
							chunk->fFlags |= pfEsHTMLChunk::kFloating;

							wchar_t *comma = wcschr( option, L',' );
							if( comma != nil )
							{

								chunk->fAbsoluteY = _wtoi( comma + 1 );
								*comma = 0;
							}
							chunk->fAbsoluteX = _wtoi( option );
						}
						else if (wcsicmp(name,L"resize")==0)
						{
							if (wcsicmp(option,L"no")==0)
								chunk->fNoResizeImg = true;
						}
						else if (wcsicmp(name,L"oncover")==0)
						{
							if (wcsicmp(option,L"yes")==0)
								chunk->fOnCover = true;
						}
						else if (wcsicmp(name,L"loop")==0)
						{
							if (wcsicmp(option,L"no")==0)
								chunk->fLoopMovie = false;
						}
					}
					chunk->fMovieIndex = movieIndex;
					movieIndex++;
					if (chunk->fOnCover)
					{
						if( chunk->fText != L"" )
							fCoverDecals.Append( chunk );
						else
							delete chunk;
					}
					else
					{
						if( chunk->fText != L"" )
							fHTMLSource.Append( chunk );
						else
							delete chunk;
					}
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;
					
				case pfEsHTMLChunk::kEditable:
					c += 9;
					SetEditable(true);
					chunk = TRACKED_NEW pfEsHTMLChunk();
					while( IGetNextOption( c, name, option ) )
					{
					}
					fHTMLSource.Append( chunk );
					// Start new paragraph chunk after this one
					lastParChunk = TRACKED_NEW pfEsHTMLChunk( nil );
					lastParChunk->fFlags = IFindLastAlignment();
					break;
			}

			start = c;
		}
		else
		{
			// Keep looking
			c++;
		}
	}

	// Final bit goes into the last paragraph chunk we had
	if( start == c )
	{
		// No actual text, just delete
		delete lastParChunk;
		lastParChunk = nil;
	}
	else if( lastParChunk != nil )
	{
		UInt32 count = (UInt32)c - (UInt32)start;
		
		wchar_t *temp = TRACKED_NEW wchar_t[ count + 1 ];
		wcsncpy( temp, start, count + 1 );
		lastParChunk->fText = temp;
		delete [] temp;
		
		// Special case to remove any last trailing carriage return
//		if( count > 1 && lastParChunk->fText[ count - 1 ] == '\n' )
//			lastParChunk->fText[ count - 1 ] = 0;

		fHTMLSource.Append( lastParChunk );
	}

	// Reset a few
	fPageStarts.Reset();
	fPageStarts.Append( 0 );
	if (fAreEditing)
		fLastPage = 0;
	else
		fLastPage = -1;
	
	return true;
}

UInt8	pfJournalBook::IGetTagType( const wchar_t *string )
{
	if( string[ 0 ] != '<' )
		return pfEsHTMLChunk::kEmpty;

	struct TagRec
	{
		const wchar_t *fTag;
		UInt8		fType;
	} tags[] = { { L"p", pfEsHTMLChunk::kParagraph },
				{ L"img", pfEsHTMLChunk::kImage },
				{ L"pb", pfEsHTMLChunk::kPageBreak },
				{ L"font", pfEsHTMLChunk::kFontChange },
				{ L"margin", pfEsHTMLChunk::kMargin },
				{ L"cover", pfEsHTMLChunk::kCover },
				{ L"book", pfEsHTMLChunk::kBook },
				{ L"decal", pfEsHTMLChunk::kDecal },
				{ L"movie", pfEsHTMLChunk::kMovie },
				{ L"editable", pfEsHTMLChunk::kEditable },
				{ nil, pfEsHTMLChunk::kEmpty } };
				

	UInt32 i;
	for( i = 0; tags[ i ].fTag != nil; i++ )
	{
		if( wcsnicmp( string + 1, tags[ i ].fTag, wcslen( tags[ i ].fTag ) ) == 0 )
		{
			// Found tag--but only space or end tag marker allowed afterwards
			char end = (char)string[ wcslen( tags[ i ].fTag ) + 1 ];
			if( end == '>' || end == ' ' )
				return tags[ i ].fType;
		}
	}

	return pfEsHTMLChunk::kEmpty;
}

hsBool	pfJournalBook::IGetNextOption( const wchar_t *&string, wchar_t *name, wchar_t *option )
{
	const wchar_t *c;


	// Advance past any white space
	while( *string == L' ' )
		string++;

	if( *string == L'>' )
	{
		string++;
		return false;
	}

	// Advance to =
	c = string;
	while( *string != L'>' && *string != L' ' && *string != L'=' && *string != L'\0' )
		string++;

	if( *string != L'=' )
		return false;

	// Copy name
	UInt32 len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes
	wcsncpy( name, c, len );
	name[len] = L'\0';

	// Find start of option value
	string++;
	while( *string == L' ' )
		string++;

	if( *string == L'\0' || *string == L'>' )
		return false;

	if( *string == L'\"' )
	{
		// Search for other quote
		string++;
		c = string;
		while( *string != L'>' && *string != L'\"' && *string != L'\0' )
			string++;

		len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes
		wcsncpy( option, c, len );
		option[len] = L'\0';
		
		if( *string == L'\"' )
			string++;

		return true;
	}

	// Non-quoted token
	c = string;
	while( *string != L' ' && *string != L'>' && *string != L'\0' )
		string++;

	len = ((UInt32)string - (UInt32)c)/2; // divide length by 2 because each character is two bytes
	wcsncpy( option, c, len );
	option[len] = L'\0';
	
	return true;
}

void	pfJournalBook::IFreeSource( void )
{
	UInt32 i;
	
	for( i = 0; i < fHTMLSource.GetCount(); i++ )
		delete fHTMLSource[ i ];
	fHTMLSource.Reset();

	for( i = 0; i < fCoverDecals.GetCount(); i++ )
		delete fCoverDecals[ i ];
	fCoverDecals.Reset();

	for( i = 0; i < fLoadedMovies.GetCount(); i++ )
	{
		plLayerBink *movie = fLoadedMovies[ i ]->movieLayer;
		movie->GetKey()->UnRefObject();
		delete fLoadedMovies[ i ];
	}
	fLoadedMovies.Reset();
}

//// IGetMipmapKey ///////////////////////////////////////////////////////////
// Looks up the key for a mipmap given the image name. Note that the given
// location is treated as a hint; if the image isn't found in that location,
// the code will attempt to look in the currently loaded age for a matching
// image name.

#ifndef PLASMA_EXTERNAL_RELEASE
#include "plJPEG/plJPEG.h"
#endif

plKey	pfJournalBook::IGetMipmapKey( const wchar_t *name, const plLocation &loc )
{
	char *cName = hsWStringToString(name);
#ifndef PLASMA_EXTERNAL_RELEASE
	if( strchr( cName, '/' ) != nil || strchr( cName, '\\' ) != nil )
	{
		// For internal use only--we allow local path names of JPEG images, to
		// facilitate fast prototyping
		plMipmap *mip = plJPEG::Instance().ReadFromFile( cName );
		hsgResMgr::ResMgr()->NewKey( cName, mip, loc );
		delete [] cName;
		return mip->GetKey();
	}
#endif

	// Try first to find in the given location
	plUoid myUoid( loc, plMipmap::Index(), cName );
	plKey key = hsgResMgr::ResMgr()->FindKey( myUoid );
	if( key != nil )
	{
		delete [] cName;
		return key;
	}


	// Next, try our "global" pre-defined age
	const plLocation &globLoc = plKeyFinder::Instance().FindLocation( "GUI", "BkBookImages" );
	myUoid = plUoid( globLoc, plMipmap::Index(), cName );
	key = hsgResMgr::ResMgr()->FindKey( myUoid );
	if( key != nil )
	{
		delete [] cName;
		return key;
	}

	// Do a search through our current age with just the name given
	if( plNetClientMgr::GetInstance() != nil )
	{
		const char *thisAge = plAgeLoader::GetInstance()->GetCurrAgeDesc().GetAgeName();
		if( thisAge != nil )
		{
			key = plKeyFinder::Instance().StupidSearch( thisAge, nil, plMipmap::Index(), cName, true );
			if( key != nil )
			{
				delete [] cName;
				return key;
			}
		}
	}

	delete [] cName;
	return nil;
}

//// IRenderPage /////////////////////////////////////////////////////////////
//	Takes the given page out of the source and renders it into the specified
//	DTMap (by GUI tag ID). If no page is cached after this one, also updates
//	the various cached info about page endings, etc.

void	pfJournalBook::IRenderPage( UInt32 page, UInt32 whichDTMap, hsBool suppressRendering /*= false*/ )
{
	if (fAreEditing)
		return; // we don't render if we are editing the book
	
	// Grab the DTMap via the GUI system
	plDynamicTextMap *dtMap = fBookGUIs[fCurBookGUI]->GetDTMap( whichDTMap );
	hsAssert( dtMap != nil, "Invalid DT map in IRenderPage()" );

	loadedMovie *movie = nil;
	bool movieAlreadyLoaded = false;

	// Make sure our page starts are up-to-snuff, at least to this point
	IRecalcPageStarts( page );

	// Render!
	hsColorRGBA color;
	color.Set( 0, 0, 0, 0 );
	if( !suppressRendering )
		dtMap->ClearToColor( color );

	hsGMaterial *material = nil;
	if (whichDTMap == pfJournalDlgProc::kTagLeftDTMap)
		material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kLeftPage);
	else if (whichDTMap == pfJournalDlgProc::kTagRightDTMap)
		material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kRightPage);
	else if (whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap)
		material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnFrontPage);
	else if (whichDTMap == pfJournalDlgProc::kTagTurnBackDTMap)
		material = fBookGUIs[fCurBookGUI]->PageMaterial(pfBookData::kTurnBackPage);

	if (material)
	{
		// clear any exiting layers (movies) from the material
		int i;
		for( i = 0; i < material->GetNumLayers(); i++ ) // remove all plLayerBink layers
		{
			plLayerInterface *matLayer = material->GetLayer(i);
			plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer);
			if (bink) // if it was a bink layer
			{
				plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(material->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it
				hsgResMgr::ResMgr()->SendRef(material->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef);
			}
		}
	}

	hsAssert(page < fPageStarts.GetCount(), "UnInitialized page start!");
	if( page <= fLastPage 
		&& page < fPageStarts.GetCount())	// Added this as a crash-prevention bandaid - MT
	{
		UInt32 idx;
		UInt16 width, height, y, x, ascent, lastX, lastY;
		
		UInt8		fontFlags, fontSize;
		const wchar_t *fontFace;
		hsColorRGBA	fontColor;
		Int16		fontSpacing;
		hsBool		needSFX = false;

		// Find and set initial font properties
		IFindFontProps( fPageStarts[ page ], fontFace, fontSize, fontFlags, fontColor, fontSpacing );
		dtMap->SetFont( fontFace, fontSize, fontFlags, false );
		dtMap->SetTextColor( fontColor, true );
		dtMap->SetLineSpacing(fontSpacing);

		for( idx = fPageStarts[ page ], x = (UInt16)fPageLMargin, y = (UInt16)fPageTMargin;
			y < (UInt16)(512 - fPageTMargin - fPageBMargin) && idx < fHTMLSource.GetCount(); idx++ )
		{
			if( fPageStarts.GetCount() > page + 1 && idx == fPageStarts[ page + 1 ] )
				break;	// Just go ahead and break at the start of the next page, since we already found it

			pfEsHTMLChunk *chunk = fHTMLSource[ idx ];

			switch( chunk->fType )
			{
				case pfEsHTMLChunk::kParagraph:				
					if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft )
					{
						dtMap->SetJustify( plDynamicTextMap::kLeftJustify );
						x = (UInt16)fPageLMargin; // reset X if our justification changes
					}
					else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight )
					{
						dtMap->SetJustify( plDynamicTextMap::kRightJustify );
						x = (UInt16)fPageLMargin; // reset X if our justification changes
					}
					else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter )
					{
						dtMap->SetJustify( plDynamicTextMap::kCenter );
						x = (UInt16)fPageLMargin; // reset X if our justification changes
					}

					dtMap->SetFirstLineIndent( (Int16)(x - fPageLMargin) );
					width = (UInt16)(512 - fPageLMargin - fPageRMargin);
					height = (UInt16)(512 - fPageBMargin - y);
					UInt32 lastChar;
					dtMap->CalcWrappedStringSize( chunk->fText.c_str(), &width, &height, &lastChar, &ascent, &lastX, &lastY );
					width = (UInt16)(512 - fPageLMargin - fPageRMargin);
					if( !suppressRendering )
						dtMap->DrawWrappedString( (UInt16)fPageLMargin, y, chunk->fText.c_str(), width, (UInt16)(512 - fPageBMargin - y), &lastX, &lastY );

					if( lastChar == 0 )
					{
						// This paragraph didn't fit on this page at *all*, so just bump it to the next
						// one artificially (the -- is to account for the for loop; see image handling below)
						y += 512;
						if( idx > fPageStarts[ page ] )
							idx--;
						break;
					}
					if( chunk->fText[ lastChar ] != 0 )
					{
						// Didn't get to render the whole paragraph in this go, so we're going to cheat
						// and split the paragraph up into two so that we can handle it properly. Note:
						// this changes the chunk array beyond this point, so we need to invalidate the
						// cache, but that's ok 'cause if we're doing this, it's probably invalid (or empty)
						// anyway
						int fTextLen = chunk->fText.length();
						wchar_t *s = TRACKED_NEW wchar_t[fTextLen+1];
						wcscpy(s,chunk->fText.c_str());
						s[fTextLen] = L'\0';

						// Note: Makes a copy of the string
						pfEsHTMLChunk *c2 = TRACKED_NEW pfEsHTMLChunk( &s[ lastChar ] );
						c2->fFlags = chunk->fFlags;
						fHTMLSource.Insert( idx + 1, c2 );

						// Clip and reallocate so we don't have two copies laying around
						s[ lastChar ] = L'\0';
						chunk->fText = s;
						delete [] s;

						// Invalidate our cache starting with the next page
						if( fPageStarts.GetCount() > page + 1 )
							fPageStarts.SetCount( page + 1 );

						y += 512;
						break;
					}
					
					x = lastX;
					y = (UInt16)(lastY - dtMap->GetCurrFont()->GetAscent());	// Since our text is top-justified

					if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) != pfEsHTMLChunk::kLeft )
					{
						// Ending X is not guaranteed to be anything useful if we're not left justified
						x = (UInt16)fPageLMargin;
					}

					break;

				case pfEsHTMLChunk::kImage:
					{
						plMipmap *mip = plMipmap::ConvertNoRef( chunk->fImageKey != nil ? chunk->fImageKey->ObjectIsLoaded() : nil );
						if( mip != nil )
						{
							// First, determine if we need to be processing FX messages
							if( chunk->fFlags & pfEsHTMLChunk::kGlowing )
								needSFX = true;
							else if( chunk->fFlags & pfEsHTMLChunk::kActAsCB )
							{
								// If our color doesn't match our checked state, we want to be fading it in
								hsColorRGBA &want = ( chunk->fFlags & pfEsHTMLChunk::kChecked ) ? chunk->fOnColor : chunk->fOffColor;
								if( want != chunk->fCurrColor )
									needSFX = true;
							}

							if( chunk->fFlags & pfEsHTMLChunk::kFloating )
							{
								// Floating image, ignore the text flow completely and just splat the image on!
								IDrawMipmap( chunk, chunk->fAbsoluteX, chunk->fAbsoluteY, mip, dtMap, whichDTMap, suppressRendering );
							}
							else
							{
								if( y + mip->GetHeight() >= 512 - fPageBMargin )
								{
									// Mipmap overlaps the bottom of this page, so forcibly break so we'll
									// end up marking the page break here (note that, unlike paragraphs, we
									// can't really break the mipmap into two...well, OK, we could, but it 
									// wouldn't make much sense :)
									y += (UInt16)(mip->GetHeight());

									// Wonderful, the break breaks us from the switch(), which means the for()
									// loops runs once more and increments idx. So this is to counter that.
									// (We better check tho, just to make sure nobody feeds us an extra-large 
									// image and sends us on an infinite loop)
									if( idx > fPageStarts[ page ] )
										idx--;
									break;
								}
								if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft )
									x = (UInt16)fPageLMargin;
								else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight )
									x = (UInt16)(512 - fPageRMargin - mip->GetWidth());
								else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter )
									x = (UInt16)(256 - ( mip->GetWidth() >> 1 ));

								IDrawMipmap( chunk, x, y, mip, dtMap, whichDTMap, suppressRendering );

								y += (UInt16)(mip->GetHeight());
								x = (UInt16)fPageLMargin;
							}
						}
					}
					break;

				case pfEsHTMLChunk::kPageBreak:
					// Time for some creative guesswork. See, if the user put a <pb> in at the end of a page,
					// but coincidentally we already broke to a new page, then the explicit <pb> is redundant,
					// so we don't want it. However, if we're at a new page *because* of a <pb>, then a new <pb>
					// is intentional, so we DO want to process it.
					if( idx == fPageStarts[ page ] )
					{
						// Did we get here b/c the last chunk was a pb?
						if( idx > 0 && fHTMLSource[ idx - 1 ]->fType != pfEsHTMLChunk::kPageBreak )
						{
							// Nope, so we DO want to ignore it!
							continue;
						}
					}
					y = (UInt16)(512 - fPageTMargin - fPageBMargin);
					x = (UInt16)fPageLMargin;
					break;

				case pfEsHTMLChunk::kFontChange:				
					IFindFontProps( idx, fontFace, fontSize, fontFlags, fontColor, fontSpacing );
					dtMap->SetFont( fontFace, fontSize, fontFlags, false );
					dtMap->SetTextColor( fontColor, true );
					dtMap->SetLineSpacing(fontSpacing);
					break;

				case pfEsHTMLChunk::kMovie:
					movieAlreadyLoaded = (IMovieAlreadyLoaded(chunk) != nil); // have we already cached it?
					plLayerBink *movieLayer = IMakeMovieLayer(chunk, x, y, (plMipmap*)dtMap, whichDTMap, suppressRendering);
					if (movieLayer)
					{
						// adjust the starting height of the movie if we are keeping it inline with the text
						UInt32 movieHeight = 0, movieWidth = 0;
						movieHeight = movieLayer->GetHeight();
						movieWidth = movieLayer->GetWidth();
						if(!(chunk->fFlags & pfEsHTMLChunk::kFloating ))
						{
							if( y + movieHeight >= 512 - fPageBMargin )
							{
								// Movie overlaps the bottom of this page, so forcibly break so we'll
								// end up marking the page break here (note that, unlike paragraphs, we
								// can't really break the Movie into two)
								y += (UInt16)movieHeight;

								// Wonderful, the break breaks us from the switch(), which means the for()
								// loops runs once more and increments idx. So this is to counter that.
								// (We better check tho, just to make sure nobody feeds us an extra-large 
								// image and sends us on an infinite loop)
								if( idx > fPageStarts[ page ] )
									idx--;
								break;
							}
							y += (UInt16)movieHeight;
							x = (UInt16)fPageLMargin;
						}
						if (!movieAlreadyLoaded) // if the movie wasn't already cached, cache it
						{
							movie = TRACKED_NEW loadedMovie;
							movie->movieLayer = movieLayer; // save the layer and chunk data
							movie->movieChunk = chunk;
							fLoadedMovies.Append(movie);
							movie = nil;
							movieAlreadyLoaded = false;
						}
						if (material && !suppressRendering)
							material->AddLayerViaNotify(movieLayer);
					}
					break;
			}
		}

		if( fPageStarts.GetCount() <= page + 1 )
			fPageStarts.ExpandAndZero( page + 2 );
		fPageStarts[ page + 1 ] = idx;

		if( idx == fHTMLSource.GetCount() )
			fLastPage = page;

		pfBookData::WhichSide thisWhich = ( whichDTMap == pfJournalDlgProc::kTagRightDTMap ) ? pfBookData::kRightSide : ( whichDTMap == pfJournalDlgProc::kTagLeftDTMap )  ? pfBookData::kLeftSide : pfBookData::kNoSides;
		if( needSFX )
			fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() | thisWhich ) );
		else
			fBookGUIs[fCurBookGUI]->RegisterForSFX( (pfBookData::WhichSide)( fBookGUIs[fCurBookGUI]->CurSFXPages() & ~thisWhich ) );
	}

	if( !suppressRendering )
		dtMap->FlushToHost();
}

//// IMoveMovies /////////////////////////////////////////////////////////////

void	pfJournalBook::IMoveMovies( hsGMaterial *source, hsGMaterial *dest )
{
	hsTArray<plLayerBink*> moviesOnPage;
	if (source && dest)
	{
		// clear any exiting layers (movies) from the material and save them to our local array
		int i;
		for( i = 0; i < source->GetNumLayers(); i++ ) // remove all plLayerBink layers
		{
			plLayerInterface *matLayer = source->GetLayer(i);
			plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer);
			if (bink) // if it was a bink layer
			{
				plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(source->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it
				hsgResMgr::ResMgr()->SendRef(source->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef);
				moviesOnPage.Append(bink);
			}
		}
		// clear the destination's movies (if it has any)
		for( i = 0; i < dest->GetNumLayers(); i++ ) // remove all plLayerBink layers
		{
			plLayerInterface *matLayer = dest->GetLayer(i);
			plLayerBink *bink = plLayerBink::ConvertNoRef(matLayer);
			if (bink) // if it was a bink layer
			{
				plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(dest->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer); // remove it
				hsgResMgr::ResMgr()->SendRef(dest->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef);
			}
		}
		// put the movies we ripped off the old page onto the new one
		for( i = 0; i < moviesOnPage.GetCount(); i++ )
		{
			dest->AddLayerViaNotify(moviesOnPage[i]);
		}
	}
}

//// IDrawMipmap /////////////////////////////////////////////////////////////

void	pfJournalBook::IDrawMipmap( pfEsHTMLChunk *chunk, UInt16 x, UInt16 y, plMipmap *mip, plDynamicTextMap *dtMap, UInt32 whichDTMap, hsBool dontRender )
{
	plMipmap *copy = TRACKED_NEW plMipmap();
	copy->CopyFrom(mip);
	if (chunk->fNoResizeImg)
	{
		// book is NOT square, there is a h/w ratio of 1/0.7
		// calc new size based on how the book has been skewed
		float xScale = (fWidthScale == 0) ? 1 : 1/(1-fWidthScale);
		float yScale = (fHeightScale == 0) ? 1 : 1/(1-fHeightScale);
		yScale *= 0.7; // adjust because the book isn't square
		UInt32 width = (UInt32)(mip->GetWidth()*xScale);
		UInt32 height = (UInt32)(mip->GetHeight()*yScale);
		UInt16 xShift;
		UInt16 yShift;

		if (dtMap->GetWidth() < width) width = dtMap->GetWidth();
		if (dtMap->GetHeight() < height) height = dtMap->GetHeight();

		if (height < mip->GetHeight())
		{
			yShift = (UInt16)((mip->GetHeight()-height)/2);
			if (y+yShift+height > dtMap->GetHeight())
				y = (UInt16)(dtMap->GetHeight()-height);
			else
				y += yShift;
		}
		else
		{
			yShift = (UInt16)((height-mip->GetHeight())/2);
			if (yShift > y)
				y = 0;
			else
				y -= yShift;
		}

		if (width < mip->GetWidth())
		{
			xShift = (UInt16)((mip->GetWidth()-width)/2);
			if (x+xShift+width > dtMap->GetWidth())
				x = (UInt16)(dtMap->GetWidth()-width);
			else
				x += xShift;
		}
		else
		{
			xShift = (UInt16)((width-mip->GetWidth())/2);
			if (xShift > x)
				x = 0;
			else
				x -= xShift;
		}
		
		copy->SetCurrLevel(0); // resize the image so it will look unchanged when rendered on the altered book
		copy->ResizeNicely((UInt16)width,(UInt16)height,plMipmap::kDefaultFilter);
	}
	if( !dontRender )
	{
		plMipmap::CompositeOptions	opts;
		if( chunk->fFlags & pfEsHTMLChunk::kActAsCB )
		{
			opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? 0 : plMipmap::kBlendWriteAlpha;
			opts.fRedTint = chunk->fCurrColor.r;
			opts.fGreenTint = chunk->fCurrColor.g;
			opts.fBlueTint = chunk->fCurrColor.b;
			opts.fOpacity = (UInt8)(chunk->fCurrColor.a * 255.f);
		}
		else 
		{
			if( chunk->fFlags & pfEsHTMLChunk::kGlowing )
				opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? 0 : plMipmap::kMaskSrcAlpha;
			else if (chunk->fFlags & pfEsHTMLChunk::kTranslucent)
				opts.fFlags = plMipmap::kMaskSrcAlpha;
			else
				opts.fFlags = ( chunk->fFlags & pfEsHTMLChunk::kBlendAlpha ) ? plMipmap::kCopySrcAlpha : plMipmap::kForceOpaque;
			opts.fOpacity = (UInt8)(chunk->fCurrOpacity * 255.f);
		}
		dtMap->Composite( copy, x, y, &opts );
	}

	if( chunk->fFlags & pfEsHTMLChunk::kCanLink )
	{
		if( whichDTMap == pfJournalDlgProc::kTagRightDTMap || whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap )
			x += (UInt16)(dtMap->GetWidth());	// Right page rects are offsetted to differentiate

		if (dontRender) // if we aren't rendering then this link isn't visible, but the index still needs to be valid, so give it a rect of 0,0,0,0
			chunk->fLinkRect.Set(0,0,0,0);
		else
			chunk->fLinkRect.Set( x, y, (Int16)(copy->GetWidth()), (Int16)(copy->GetHeight()) );
		fVisibleLinks.Append( chunk );
	}
	delete copy;
}

pfJournalBook::loadedMovie *pfJournalBook::IMovieAlreadyLoaded(pfEsHTMLChunk *chunk)
{
	int i;
	for (i=0; i<fLoadedMovies.GetCount(); i++) // filename and id# must both match
	{
		if ((chunk->fText == fLoadedMovies[i]->movieChunk->fText)&&(chunk->fMovieIndex == fLoadedMovies[i]->movieChunk->fMovieIndex))
			return fLoadedMovies[i];
	}
	return nil;
}

plKey pfJournalBook::GetMovie(UInt8 index)
{
	loadedMovie *movie = IGetMovieByIndex(index);
	if (movie)
		return movie->movieLayer->GetKey();
	return plKey(nil);
}

pfJournalBook::loadedMovie *pfJournalBook::IGetMovieByIndex(UInt8 index)
{
	int i;
	for (i=0; i<fLoadedMovies.GetCount(); i++)
	{
		if (fLoadedMovies[i]->movieChunk->fMovieIndex == index)
			return fLoadedMovies[i];
	}
	return nil;
}

plLayerBink *pfJournalBook::IMakeMovieLayer(pfEsHTMLChunk *chunk, UInt16 x, UInt16 y, plMipmap *baseMipmap, UInt32 whichDTMap, hsBool dontRender)
{
	// see if it's already loaded
	loadedMovie *movie = IMovieAlreadyLoaded(chunk);
	plLayer* layer = nil;
	plLayerBink* movieLayer = nil;
	UInt16 movieWidth=0,movieHeight=0;
	if (movie)
	{
		movieLayer = movie->movieLayer;
		layer = plLayer::ConvertNoRef(movieLayer->BottomOfStack());
		movieWidth = movieLayer->GetWidth();
		movieHeight = movieLayer->GetHeight();
	}
	else
	{
		// Create the layer and register it.

		// We'll need a unique name. This is a hack, but an effective hack.
		static int uniqueSuffix = 0;
		char buff[256];

		sprintf(buff, "%s_%d_ml", GetKey()->GetName(), uniqueSuffix);
		layer = TRACKED_NEW plLayer;
		hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

		sprintf(buff, "%s_%d_m", GetKey()->GetName(), uniqueSuffix++);
		movieLayer = TRACKED_NEW plLayerBink;
		hsgResMgr::ResMgr()->NewKey(buff, movieLayer, GetKey()->GetUoid().GetLocation());
		movieLayer->GetKey()->RefObject(); // we want to own a ref so we can nuke it at will

		movieLayer->AttachViaNotify(layer);

		// Initialize it.
		char *name = hsWStringToString(chunk->fText.c_str());
		movieLayer->SetMovieName(name);
		delete [] name;
		movieLayer->Eval(0,0,0); // set up the movie

		movieWidth = movieLayer->GetWidth();
		movieHeight = movieLayer->GetHeight();

		if (movieHeight == 0 || movieWidth == 0) // problem loading the file
		{
			movieLayer->GetKey()->UnRefObject();
			return nil;
		}
	}

	if (layer)
	{
		layer->InitToDefault();

		layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.0f));
		layer->SetOpacity(1.0f);

		// Blend flags, movies are opaque, but they don't take the whole page, so alphamapping them
		layer->SetBlendFlags(hsGMatState::kBlendAlpha);

		// Movie shouldn't have to ZWrite
		layer->SetZFlags(hsGMatState::kZNoZWrite);

		// No special shading.
		layer->SetShadeFlags(0);

		// Clamp all textures.
		layer->SetClampFlags(hsGMatState::kClampTexture);

		// Draw passes individually.
		layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere);

		// Shared UV coordinates.
		layer->SetUVWSrc(0);

		if( chunk->fFlags & pfEsHTMLChunk::kFloating )
		{
			// Floating movie, ignore the text flow completely and just splat the movie on!
			x = chunk->fAbsoluteX;
			y = chunk->fAbsoluteY;
		}

		if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kLeft )
			x = (UInt16)fPageLMargin;
		else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kRight )
			x = (UInt16)(baseMipmap->GetWidth() - fPageRMargin - movieWidth);
		else if( ( chunk->fFlags & pfEsHTMLChunk::kAlignMask ) == pfEsHTMLChunk::kCenter )
			x = (UInt16)((baseMipmap->GetWidth() >> 1) - (movieWidth >> 1));

		// x and y are in pixels, need to convert to a range of 0 to 1
		float xRel = (float)x/(float)baseMipmap->GetWidth();
		float yRel = (float)y/(float)baseMipmap->GetHeight();

		// Need to convert the scaling to texture space
		float xScale = (float)baseMipmap->GetWidth()/(float)movieWidth;
		float yScale = (float)baseMipmap->GetHeight()/(float)movieHeight;

		if (chunk->fNoResizeImg)
		{
			// book is NOT square, there is a h/w ratio of 1/0.7
			// calc new size based on how the book has been skewed
			xScale *= (fWidthScale == 0) ? 1 : (1-fWidthScale);
			yScale *= (fHeightScale == 0) ? 1 : (1-fHeightScale);
			yScale *= 1/0.7; // adjust because the book isn't square
		}

		hsVector3 scaleVec(xScale,yScale,1), translateVec(-(xRel*xScale),-(yRel*yScale),0);
		hsMatrix44 scaleMat, translateMat;
		scaleMat.MakeScaleMat(&scaleVec);
		translateMat.MakeTranslateMat(&translateVec);

		hsMatrix44 flipMat;
		if (chunk->fOnCover) // cover movies need to be y flipped
		{
			hsVector3 yTransVec(0,-1,0), invertYVec(1,-1,1);
			hsMatrix44 invertY, transY;
			invertY.MakeScaleMat(&invertYVec);
			transY.MakeTranslateMat(&yTransVec);
			flipMat = invertY * transY;
		}
		else // left page movies need to be x flipped
		{
			if ((whichDTMap == pfJournalDlgProc::kTagLeftDTMap) || (whichDTMap == pfJournalDlgProc::kTagTurnBackDTMap))
			{
				hsVector3 xTransVec(-1,0,0), invertXVec(-1,1,1);
				hsMatrix44 invertX, transX;
				invertX.MakeScaleMat(&invertXVec);
				transX.MakeTranslateMat(&xTransVec);
				flipMat = invertX * transX;
			}
			else
				flipMat = hsMatrix44::IdentityMatrix();
		}
		
		hsMatrix44 xfm;
		xfm = translateMat * scaleMat * flipMat;

		layer->SetTransform(xfm);
	}
	if( chunk->fFlags & pfEsHTMLChunk::kCanLink )
	{
		if( whichDTMap == pfJournalDlgProc::kTagRightDTMap || whichDTMap == pfJournalDlgProc::kTagTurnFrontDTMap )
			x += (UInt16)(baseMipmap->GetWidth());	// Right page rects are offsetted to differentiate

		if (dontRender) // if we aren't rendering then this link isn't visible, but the index still needs to be valid, so give it a rect of 0,0,0,0
			chunk->fLinkRect.Set(0,0,0,0);
		else
			chunk->fLinkRect.Set( x, y, movieWidth, movieHeight );
		fVisibleLinks.Append( chunk );
	}

	plAnimTimeConvert &timeConvert = movieLayer->GetTimeConvert();
	timeConvert.SetBegin(0);
	timeConvert.SetEnd(movieLayer->GetLength());
	if (chunk->fLoopMovie)
	{
		timeConvert.SetLoopPoints(0,movieLayer->GetLength());
		timeConvert.Loop();
	}
	timeConvert.Start(); // start the show!

	return movieLayer;
}

plLayerInterface *pfJournalBook::IMakeBaseLayer(plMipmap *image)
{
	// Create the layer and register it.

	// We'll need a unique name. This is a hack, but an effective hack.
	static int uniqueSuffix = 0;
	char buff[256];
	sprintf(buff, "%s_%d", GetKey()->GetName(), uniqueSuffix++);

	plLayer* layer = TRACKED_NEW plLayer;
	hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

	// Initialize it.
	layer->InitToDefault();

	layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
	if (fTintCover)
		layer->SetRuntimeColor(hsColorRGBA().Set(fCoverTint.r, fCoverTint.g, fCoverTint.b, 1.f));
	layer->SetOpacity(1.f);

	// Blend flags, opaque for the bottom layer.
	layer->SetBlendFlags(0);

	// Only the bottom layer writes it's Z value.
	layer->SetZFlags(0);

	// No special shading.
	layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog);

	// Clamp all textures.
	layer->SetClampFlags(hsGMatState::kClampTexture);

	// Draw passes individually.
	layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere);

	// Shared UV coordinates.
	layer->SetUVWSrc(0);

	// Set up the transform.
	hsVector3 yTransVec(0,-1,0), invertYVec(1,-1,1);
	hsMatrix44 xfm, invertY, transY, flipY;
	invertY.MakeScaleMat(&invertYVec);
	transY.MakeTranslateMat(&yTransVec);
	flipY = invertY * transY;
	xfm = flipY;

	layer->SetTransform(xfm);

	// Set the texture (assumes mipmap is non-nil).
	plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
	hsgResMgr::ResMgr()->SendRef(image->GetKey(), refMsg, plRefFlags::kActiveRef);

	return plLayerInterface::ConvertNoRef(layer);
}

plLayerInterface *pfJournalBook::IMakeDecalLayer(pfEsHTMLChunk *decalChunk, plMipmap *decal, plMipmap *baseMipmap)
{
	// Create the layer and register it.

	// We'll need a unique name. This is a hack, but an effective hack.
	static int uniqueSuffix = 0;
	char buff[256];
	sprintf(buff, "%s_%d_d", GetKey()->GetName(), uniqueSuffix++);

	plLayer* layer = TRACKED_NEW plLayer;
	hsgResMgr::ResMgr()->NewKey(buff, layer, GetKey()->GetUoid().GetLocation());

	// Initialize it.
	layer->InitToDefault();

	// tint the layer only if the decal wants to be tinted
	layer->SetAmbientColor(hsColorRGBA().Set(0.f, 0.f, 0.f, 1.f));
	if (decalChunk->fTintDecal)
		layer->SetRuntimeColor(hsColorRGBA().Set(fCoverTint.r, fCoverTint.g, fCoverTint.b, 1.f));
	layer->SetOpacity(1.f);

	// Blend flags, decals are alpha blended.
	layer->SetBlendFlags(hsGMatState::kBlendAlpha);

	// Only the bottom layer writes it's Z value.
	layer->SetZFlags(hsGMatState::kZNoZWrite);

	// No special shading.
	layer->SetShadeFlags(hsGMatState::kShadeReallyNoFog);

	// Clamp all textures.
	layer->SetClampFlags(hsGMatState::kClampTexture);

	// Draw passes individually.
	layer->SetMiscFlags(hsGMatState::kMiscRestartPassHere);

	// Shared UV coordinates.
	layer->SetUVWSrc(0);

	// Set up the transform.
	UInt16 x,y;
	x = decalChunk->fAbsoluteX;
	y = decalChunk->fAbsoluteY;

	if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kLeft)
		x = (UInt16)fPageLMargin;
	else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kRight)
		x = (UInt16)(baseMipmap->GetWidth() - decal->GetWidth());
	else if((decalChunk->fFlags & pfEsHTMLChunk::kAlignMask) == pfEsHTMLChunk::kCenter)
		x = (UInt16)((baseMipmap->GetWidth() >> 1) - (decal->GetWidth() >> 1));

	// x and y are in pixels, need to convert to a range of 0 to 1
	float xRel = (float)x/(float)baseMipmap->GetWidth();
	float yRel = (float)y/(float)baseMipmap->GetHeight();

	// Need to convert the scaling to texture space
	float xScale = (float)baseMipmap->GetWidth()/(float)decal->GetWidth();
	float yScale = (float)baseMipmap->GetHeight()/(float)decal->GetHeight();

	if (decalChunk->fNoResizeImg)
	{
		// book is NOT square, there is a h/w ratio of 1/0.7
		// calc new size based on how the book has been skewed
		xScale *= (fWidthScale == 0) ? 1 : (1-fWidthScale);
		yScale *= (fHeightScale == 0) ? 1 : (1-fHeightScale);
		yScale *= 1/0.7; // adjust because the book isn't square
	}

	hsVector3 scaleVec(xScale,yScale,1), translateVec(-(xRel*xScale),-(yRel*yScale),0), yTransVec(0,-1,0), invertYVec(1,-1,1);
	hsMatrix44 scaleMat, translateMat, invertY, transY;
	scaleMat.MakeScaleMat(&scaleVec);
	translateMat.MakeTranslateMat(&translateVec);
	invertY.MakeScaleMat(&invertYVec);
	transY.MakeTranslateMat(&yTransVec);

	hsMatrix44 xfm, flipY;
	flipY = invertY * transY;
	xfm = translateMat * scaleMat * flipY;

	layer->SetTransform(xfm);

	// Set the texture (assumes mipmap is non-nil).
	plLayRefMsg* refMsg = TRACKED_NEW plLayRefMsg(layer->GetKey(), plRefMsg::kOnCreate, 0, plLayRefMsg::kTexture);
	hsgResMgr::ResMgr()->SendRef(decal->GetKey(), refMsg, plRefFlags::kActiveRef);

	return plLayerInterface::ConvertNoRef(layer);
}

void pfJournalBook::ISetDecalLayers(hsGMaterial *material,hsTArray<plLayerInterface*> layers)
{
	// First, clear out the existing layers.
	int i;
	for( i = material->GetNumLayers()-1; i >= 0; i-- )
	{
		plMatRefMsg* refMsg = TRACKED_NEW plMatRefMsg(material->GetKey(), plRefMsg::kOnRemove, i, plMatRefMsg::kLayer);
		hsgResMgr::ResMgr()->SendRef(material->GetLayer(i)->GetKey(), refMsg, plRefFlags::kActiveRef);
	}

	// Now append our new layers in order.
	for( i = 0; i < layers.GetCount(); i++ )
	{
		material->AddLayerViaNotify(layers[i]);
	}
}

//// IFindFontProps //////////////////////////////////////////////////////////
// Starting at the given chunk, works backwards to determine the full set of current
// font properties at that point, or assigns defaults if none were specified

void	pfJournalBook::IFindFontProps( UInt32 chunkIdx, const wchar_t *&face, UInt8 &size, UInt8 &flags, hsColorRGBA &color, Int16 &spacing )
{
	enum Which
	{
		kFace	= 0x01,
		kSize	= 0x02,
		kFlags	= 0x04,
		kColor	= 0x08,
		kSpacing= 0x10,

		kAllFound = kFace | kSize | kFlags | kColor | kSpacing
	};

	// Start empty
	UInt8	found = 0;

	// Work backwards and fill in our properties
	chunkIdx++;
	do
	{
		chunkIdx--;
		if (fHTMLSource.Count() <= chunkIdx)
			break; // apparently it's sometimes possible for fHTMLSource to be empty (parse errors?)
		pfEsHTMLChunk *chunk = fHTMLSource[ chunkIdx ];

		if( chunk->fType == pfEsHTMLChunk::kFontChange )
		{
			// What do we (still) need?
			if( !( found & kFace ) && chunk->fText != L"" )
			{
				face = chunk->fText.c_str();
				found |= kFace;
			}
			if( !( found & kSize ) && chunk->fFontSize > 0 )
			{
				size = chunk->fFontSize;
				found |= kSize;
			}
			if( !( found & kFlags ) && ( chunk->fFlags & pfEsHTMLChunk::kFontMask ) != 0 )
			{
				flags = 0;
				if( chunk->fFlags & pfEsHTMLChunk::kFontBold )
					flags |= plDynamicTextMap::kFontBold;
				if( chunk->fFlags & pfEsHTMLChunk::kFontItalic )
					flags |= plDynamicTextMap::kFontItalic;

				found |= kFlags;
			}
			if( !( found & kColor ) && ( chunk->fFlags & pfEsHTMLChunk::kFontColor ) )
			{
				color = chunk->fColor;
				found |= kColor;
			}
			if( !( found & kSpacing ) && ( chunk->fFlags & pfEsHTMLChunk::kFontSpacing ) )
			{
				spacing = chunk->fLineSpacing;
				found |= kSpacing;
			}
		}

	} while( chunkIdx != 0 && found != kAllFound );

	// Set any un-found defaults
	if( !( found & kFace ) )
		face = L"Arial";
	if( !( found & kSize ) )
		size = 24;
	if( !( found & kFlags ) )
		flags = 0;
	if( !( found & kColor ) )
		color.Set( 0.f, 0.f, 0.f, 1.f );
	if( !( found & kSpacing ) )
		spacing = 0;
}

//// IFindLastAlignment //////////////////////////////////////////////////////
// Find the last paragraph chunk and thus the last par alignment settings

UInt8	pfJournalBook::IFindLastAlignment( void ) const
{
	Int32 idx;


	for( idx = fHTMLSource.GetCount() - 1; idx >= 0; idx-- )
	{
		if( fHTMLSource[ idx ]->fType == pfEsHTMLChunk::kParagraph && fHTMLSource[ idx ]->fFlags != 0 )
			return (UInt8)(fHTMLSource[ idx ]->fFlags);
	}

	return pfEsHTMLChunk::kLeft;
}

//// IRecalcPageStarts ///////////////////////////////////////////////////////
// Ensures that all the page starts are calced up to the given page (but not including it)

void	pfJournalBook::IRecalcPageStarts( UInt32 upToPage )
{
	UInt32 page;


	// Well, sadly, we can't really calc the page starts without at least a DTMap
	// we can change things on...so we just pick one and render. Note: this WILL
	// trash the font settings on the given DTMap!

	// We assume that the stored page starts we already have are accurate, so
	// just start from there and calc onward

	for( page = fPageStarts.GetCount()-1; page < upToPage && page <= fLastPage; page++ )
	{
		// normally we would surpress rendering the pages, but that seems to have a bug in it
		// that causes lost text that the rendering doesn't have. Since it isn't very costly to
		// actually draw them all (even in large journals), we're just going to do it
		IRenderPage( page, pfJournalDlgProc::kTagTurnBackDTMap, false );
		// Reset any "visible" links since they aren't really visible
		UInt16 i;
		for (i=0; i<fVisibleLinks.Count(); i++)
		{
			fVisibleLinks[i]->fLinkRect.Set(0,0,0,0);
		}
	}
}

//// ISendNotify /////////////////////////////////////////////////////////////
// Just sends out a notify to our currently set receiver key

void	pfJournalBook::ISendNotify( UInt32 type, UInt32 linkID )
{
	if( fCallbackKey != nil )
	{
		plNotifyMsg *pMsg = TRACKED_NEW plNotifyMsg;
		pMsg->AddBookEvent( type, linkID );
		pMsg->SetBCastFlag( plMessage::kNetPropagate, false ); // don't deliver networked!
		pMsg->Send( fCallbackKey );
	}
}

//// SetBookSize /////////////////////////////////////////////////////////////
// Sets the book size scaling. 1,1 would be full size, 0,0 is the smallest size possible
// Note: internally we store these as the seek positions on our animations,
// so the incoming parameters are actually inverse of what we finally want

void	pfJournalBook::SetBookSize( hsScalar width, hsScalar height )
{
	fWidthScale = 1.f - width;
	fHeightScale = 1.f - height;

	if( fBookGUIs[fCurBookGUI]->CurBook() == this )
		fBookGUIs[fCurBookGUI]->SetCurrSize( fWidthScale, fHeightScale );
}

//// ILoadAllImages //////////////////////////////////////////////////////////
// Load (or unload) all the images for the book

void	pfJournalBook::ILoadAllImages( hsBool unload )
{
	UInt32	i;

	// load the cover
	if( fCoverFromHTML && fCoverMipKey != nil )
	{
		if( unload )
			fBookGUIs[fCurBookGUI]->GetKey()->Release( fCoverMipKey );
		else
		{
			plGenRefMsg *ref = TRACKED_NEW plGenRefMsg( fBookGUIs[fCurBookGUI]->GetKey(), plRefMsg::kOnCreate, -1, kRefImage );
			hsgResMgr::ResMgr()->AddViaNotify( fCoverMipKey, ref, plRefFlags::kActiveRef );
		}
	}

	for( i = 0; i < fHTMLSource.GetCount(); i++ )
	{
		if( fHTMLSource[ i ]->fType == pfEsHTMLChunk::kImage && fHTMLSource[ i ]->fImageKey != nil )
		{
			if( unload )
				fBookGUIs[fCurBookGUI]->GetKey()->Release( fHTMLSource[ i ]->fImageKey );
			else
			{
				plGenRefMsg *ref = TRACKED_NEW plGenRefMsg( fBookGUIs[fCurBookGUI]->GetKey(), plRefMsg::kOnCreate, -1, kRefImage );  
				hsgResMgr::ResMgr()->AddViaNotify( fHTMLSource[ i ]->fImageKey, ref, plRefFlags::kActiveRef );
			}
		}
	}
}

void pfJournalBook::IPurgeDynaTextMaps( )
{
	plDynamicTextMap* turnFront = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnFrontDTMap );
	if (turnFront)
		turnFront->PurgeImage();

	plDynamicTextMap* right = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagRightDTMap );
	if (right)
		right->PurgeImage();

	plDynamicTextMap* turnBack = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagTurnBackDTMap );
	if (turnBack)
		turnBack->PurgeImage();

	plDynamicTextMap* left = fBookGUIs[fCurBookGUI]->GetDTMap( pfJournalDlgProc::kTagLeftDTMap );
	if (left)
		left->PurgeImage();

	pfGUIMultiLineEditCtrl *leftEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl );
	if (leftEdit)
		leftEdit->PurgeDynaTextMapImage();

	pfGUIMultiLineEditCtrl *rightEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagRightEditCtrl );
	if (rightEdit)
		rightEdit->PurgeDynaTextMapImage();

	pfGUIMultiLineEditCtrl *turnFrontEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagTurnFrontEditCtrl );
	if (turnFrontEdit)
		turnFrontEdit->PurgeDynaTextMapImage();

	pfGUIMultiLineEditCtrl *turnBackEdit = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagTurnBackEditCtrl );
	if (turnBackEdit)
		turnBackEdit->PurgeDynaTextMapImage();
}

std::string pfJournalBook::GetEditableText()
{
	pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl );
	if (left)
	{
		char *temp = left->GetNonCodedBuffer();
		std::string retVal = temp;
		delete [] temp;
		return retVal;
	}
	return "";
}

void pfJournalBook::SetEditableText(std::string text)
{
	pfGUIMultiLineEditCtrl *left = fBookGUIs[fCurBookGUI]->GetEditCtrl( pfJournalDlgProc::kTagLeftEditCtrl );
	if (left)
	{
		left->SetBuffer(text.c_str());
		left->ForceUpdate();
	}
}