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

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 "sys/platform.h"
#include "tools/compilers/aas/AASFile_local.h"

#include "tools/compilers/aas/AASCluster.h"

/*
================
idAASCluster::UpdatePortal
================
*/
bool idAASCluster::UpdatePortal( int areaNum, int clusterNum ) {
	int portalNum;
	aasPortal_t *portal;

	// find the portal for this area
	for ( portalNum = 1; portalNum < file->portals.Num(); portalNum++ ) {
		if ( file->portals[portalNum].areaNum == areaNum ) {
			break;
		}
	}

	if ( portalNum >= file->portals.Num() ) {
		common->Error( "no portal for area %d", areaNum );
		return true;
	}

	portal = &file->portals[portalNum];

	// if the portal is already fully updated
	if ( portal->clusters[0] == clusterNum ) {
		return true;
	}
	if ( portal->clusters[1] == clusterNum ) {
		return true;
	}
	// if the portal has no front cluster yet
	if ( !portal->clusters[0] ) {
		portal->clusters[0] = clusterNum;
	}
	// if the portal has no back cluster yet
	else if ( !portal->clusters[1] )
	{
		portal->clusters[1] = clusterNum;
	}
	else
	{
		// remove the cluster portal flag contents
		file->areas[areaNum].contents &= ~AREACONTENTS_CLUSTERPORTAL;
		return false;
	}

	// set the area cluster number to the negative portal number
	file->areas[areaNum].cluster = -portalNum;

	// add the portal to the cluster using the portal index
	file->portalIndex.Append( portalNum );
	file->clusters[clusterNum].numPortals++;
	return true;
}

/*
================
idAASCluster::FloodClusterAreas_r
================
*/
bool idAASCluster::FloodClusterAreas_r( int areaNum, int clusterNum ) {
	aasArea_t *area;
	aasFace_t *face;
	int faceNum, i;
	idReachability *reach;

	area = &file->areas[areaNum];

	// if the area is already part of a cluster
	if ( area->cluster > 0 ) {
		if ( area->cluster == clusterNum ) {
			return true;
		}
		// there's a reachability going from one cluster to another only in one direction
		common->Error( "cluster %d touched cluster %d at area %d\r\n", clusterNum, file->areas[areaNum].cluster, areaNum );
		return false;
	}

	// if this area is a cluster portal
	if ( area->contents & AREACONTENTS_CLUSTERPORTAL ) {
		return UpdatePortal( areaNum, clusterNum );
	}

	// set the area cluster number
	area->cluster = clusterNum;

	if ( !noFaceFlood ) {
		// use area faces to flood into adjacent areas
		for ( i = 0; i < area->numFaces; i++ ) {
			faceNum = abs(file->faceIndex[area->firstFace + i]);
			face = &file->faces[faceNum];
			if ( face->areas[0] == areaNum ) {
				if ( face->areas[1] ) {
					if ( !FloodClusterAreas_r( face->areas[1], clusterNum ) ) {
						return false;
					}
				}
			}
			else {
				if ( face->areas[0] ) {
					if ( !FloodClusterAreas_r( face->areas[0], clusterNum ) ) {
						return false;
					}
				}
			}
		}
	}

	// use the reachabilities to flood into other areas
	for ( reach = file->areas[areaNum].reach; reach; reach = reach->next ) {
		if ( !FloodClusterAreas_r( reach->toAreaNum, clusterNum) ) {
			return false;
		}
	}

	// use the reversed reachabilities to flood into other areas
	for ( reach = file->areas[areaNum].rev_reach; reach; reach = reach->rev_next ) {
		if ( !FloodClusterAreas_r( reach->fromAreaNum, clusterNum) ) {
			return false;
		}
	}

	return true;
}

/*
================
idAASCluster::RemoveAreaClusterNumbers
================
*/
void idAASCluster::RemoveAreaClusterNumbers( void ) {
	int i;

	for ( i = 1; i < file->areas.Num(); i++ ) {
		file->areas[i].cluster = 0;
	}
}

/*
================
idAASCluster::NumberClusterAreas
================
*/
void idAASCluster::NumberClusterAreas( int clusterNum ) {
	int i, portalNum;
	aasCluster_t *cluster;
	aasPortal_t *portal;

	cluster = &file->clusters[clusterNum];
	cluster->numAreas = 0;
	cluster->numReachableAreas = 0;

	// number all areas in this cluster WITH reachabilities
	for ( i = 1; i < file->areas.Num(); i++ ) {

		if ( file->areas[i].cluster != clusterNum ) {
			continue;
		}

		if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
			continue;
		}

		file->areas[i].clusterAreaNum = cluster->numAreas++;
		cluster->numReachableAreas++;
	}

	// number all portals in this cluster WITH reachabilities
	for ( i = 0; i < cluster->numPortals; i++ ) {
		portalNum = file->portalIndex[cluster->firstPortal + i];
		portal = &file->portals[portalNum];

		if ( !(file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
			continue;
		}

		if ( portal->clusters[0] == clusterNum ) {
			portal->clusterAreaNum[0] = cluster->numAreas++;
		}
		else {
			portal->clusterAreaNum[1] = cluster->numAreas++;
		}
		cluster->numReachableAreas++;
	}

	// number all areas in this cluster WITHOUT reachabilities
	for ( i = 1; i < file->areas.Num(); i++ ) {

		if ( file->areas[i].cluster != clusterNum ) {
			continue;
		}

		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
			continue;
		}

		file->areas[i].clusterAreaNum = cluster->numAreas++;
	}

	// number all portals in this cluster WITHOUT reachabilities
	for ( i = 0; i < cluster->numPortals; i++ ) {
		portalNum = file->portalIndex[cluster->firstPortal + i];
		portal = &file->portals[portalNum];

		if ( file->areas[portal->areaNum].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
			continue;
		}

		if ( portal->clusters[0] == clusterNum ) {
			portal->clusterAreaNum[0] = cluster->numAreas++;
		}
		else {
			portal->clusterAreaNum[1] = cluster->numAreas++;
		}
	}
}

/*
================
idAASCluster::FindClusters
================
*/
bool idAASCluster::FindClusters( void ) {
	int i, clusterNum;
	aasCluster_t cluster;

	RemoveAreaClusterNumbers();

	for ( i = 1; i < file->areas.Num(); i++ ) {
		// if the area is already part of a cluster
		if ( file->areas[i].cluster ) {
			continue;
		}

		// if not flooding through faces only use areas that have reachabilities
		if ( noFaceFlood ) {
			if ( !(file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY)) ) {
				continue;
			}
		}

		// if the area is a cluster portal
		if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
			continue;
		}

		cluster.numAreas = 0;
		cluster.numReachableAreas = 0;
		cluster.firstPortal = file->portalIndex.Num();
		cluster.numPortals = 0;
		clusterNum = file->clusters.Num();
		file->clusters.Append( cluster );

		// flood the areas in this cluster
		if ( !FloodClusterAreas_r( i, clusterNum ) ) {
			return false;
		}

		// number the cluster areas
		NumberClusterAreas( clusterNum );
	}
	return true;
}

/*
================
idAASCluster::CreatePortals
================
*/
void idAASCluster::CreatePortals( void ) {
	int i;
	aasPortal_t portal;

	for ( i = 1; i < file->areas.Num(); i++ ) {
		// if the area is a cluster portal
		if ( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) {
			portal.areaNum = i;
			portal.clusters[0] = portal.clusters[1] = 0;
			portal.maxAreaTravelTime = 0;
			file->portals.Append( portal );
		}
	}
}

/*
================
idAASCluster::TestPortals
================
*/
bool idAASCluster::TestPortals( void ) {
	int i;
	aasPortal_t *portal, *portal2;
	aasArea_t *area, *area2;
	idReachability *reach;
	bool ok;

	ok = true;
	for ( i = 1; i < file->portals.Num(); i++ ) {
		portal = &file->portals[i];
		area = &file->areas[portal->areaNum];

		// if this portal was already removed
		if ( !( area->contents & AREACONTENTS_CLUSTERPORTAL) ) {
			continue;
		}

		// may not removed this portal if it has a reachability to a removed portal
		for ( reach = area->reach; reach; reach = reach->next ) {
			area2 = &file->areas[ reach->toAreaNum ];
			if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
				continue;
			}
			if ( area2->cluster < 0 ) {
				break;
			}
		}
		if ( reach ) {
			continue;
		}

		// may not removed this portal if it has a reversed reachability to a removed portal
		for ( reach = area->rev_reach; reach; reach = reach->rev_next ) {
			area2 = &file->areas[ reach->toAreaNum ];
			if ( area2->contents & AREACONTENTS_CLUSTERPORTAL ) {
				continue;
			}
			if ( area2->cluster < 0 ) {
				break;
			}
		}
		if ( reach ) {
			continue;
		}

		// portal should have two clusters set
		if ( !portal->clusters[0] ) {
			area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
			ok = false;
			continue;
		}
		if ( !portal->clusters[1] ) {
			area->contents &= ~AREACONTENTS_CLUSTERPORTAL;
			ok = false;
			continue;
		}

		// this portal may not have reachabilities to a portal that doesn't seperate the same clusters
		for ( reach = area->reach; reach; reach = reach->next ) {
			area2 = &file->areas[ reach->toAreaNum ];

			if ( !(area2->contents & AREACONTENTS_CLUSTERPORTAL) ) {
				continue;
			}

			if ( area2->cluster > 0 ) {
				area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
				ok = false;
				continue;
			}

			portal2 = &file->portals[ -file->areas[ reach->toAreaNum ].cluster ];

			if ( ( portal2->clusters[0] != portal->clusters[0] && portal2->clusters[0] != portal->clusters[1] ) ||
				( portal2->clusters[1] != portal->clusters[0] && portal2->clusters[1] != portal->clusters[1] ) ) {
				area2->contents &= ~AREACONTENTS_CLUSTERPORTAL;
				ok = false;
				continue;
			}
		}
	}

	return ok;
}

/*
================
idAASCluster::RemoveInvalidPortals
================
*/
void idAASCluster::RemoveInvalidPortals( void ) {
	int i, j, k, face1Num, face2Num, otherAreaNum, numOpenAreas, numInvalidPortals;
	aasFace_t *face1, *face2;

	numInvalidPortals = 0;
	for ( i = 0; i < file->areas.Num(); i++ ) {
		if ( !( file->areas[i].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
			continue;
		}

		numOpenAreas = 0;
		for ( j = 0; j < file->areas[i].numFaces; j++ ) {
			face1Num = file->faceIndex[ file->areas[i].firstFace + j ];
			face1 = &file->faces[ abs(face1Num) ];
			otherAreaNum = face1->areas[ face1Num < 0 ];

			if ( !otherAreaNum ) {
				continue;
			}

			for ( k = 0; k < j; k++ ) {
				face2Num = file->faceIndex[ file->areas[i].firstFace + k ];
				face2 = &file->faces[ abs(face2Num) ];
				if ( otherAreaNum == face2->areas[ face2Num < 0 ] ) {
					break;
				}
			}
			if ( k < j ) {
				continue;
			}

			if ( !( file->areas[otherAreaNum].contents & AREACONTENTS_CLUSTERPORTAL ) ) {
				numOpenAreas++;
			}
		}

		if ( numOpenAreas <= 1 ) {
			file->areas[i].contents &= AREACONTENTS_CLUSTERPORTAL;
			numInvalidPortals++;
		}
	}

	common->Printf( "\r%6d invalid portals removed\n", numInvalidPortals );
}

/*
================
idAASCluster::Build
================
*/
bool idAASCluster::Build( idAASFileLocal *file ) {

	common->Printf( "[Clustering]\n" );

	this->file = file;
	this->noFaceFlood = true;

	RemoveInvalidPortals();

	while( 1 ) {

		// delete all existing clusters
		file->DeleteClusters();

		// create the portals from the portal areas
		CreatePortals();

		common->Printf( "\r%6d", file->portals.Num() );

		// find the clusters
		if ( !FindClusters() ) {
			continue;
		}

		// test the portals
		if ( !TestPortals() ) {
			continue;
		}

		break;
	}

	common->Printf( "\r%6d portals\n", file->portals.Num() );
	common->Printf( "%6d clusters\n", file->clusters.Num() );

	for ( int i = 0; i < file->clusters.Num(); i++ ) {
		common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
	}

	file->ReportRoutingEfficiency();

	return true;
}

/*
================
idAASCluster::BuildSingleCluster
================
*/
bool idAASCluster::BuildSingleCluster( idAASFileLocal *file ) {
	int i, numAreas;
	aasCluster_t cluster;

	common->Printf( "[Clustering]\n" );

	this->file = file;

	// delete all existing clusters
	file->DeleteClusters();

	cluster.firstPortal = 0;
	cluster.numPortals = 0;
	cluster.numAreas = file->areas.Num();
	cluster.numReachableAreas = 0;
	// give all reachable areas in the cluster a number
	for ( i = 0; i < file->areas.Num(); i++ ) {
		file->areas[i].cluster = file->clusters.Num();
		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
			file->areas[i].clusterAreaNum = cluster.numReachableAreas++;
		}
	}
	// give the remaining areas a number within the cluster
	numAreas = cluster.numReachableAreas;
	for ( i = 0; i < file->areas.Num(); i++ ) {
		if ( file->areas[i].flags & (AREA_REACHABLE_WALK|AREA_REACHABLE_FLY) ) {
			continue;
		}
		file->areas[i].clusterAreaNum = numAreas++;
	}
	file->clusters.Append( cluster );

	common->Printf( "%6d portals\n", file->portals.Num() );
	common->Printf( "%6d clusters\n", file->clusters.Num() );

	for ( i = 0; i < file->clusters.Num(); i++ ) {
		common->Printf( "%6d reachable areas in cluster %d\n", file->clusters[i].numReachableAreas, i );
	}

	file->ReportRoutingEfficiency();

	return true;
}
