/*
   BobToolz plugin for GtkRadiant
   Copyright (C) 2001 Gordon Biggans

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

   This library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "StdAfx.h"

#include "funchandlers.h"

#include "IntersectDialog.h"
#include "PolygonDialog.h"
#include "StairDialog.h"
#include "DoorDialog.h"
#include "IntersectInfoDialog.h"
#include "BrushCheckDialog.h"
#include "AutoCaulkDialog.h"
#include "AutoCaulkStartDialog.h"
#include "TextureResetDialog.h"
#include "pathplotterdialog.h"

#include "DEntity.h"
#include "shapes.h"
#include "lists.h"
#include "misc.h"
#include "DShape.h"

// for autocaulk
std::list<Str> exclusionList;       // whole brush exclusion
std::list<Str> exclusionList_Face;  // single face exclusion

BOOL el1Loaded;
BOOL el2Loaded;

DBobView*   g_PathView = NULL;
// -------------

/************************
    Global Variables
************************/

CPolygonDialog polygonDlg;
CIntersectDialog intrDlg;
CStairDialog stairDlg;
CDoorDialog doorDlg;
CAutoCaulkStartDialog autocaulkDlg;
CTextureResetDialog texRstDlg;
CPathPlotterDialog ppDlg;

/************************
    --Main Functions--
************************/

void LoadLists(){
	char buffer[256];

	if ( !el1Loaded ) {
		el1Loaded = LoadExclusionList( GetFilename( buffer, "bt\\bt-el1.txt" ), &exclusionList );
	}
	if ( !el2Loaded ) {
		el2Loaded = LoadExclusionList( GetFilename( buffer, "bt\\bt-el2.txt" ), &exclusionList );
	}
}

void PolygonBuilder( vec3_t vMin, vec3_t vMax ){
	// ensure we have something selected
	if ( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) {
		MessageBox( NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK );
		return;
	}

	// tell Radiant we want to access the selected brushes
	g_FuncTable.m_pfnAllocateSelectedBrushHandles();

	// get handle to size definition brush
	brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle( 0 );
	// cant release until we delete the brush, if we do...

	// ask user for type, size, etc....
	if ( polygonDlg.DoModal() == IDOK ) {
		DShape poly;

		g_FuncTable.m_pfnDeleteBrushHandle( brush );

		if ( polygonDlg.m_bInverse ) {
			poly.BuildInversePrism( vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_bAlignTop );
		}
		else
		{
			if ( polygonDlg.m_bBorder ) {
				poly.BuildBorderedPrism( vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_nBorderSize, polygonDlg.m_bAlignTop );
			}
			else{
				poly.BuildRegularPrism( vMin, vMax, polygonDlg.m_nSideCount, polygonDlg.m_bAlignTop );
			}
		}

		poly.Commit();
	}


	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
}


void IntersectionFinder(){
	if ( intrDlg.DoModal() == IDCANCEL ) {
		return;
	}

	if ( intrDlg.m_nBrushOptions == BRUSH_OPT_SELECTED ) {
		// ensure we have enough brushes selected
		if ( g_FuncTable.m_pfnSelectedBrushCount() < 2 ) {
			MessageBox( NULL, "Invalid number of brushes selected, choose at least 2", "Error", MB_OK );
			return;
		}
	}

	CIntersectInfoDialog*   intrInfoDlg = new CIntersectInfoDialog();
	intrInfoDlg->Create( IDD_INTERSECT_INFO_DIALOG );

	DEntity world;

	switch ( intrDlg.m_nBrushOptions )
	{
	case BRUSH_OPT_SELECTED:
	{
		world.LoadSelectedBrushes( &intrInfoDlg->m_prog1 );
		break;
	}
	case BRUSH_OPT_WHOLE_MAP:
	{
		world.LoadFromEntity( 0, &intrInfoDlg->m_prog1 );
		break;
	}
	}

	world.RemoveNonCheckBrushes( &exclusionList, intrDlg.m_bUseDetail );
	BOOL* pbSelectList;
	if ( intrDlg.m_bDuplicateOnly ) {
		pbSelectList = world.BuildDuplicateList();
	}
	else{
		pbSelectList = world.BuildIntersectList();
	}

	world.SelectBrushes( pbSelectList );

	intrInfoDlg->DestroyWindow();
	delete[] pbSelectList;
}

void StairBuilder( vec3_t vMin, vec3_t vMax ){
	// ensure we have something selected
	if ( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) {
		MessageBox( NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK );
		return;
	}

	// tell Radiant we want to access the selected brushes
	g_FuncTable.m_pfnAllocateSelectedBrushHandles();

	// get handle to size definition brush
	brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle( 0 );
	// cant release until we delete the brush, if we do...


	// ask user for type, size, etc....
	if ( stairDlg.DoModal() == IDOK ) {

		// calc brush size
		vec3_t size;
		_VectorSubtract( vMax, vMin, size );


		if ( ( (int)size[2] % stairDlg.m_nStairHeight ) != 0 ) {
			// stairs must fit evenly into brush
			MessageBox( NULL, "Invalid stair height\nHeight of block must be divisable by stair height", "Error", MB_OK );
		}
		else
		{

			// Remove Size Brush
			g_FuncTable.m_pfnDeleteBrushHandle( brush );


			// Get Step Count, Direction of Stairs, Stair Style
			int numSteps = (int)size[2] / stairDlg.m_nStairHeight;
			int direction = stairDlg.m_StairDir;
			int style = stairDlg.m_StairStyle;

			if ( stairDlg.m_StairStyle == STYLE_CORNER ) {
				BuildCornerStairs( vMin, vMax, numSteps, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture );
			}
			else
			{
				// Get Step Dimensions
				float stairHeight = (float)stairDlg.m_nStairHeight;
				float stairWidth;
				if ( ( direction == MOVE_EAST ) || ( direction == MOVE_WEST ) ) {
					stairWidth = ( size[0] ) / numSteps;
				}
				else{
					stairWidth = ( size[1] ) / numSteps;
				}


				// Build Base For Stair (bob's style)
				if ( style == STYLE_BOB ) {
					Build_Wedge( direction, vMin, vMax, TRUE );
				}


				// Set First Step Starting Position
				vMax[2] = vMin[2] + stairHeight;
				SetInitialStairPos( direction, vMin, vMax, stairWidth );


				// Build The Steps
				for ( int i = 0; i < numSteps; i++ )
				{
					if ( style == STYLE_BOB ) {
						Build_StairStep_Wedge( direction, vMin, vMax, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture, stairDlg.m_bDetail );
					}
					else if ( style == STYLE_ORIGINAL ) {
						Build_StairStep( vMin, vMax, "textures/common/caulk", (LPCTSTR)stairDlg.m_riserTexture, direction );
					}

					// get step into next position
					MoveBlock( direction, vMin, vMax, stairWidth );
					vMax[2] += stairHeight;
					if ( style == STYLE_BOB ) {
						vMin[2] += stairHeight; // wedge bottom must be raised
					}
				}
			}
		}
	}

	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
}

void DoorBuilder( vec3_t vMin, vec3_t vMax ){
	// ensure we have something selected
	if ( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) {
		MessageBox( NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK );
		return;
	}

	// tell Radiant we want to access the selected brushes
	g_FuncTable.m_pfnAllocateSelectedBrushHandles();

	// get handle to size definition brush
	brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle( 0 );
	// cant release until we delete the brush, if we do...



	strcpy( doorDlg.m_fbTextureName.GetBuffer( 256 ), g_FuncTable.m_pfnGetCurrentTexture() );

	if ( doorDlg.DoModal() == IDOK ) {
		g_FuncTable.m_pfnDeleteBrushHandle( brush );

		BuildDoorsX2( vMin, vMax,
					  doorDlg.m_bSclMainHor, doorDlg.m_bSclMainVert,
					  doorDlg.m_bSclTrimHor, doorDlg.m_bSclTrimVert,
					  (LPCTSTR)doorDlg.m_fbTextureName,
					  (LPCTSTR)doorDlg.m_trimTextureName,
					  doorDlg.m_doorDirection );
	}

	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
}

void FixBrushes(){
	DEntity world;

	CIntersectInfoDialog*   intrInfoDlg = new CIntersectInfoDialog();
	intrInfoDlg->Create( IDD_INTERSECT_INFO_DIALOG );

	world.LoadFromEntity( 0, &intrInfoDlg->m_prog1 );

	intrInfoDlg->DestroyWindow();

	CBrushCheckDialog*  chkDlg = new CBrushCheckDialog();
	chkDlg->Create( IDD_BRUSHCHECKER_DIALOG );

	int count = world.FixBrushes( TRUE, &chkDlg->m_prog1 );

	chkDlg->DestroyWindow();

	Sys_Printf( "%i invalid/duplicate planes removed\n", count );
}

void AutoCaulk(){
	UndoableCommand undo( "bobToolz.autoCaulk" );

	if ( !el1Loaded ) {
		autocaulkDlg.m_Warning1 = "WARNING: Brush exclusion list not found\n, ALL BRUSHES WILL BE USED";
	}

	if ( autocaulkDlg.DoModal() == IDCANCEL ) {
		return;
	}

	if ( autocaulkDlg.m_nMode == MODE_AC_BUILD_MINI_PRT ) {
		BuildMiniPrt( &exclusionList );
		return;
	}

	CAutoCaulkDialog* acDlg = new CAutoCaulkDialog;
	acDlg->Create( IDD_AUTOCAULK_DIALOG );

	char filename[1204];

	if ( autocaulkDlg.m_nMode == MODE_AC_NORMAL ) {
		char* rad_filename = g_BSPTable.m_pfnGetMapName();
		if ( !rad_filename ) {
			MessageBox( NULL, "An Error Occurred While Trying To Get The Map Filename", "Error", MB_OK );
			acDlg->DestroyWindow();
			return;
		}

		strcpy( filename, rad_filename );

		char* ext = strrchr( filename, '.' ) + 1;
		strcpy( ext, "prt" ); // rename the extension
	}
	else
	{
		IEpair* pEp = g_EpairTable.m_pfnIEpairForProjectKeys();
		char *pn = pEp->ValueForKey( "mapspath" );
		pEp->DecRef();

		strcpy( filename, pn );
		strcat( filename, "/ac_prt.prt" );
	}

	DEntity portals;
	if ( !portals.LoadFromPrt( filename, &acDlg->m_prog1 ) ) {
		MessageBox( NULL, "Failed To Load Portal File", "Error", MB_OK );
		acDlg->DestroyWindow();
		return;
	}
	// load portal file

	CIntersectInfoDialog*   intrInfoDlg = new CIntersectInfoDialog();
	intrInfoDlg->Create( IDD_INTERSECT_INFO_DIALOG );

	DEntity world;

	world.LoadFromEntity( 0, &intrInfoDlg->m_prog1 );
	intrInfoDlg->DestroyWindow();

	if ( autocaulkDlg.m_nMode == MODE_AC_NORMAL ) {
		world.RemoveNonCheckBrushes( &exclusionList, FALSE );
	}
	else{
		world.RemoveNonCheckBrushes( &exclusionList, TRUE );
	}

	world.ResetChecks( &exclusionList_Face );

	int caulkedCount = 0;
	int killCnt = world.AutoCaulk( &portals, autocaulkDlg.m_bAllowDestruction, &caulkedCount, &acDlg->m_prog2 );

	if ( autocaulkDlg.m_bAllowDestruction ) {
		Sys_Printf( "%i unrequired brush(es) killed\n", killCnt );
	}
	Sys_Printf( "%i face(s) caulked\n", caulkedCount );

	acDlg->DestroyWindow();
}

void ResetTextures(){
	texRstDlg.m_TextureName = GetCurrentTexture();
	texRstDlg.m_NewTextureName = GetCurrentTexture();

	if ( texRstDlg.DoModal() == IDCANCEL ) {
		return;
	}

	float fScale[2];
	float fShift[2];
	fScale[1] = texRstDlg.m_fScaleVertical;
	fScale[0] = texRstDlg.m_fScaleHorizontal;

	fShift[1] = (float)texRstDlg.m_nShiftVertical;
	fShift[0] = (float)texRstDlg.m_nShiftHorizontal;

	DEntity world;
	world.LoadFromEntity( 0, NULL );

	if ( texRstDlg.m_bAllTextures ) {
		world.ResetTextures( NULL, fScale, fShift, texRstDlg.m_nRotation, texRstDlg.m_NewTextureName, texRstDlg.m_bOnlyTexture );
	}
	else{
		world.ResetTextures( texRstDlg.m_TextureName, fScale, fShift, texRstDlg.m_nRotation, texRstDlg.m_NewTextureName, texRstDlg.m_bOnlyTexture );
	}
}

void PathPlotter(){
	int ret = ppDlg.DoModal();
	if ( ret == IDCANCEL ) {
		return;
	}
	if ( ret == IDNO ) {
		if ( g_PathView ) {
			delete g_PathView;
		}

		return;
	}

	if ( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) {
		MessageBox( NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK );
		return;
	}

	// tell Radiant we want to access the selected brushes
	g_FuncTable.m_pfnAllocateSelectedBrushHandles();

	// get handle to size definition brush
	brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle( 0 );
	// cant release until we delete the brush, if we do...

	DEntity world;
	world.LoadEPairList( *g_FuncTable.m_pfnGetEntityKeyValList( brush->owner ) );

	DEPair* trigger_ep = world.FindEPairByKey( "targetname" );

	if ( trigger_ep ) {
		if ( !strcmp( world.m_Classname, "trigger_push" ) ) {
			DEPair* target_ep = world.FindEPairByKey( "target" );
			if ( target_ep ) {
				entity_s* entTarget = FindEntityFromTargetname( target_ep->value );
				if ( entTarget ) {
					if ( g_PathView ) {
						delete g_PathView;
					}
					g_PathView = new DBobView;

					g_PathView->Begin( trigger_ep->value, target_ep->value, ppDlg.m_fMultiplier, ppDlg.m_nPoints, ppDlg.m_fGravity, ppDlg.m_bNoUpdate, ppDlg.m_bShowExtra );
				}
				else{
					MessageBox( NULL, "trigger_push target could not be found.", "Error", MB_OK );
				}
			}
			else{
				MessageBox( NULL, "trigger_push has no target.", "Error", MB_OK );
			}
		}
		else{
			MessageBox( NULL, "You must select a 'trigger_push' entity.", "Error", MB_OK );
		}
	}
	else{
		MessageBox( NULL, "Entity must have a targetname", "Error", MB_OK );
	}

	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
}

void PitBuilder( vec3_t vMin, vec3_t vMax ){
	// ensure we have something selected
	if ( g_FuncTable.m_pfnSelectedBrushCount() != 1 ) {
		MessageBox( NULL, "Invalid number of brushes selected, chose 1 only", "Error", MB_OK );
		return;
	}

	// tell Radiant we want to access the selected brushes
	g_FuncTable.m_pfnAllocateSelectedBrushHandles();

	// get handle to size definition brush
	brush_t *brush = (brush_t*)g_FuncTable.m_pfnGetSelectedBrushHandle( 0 );
	// cant release until we delete the brush, if we do...

	DShape pit;

	if ( pit.BuildPit( vMin, vMax ) ) {
		pit.Commit();

		g_FuncTable.m_pfnDeleteBrushHandle( brush );
	}
	else{
		MessageBox( NULL, "Failed To Make Pit\nTry Making The Brush Bigger", "Error", MB_OK );
	}

	g_FuncTable.m_pfnReleaseSelectedBrushHandles();
}
