/*
===========================================================================

Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.

This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").

Doom 3 Source Code 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.

Doom 3 Source Code 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 Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.

In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.

If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.

===========================================================================
*/

#include "tools/edit_gui_common.h"


#include "../../sys/win32/rc/guied_resource.h"
#include "../../renderer/tr_local.h"
#include "../../ui/EditWindow.h"
#include "../../ui/ListWindow.h"
#include "../../ui/BindWindow.h"
#include "../../ui/RenderWindow.h"
#include "../../ui/ChoiceWindow.h"

#include "GEApp.h"

rvGEWindowWrapper::rvGEWindowWrapper( idWindow *window,EWindowType type ) {
	assert(window);

	mWindow = window;
	mFlippedHorz = false;
	mFlippedVert = false;
	mHidden = false;
	mDeleted = false;
	mSelected = false;
	mType = type;
	mExpanded = false;
	mOldVisible = false;
	mMoveable = true;

	if ( dynamic_cast< idEditWindow*>(window) ) {
		mType = WT_EDIT;
	} else if ( dynamic_cast< idListWindow*>(window) ) {
		mType = WT_LIST;
	} else if ( dynamic_cast< idBindWindow*>(window) ) {
		mType = WT_BIND;
	} else if ( dynamic_cast< idRenderWindow*>(window) ) {
		mType = WT_RENDER;
	} else if ( dynamic_cast< idChoiceWindow*>(window) ) {
		mType = WT_CHOICE;
	} else {
		mType = WT_NORMAL;
	}

	// Attach the wrapper to the window by adding a defined variable
	// with the wrappers pointer stuffed into an integer
	idWinInt *var = new idWinInt();
	int x = (int)this;
	*var = x;
	var->SetEval(false);
	var->SetName("guied_wrapper");
	mWindow->AddDefinedVar(var);

	SetStateKey("name", mWindow->GetName(), false);
}

/*
================
rvGEWindowWrapper::GetWrapper

Static method that returns the window wrapper for the given window class
================
*/
rvGEWindowWrapper * rvGEWindowWrapper::GetWrapper( idWindow *window ) {
	idWinInt *var;
	var = dynamic_cast< idWinInt*>(window->GetWinVarByName("guied_wrapper"));
	return var ? ((rvGEWindowWrapper *) (int) (*var)) : NULL;
}

/*
================
rvGEWindowWrapper::UpdateRect

Updates the gui editor's representation of the window rectangle from the
windows rectangle
================
*/
void rvGEWindowWrapper::UpdateRect( void ) {
	idVec4 rect;
	idWinRectangle *winrect;

	winrect = dynamic_cast< idWinRectangle*>(mWindow->GetWinVarByName("rect"));
	assert(winrect);
	rect = winrect->ToVec4();

	mFlippedHorz = false;
	mFlippedVert = false;

	if ( rect[2] < 0 ) {
		mFlippedHorz = true;
		rect[2] *= -1;
	}

	if ( rect[3] < 0 ) {
		mFlippedVert = true;
		rect[3] *= -1;
	}

	mClientRect = rect;

	CalcScreenRect();

	const char *rstr = mState.GetString("rect", "0,0,0,0");
	mMoveable = !IsExpression(rstr);
}

/*
================
rvGEWindowWrapper::CalcScreenRect

Calculates the screen rectangle from the client rectangle by running through
the parent windows and adding their offsets
================
*/
void rvGEWindowWrapper::CalcScreenRect( void ) {
	idWindow *parent;

	mScreenRect = mClientRect;

	if ( NULL != (parent = mWindow->GetParent()) ) {
		rvGEWindowWrapper *wrapper = GetWrapper(parent);
		assert(wrapper);

		mScreenRect[0] += wrapper->GetScreenRect()[0];
		mScreenRect[1] += wrapper->GetScreenRect()[1];
	}

	// Since our screen rectangle has changed we need to tell all our our children to update
	// their screen rectangles as well.
	int i;
	int count;
	rvGEWindowWrapper *pwrapper;

	pwrapper = rvGEWindowWrapper::GetWrapper(mWindow);

	for ( count = pwrapper->GetChildCount(), i = 0; i < count; i ++ ) {
		rvGEWindowWrapper *wrapper;

		wrapper = rvGEWindowWrapper::GetWrapper(pwrapper->GetChild(i));

		// Usually we assert if there is no wrapper but since this method is called
		// when the wrappers are being attached to the windows there may be no wrappers
		// for any of the children.
		if ( !wrapper ) {
			continue;
		}

		wrapper->CalcScreenRect();
	}
}

/*
================
rvGEWindowWrapper::SetRect

Sets the wrapper's rectangle and the attached windows rectangle as well
================
*/
void rvGEWindowWrapper::SetRect( idRectangle &rect ) {
	const char *s;

	mClientRect = rect;
	CalcScreenRect();

	s = va("%d,%d,%d,%d", (int) (rect.x + 0.5f), (int) (rect.y + 0.5f), (int) ((rect.w + 0.5f) * (mFlippedHorz ? -1 : 1)), (int) ((rect.h + 0.5f) * (mFlippedVert ? -1 : 1)));

	mState.Set("rect", s);

	UpdateWindowState();
}

/*
================
rvGEWindowWrapper::SetHidden

Sets the wrappers hidden state
================
*/
void rvGEWindowWrapper::SetHidden( bool h ) {
	mHidden = h;

	UpdateWindowState();
}

/*
================
rvGEWindowWrapper::SetDeleted

Sets the deleted state of the wrapper which in turns sets whether or
not the window is visible
================
*/
void rvGEWindowWrapper::SetDeleted( bool del ) {
	mDeleted = del;

	UpdateWindowState();
}

/*
================
rvGEWindowWrapper::SetState

Sets the window state from the given dictionary
================
*/
void rvGEWindowWrapper::SetState( const idDict &dict ) {
	mState.Clear();
	mState.Copy(dict);

	// Push the window state to the window itself
	UpdateWindowState();

	// Update the internal rectangle since it may have changed in the state
	UpdateRect();
}

/*
================
rvGEWindowWrapper::SetStateKey

Sets the given state key and updates the
================
*/
void rvGEWindowWrapper::SetStateKey( const char *key,const char *value,bool update ) {
	mState.Set(key, value);

	if ( update ) {
		UpdateWindowState();

		// Make sure the rectangle gets updated if its changing
		if ( !idStr::Icmp(key, "rect") ) {
			UpdateRect();
		}
	}
}

/*
================
rvGEWindowWrapper::DeleteStateKey

Sets the given state key and updates the
================
*/
void rvGEWindowWrapper::DeleteStateKey( const char *key ) {
	if ( !idStr::Icmp(key, "rect") || !idStr::Icmp(key, "name") ) {
		return;
	}

	mState.Delete(key);
}

/*
================
rvGEWindowWrapper::UpdateWindowState

Updates the windows real state with wrappers internal state.  Visibility is
handled specially
================
*/
void rvGEWindowWrapper::UpdateWindowState( void ) {
	idStr realVisible;
	bool tempVisible;
	//	int	  i;

	realVisible = mState.GetString("visible", "1");
	tempVisible = atoi(realVisible) ? true : false;
	if ( tempVisible != mOldVisible ) {
		mHidden = !tempVisible;
		mOldVisible = tempVisible;
	}

	tempVisible = !mDeleted && !mHidden;

	// Temporarily change the visible state so we can hide/unhide the window
	mState.Set("visible", tempVisible ? "1" : "0");

	mWindow->UpdateFromDictionary(mState);

	// Put the visible state back to the real value
	mState.Set("visible", realVisible);
}

/*
================
rvGEWindowWrapper::WindowFromPoint

Returns the topmost window under the given point
================
*/
idWindow * rvGEWindowWrapper::WindowFromPoint( float x,float y,bool visibleOnly ) {
	int count;
	int i;
	rvGEWindowWrapper *pwrapper;

	// If the window isnt visible then skip it
	if ( visibleOnly && (mHidden || mDeleted) ) {
		return NULL;
	}

	// Now check our children next
	pwrapper = GetWrapper(mWindow);
	count = pwrapper->GetChildCount();

	for ( i = count - 1; i >= 0; i -- ) {
		rvGEWindowWrapper *wrapper;
		idWindow *child;

		child = pwrapper->GetChild(i);
		assert(child);

		wrapper = rvGEWindowWrapper::GetWrapper(child);
		if ( !wrapper ) {
			continue;
		}

		if ( child = wrapper->WindowFromPoint(x, y) ) {
			return child;
		}
	}

	// We have to check this last because a child could be out outside of the parents
	// rectangle and we still want it selectable.
	if ( !mScreenRect.Contains(x, y) ) {
		return NULL;
	}

	return mWindow;
}

/*
================
rvGEWindowWrapper::StringToWindowType

Converts the given string to a window type
================
*/
rvGEWindowWrapper::EWindowType rvGEWindowWrapper::StringToWindowType( const char *string ) {
	if ( !idStr::Icmp(string, "windowDef") ) {
		return WT_NORMAL;
	} else if ( !idStr::Icmp(string, "editDef") ) {
		return WT_EDIT;
	} else if ( !idStr::Icmp(string, "choiceDef") ) {
		return WT_CHOICE;
	} else if ( !idStr::Icmp(string, "sliderDef") ) {
		return WT_SLIDER;
	} else if ( !idStr::Icmp(string, "bindDef") ) {
		return WT_BIND;
	} else if ( !idStr::Icmp(string, "listDef") ) {
		return WT_LIST;
	} else if ( !idStr::Icmp(string, "renderDef") ) {
		return WT_RENDER;
	} else if ( !idStr::Icmp(string, "htmlDef") ) {
		return WT_HTML;
	}

	return WT_UNKNOWN;
}

/*
================
rvGEWindowWrapper::WindowTypeToString

Converts the given window type to a string
================
*/
const char * rvGEWindowWrapper::WindowTypeToString( EWindowType type ) {
	static const char *typeNames[] = {
		"Unknown", "windowDef", "editDef", "htmlDef", "choiceDef", "sliderDef", "bindDef", "listDef", "renderDef"
	};

	return typeNames[(int) type];
}

/*
================
rvGEWindowWrapper::GetChildCount

Returns the number of children the window being wrapped has
================
*/
int rvGEWindowWrapper::GetChildCount( void ) {
	if ( !CanHaveChildren() ) {
		return 0;
	}

	return mWindow->GetChildCount();
}

/*
================
rvGEWindowWrapper::EnumChildren

Enumerates over the child windows while properly ignoring any that
are not wrapped.
================
*/
bool rvGEWindowWrapper::EnumChildren( PFNENUMCHILDRENPROC proc,void *data ) {
	int count;
	int i;

	if ( !CanHaveChildren() ) {
		return false;
	}

	for ( count = GetChildCount(), i = 0; i < count; i ++ ) {
		if ( !proc(rvGEWindowWrapper::GetWrapper(GetChild(i)), data) ) {
			return false;
		}
	}

	return true;
}

/*
================
rvGEWindowWrapper::CanHaveChildren

Returns true if the window is allowed to have child windows
================
*/
bool rvGEWindowWrapper::CanHaveChildren( void ) {
	if ( mType == WT_HTML || mType == WT_LIST ) {
		return false;
	}

	return true;
}

/*
================
rvGEWindowWrapper::GetDepth

Returns the depth of the wrapped window
================
*/
int rvGEWindowWrapper::GetDepth( void ) {
	idWindow *parent;
	int depth;

	for ( depth = 0, parent = mWindow->GetParent(); parent; parent = parent->GetParent(), depth++ )
		;

	return depth;
}

/*
================
rvGEWindowWrapper::Expand

Expand the window in the navigator and all of its parents too
================
*/
bool rvGEWindowWrapper::Expand( void ) {
	bool result;

	if ( mWindow->GetParent() ) {
		result = rvGEWindowWrapper::GetWrapper(mWindow->GetParent())->Expand();
	} else {
		result = false;
	}

	if ( mExpanded || !CanHaveChildren() || !GetChildCount() ) {
		return result;
	}

	mExpanded = true;

	return true;
}

/*
================
rvGEWindowWrapper::Collapse

Returns the depth of the wrapped window
================
*/
bool rvGEWindowWrapper::Collapse( void ) {
	bool result;

	if ( mWindow->GetParent() ) {
		result = rvGEWindowWrapper::GetWrapper(mWindow->GetParent())->Expand();
	} else {
		result = false;
	}

	if ( !mExpanded ) {
		return result;
	}

	mExpanded = false;

	return true;
}

/*
================
rvGEWindowWrapper::Finish

After a the windwo wrapper is attached to a window and the window is parsed
the finish method is called to finish up any last details
================
*/
void rvGEWindowWrapper::Finish( void ) {
	mOldVisible = ((bool) * dynamic_cast< idWinBool*>(mWindow->GetWinVarByName("visible")));
	mHidden = mOldVisible ? false : true;
	UpdateRect();
}

/*
================
rvGEWindowWrapper::Finish

After a the windwo wrapper is attached to a window and the window is parsed
the finish method is called to finish up any last details
================
*/
bool rvGEWindowWrapper::VerfiyStateKey( const char *name,const char *value,idStr *result ) {
	idStr old;
	bool failed;
	idParser src(value, strlen(value), "", LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT);

	// Save the current value
	old = mState.GetString(name);
	failed = false;

	// Try to parse in the value
	try {
		if ( !mWindow->ParseInternalVar(name, &src) ) {
			// Kill the old register since the parse reg entry will add a new one
			if ( !mWindow->ParseRegEntry(name, &src) ) {
				failed = true;
			}
		}
	} catch ( idException &) {
		failed = true;
	}

	// Restore the old value
	idParser src2(old, old.Length(), "", LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT);
	if ( !mWindow->ParseInternalVar(name, &src2) ) {
		if ( !mWindow->ParseRegEntry(name, &src2) ) {
		}
	}

	// Check to see if the old value matches the new value
	idStr before;
	idStr after;

	before = value;
	before.StripTrailingWhitespace();
	src.GetStringFromMarker(after);
	after.StripTrailingWhitespace();

	if ( result ) {
		*result = after;
	}

	if ( failed || before.Cmp(after) ) {
		return false;
	}

	return true;
}
