#include "common.h"

#include "Timer.h"
#include "WaterLevel.h"
#include "ModelIndices.h"
#include "Physical.h"
#include "Vehicle.h"
#include "Floater.h"

cBuoyancy mod_Buoyancy;

float fVolMultiplier = 1.0f;
// amount of boat volume in bounding box
// 1.0-volume is the empty space in the bbox
float fBoatVolumeDistribution[9] = {
	// rear
	0.75f, 0.9f, 0.75f,
	0.95f, 1.0f, 0.95f,
	0.4f, 0.7f, 0.4f
	// bow
};
float fBoatVolumeDistributionCat[9] = {
	0.9f, 0.3f, 0.9f,
	1.0f, 0.5f, 1.0f,
	0.95f, 0.4f, 0.95f
};
float fBoatVolumeDistributionSail[9] = {
	0.55f, 0.95f, 0.55f,
	0.75f, 1.1f, 0.75f,
	0.3f, 0.8f, 0.3f
};
float fBoatVolumeDistributionDinghy[9] = {
	0.65f, 0.85f, 0.65f,
	0.85f, 1.1f, 0.85f,
	0.65f, 0.95f, 0.65f
};
float fBoatVolumeDistributionSpeed[9] = {
	0.7f, 0.9f, 0.7f,
	0.95f, 1.0f, 0.95f,
	0.6f, 0.7f, 0.6f
};

bool
cBuoyancy::ProcessBuoyancy(CPhysical *phys, float buoyancy, CVector *point, CVector *impulse)
{
	m_numSteps = 2.0f;

	if(!CWaterLevel::GetWaterLevel(phys->GetPosition(), &m_waterlevel, phys->bTouchingWater))
		return false;
	m_matrix = phys->GetMatrix();

	PreCalcSetup(phys, buoyancy);
	SimpleCalcBuoyancy();
	float f = CalcBuoyancyForce(phys, point, impulse);
	if(m_isBoat)
		return true;
	return f != 0.0f;
}

bool
cBuoyancy::ProcessBuoyancyBoat(CVehicle *veh, float buoyancy, CVector *point, CVector *impulse, bool bNoTurnForce)
{
	m_numSteps = 2.0f;

	if(!CWaterLevel::GetWaterLevel(veh->GetPosition(), &m_waterlevel, veh->bTouchingWater))
		return false;
	m_matrix = veh->GetMatrix();
	PreCalcSetup(veh, buoyancy);


	float x, y;
	int ix, i;
	tWaterLevel waterPosition;
	CVector waterNormal;

	// Floater is divided into 3x3 parts. Process and sum each of them
	float volDiv = 1.0f/((m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f));
	ix = 0;
	for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){
		i = ix;
		for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){
			CVector waterLevel(x, y, 0.0f);
			FindWaterLevelNorm(m_positionZ, &waterLevel, &waterPosition, &waterNormal);
			switch(veh->GetModelIndex()){
			case MI_RIO:
				fVolMultiplier = fBoatVolumeDistributionCat[i];
				break;
			case MI_SQUALO:
			case MI_SPEEDER:
			case MI_JETMAX:
				fVolMultiplier = fBoatVolumeDistributionSpeed[i];
				break;
			case MI_COASTG:
			case MI_DINGHY:
				fVolMultiplier = fBoatVolumeDistributionDinghy[i];
				break;
			case MI_MARQUIS:
				fVolMultiplier = fBoatVolumeDistributionSail[i];
				break;
			case MI_PREDATOR:
			case MI_SKIMMER:
			case MI_REEFER:
			case MI_TROPIC:
			default:
				fVolMultiplier = fBoatVolumeDistribution[i];
				break;
			}
			if(waterPosition != FLOATER_ABOVE_WATER){
				float volume = SimpleSumBuoyancyData(waterLevel, waterPosition);
				float upImpulse = volume * volDiv * buoyancy * CTimer::GetTimeStep();
				CVector speed = veh->GetSpeed(Multiply3x3(veh->GetMatrix(), CVector(x, y, 0.0f)));
				float damp = 1.0f - DotProduct(speed, waterNormal)*veh->pHandling->fSuspensionDampingLevel;
				float finalImpulse = upImpulse*Max(damp, 0.0f);
				impulse->z += finalImpulse;
				if(!bNoTurnForce)
					veh->ApplyTurnForce(finalImpulse*waterNormal, Multiply3x3(m_matrix, waterLevel));
			}
			i += 3;
		}
		ix++;
	}

	m_volumeUnderWater *= volDiv;

	*point = Multiply3x3(m_matrix, m_impulsePoint);
	return m_isBoat || m_haveVolume;

}

void
cBuoyancy::PreCalcSetup(CPhysical *phys, float buoyancy)
{
	CColModel *colModel;

	m_isBoat = phys->IsVehicle() && ((CVehicle*)phys)->IsBoat();
	colModel = phys->GetColModel();
	m_dimMin = colModel->boundingBox.min;
	m_dimMax = colModel->boundingBox.max;

	if(m_isBoat){
		switch(phys->GetModelIndex()){
		case MI_PREDATOR:
		default:
			m_dimMax.y *= 1.05f;
			m_dimMin.y *= 0.9f;
			break;
		case MI_SPEEDER:
			m_dimMax.y *= 1.25f;
			m_dimMin.y *= 0.83f;
			break;
		case MI_REEFER:
			m_dimMin.y *= 0.9f;
			break;
		case MI_RIO:
			m_dimMax.y *= 0.9f;
			m_dimMin.y *= 0.9f;
			m_dimMax.z += 0.25f;
			m_dimMin.z -= 0.2f;
			break;
		case MI_SQUALO:
			m_dimMax.y *= 0.9f;
			m_dimMin.y *= 0.9f;
			break;
		case MI_TROPIC:
			m_dimMax.y *= 1.3f;
			m_dimMin.y *= 0.82f;
			m_dimMin.z -= 0.2f;
			break;
		case MI_SKIMMER:
			m_dimMin.y = -m_dimMax.y;
			m_dimMax.y *= 1.2f;
			break;
		case MI_COASTG:
			m_dimMax.y *= 1.1f;
			m_dimMin.y *= 0.9f;
			m_dimMin.z -= 0.3f;
			break;
		case MI_DINGHY:
			m_dimMax.y *= 1.3f;
			m_dimMin.y *= 0.9f;
			m_dimMin.z -= 0.2f;
			break;
		case MI_MARQUIS:
			m_dimMax.y *= 1.3f;
			m_dimMin.y *= 0.9f;
			break;
		case MI_JETMAX:
			m_dimMin.y *= 0.9f;
			break;
		}
	}

	m_step = (m_dimMax - m_dimMin)/m_numSteps;

	if(m_step.z > m_step.x && m_step.z > m_step.y){
		m_stepRatio.x = m_step.x/m_step.z;
		m_stepRatio.y = m_step.y/m_step.z;
		m_stepRatio.z = 1.0f;
	}else if(m_step.y > m_step.x && m_step.y > m_step.z){
		m_stepRatio.x = m_step.x/m_step.y;
		m_stepRatio.y = 1.0f;
		m_stepRatio.z = m_step.z/m_step.y;
	}else{
		m_stepRatio.x = 1.0f;
		m_stepRatio.y = m_step.y/m_step.x;
		m_stepRatio.z = m_step.z/m_step.x;
	}

	m_haveVolume = false;
	m_numPartialVolumes = 1.0f;
	m_volumeUnderWater = 0.0f;
	m_impulsePoint = CVector(0.0f, 0.0f, 0.0f);
	m_position = phys->GetPosition();
	m_positionZ = CVector(0.0f, 0.0f, m_position.z);
	m_buoyancy = buoyancy;
	m_waterlevel += m_waterLevelInc;
}

void
cBuoyancy::SimpleCalcBuoyancy(void)
{
	float x, y;
	tWaterLevel waterPosition;

	// Floater is divided into 3x3 parts. Process and sum each of them
	for(x = m_dimMin.x; x <= m_dimMax.x; x += m_step.x){
		for(y = m_dimMin.y; y <= m_dimMax.y; y += m_step.y){
			CVector waterLevel(x, y, 0.0f);
			FindWaterLevel(m_positionZ, &waterLevel, &waterPosition);
			fVolMultiplier = 1.0f;
			if(waterPosition != FLOATER_ABOVE_WATER)
				SimpleSumBuoyancyData(waterLevel, waterPosition);
		}
	}

	m_volumeUnderWater /= (m_dimMax.z - m_dimMin.z)*sq(m_numSteps+1.0f);
}

float
cBuoyancy::SimpleSumBuoyancyData(CVector &waterLevel, tWaterLevel waterPosition)
{
	static float fThisVolume;
	static CVector AverageOfWaterLevel;
	static float fFraction;
	static float fRemainingSlice;

	float submerged = Abs(waterLevel.z - m_dimMin.z);
	// subtract empty space from submerged volume
	fThisVolume = submerged - (1.0f - fVolMultiplier);
	if(fThisVolume < 0.0f)
		return 0.0f;

	if(m_isBoat){
		fThisVolume *= fVolMultiplier;
		fThisVolume = sq(fThisVolume);
	}

	m_volumeUnderWater += fThisVolume;

	AverageOfWaterLevel.x = waterLevel.x * m_stepRatio.x;
	AverageOfWaterLevel.y = waterLevel.y * m_stepRatio.y;
	AverageOfWaterLevel.z = (waterLevel.z+m_dimMin.z)/2.0f * m_stepRatio.z;

	if(m_flipAverage)
		AverageOfWaterLevel = -AverageOfWaterLevel;

	fFraction = 1.0f/m_numPartialVolumes;
	fRemainingSlice = 1.0f - fFraction;
	m_impulsePoint = m_impulsePoint*fRemainingSlice + AverageOfWaterLevel*fThisVolume*fFraction;
	m_numPartialVolumes += 1.0f;
	m_haveVolume = true;
	return fThisVolume;
}

void
cBuoyancy::FindWaterLevel(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition)
{
	*waterPosition = FLOATER_IN_WATER;
	// waterLevel is a local x,y point
	// m_position is the global position of our floater
	// zpos is the global z coordinate of our floater
	CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel);
	CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z,
		&waterLevel->z, true);
	waterLevel->z -= xWaterLevel.z + zpos.z;	// make local
	if(waterLevel->z > m_dimMax.z){
		waterLevel->z = m_dimMax.z;
		*waterPosition = FLOATER_UNDER_WATER;
	}else if(waterLevel->z < m_dimMin.z){
		waterLevel->z = m_dimMin.z;
		*waterPosition = FLOATER_ABOVE_WATER;
	}
}

// Same as above but also get normal
void
cBuoyancy::FindWaterLevelNorm(const CVector &zpos, CVector *waterLevel, tWaterLevel *waterPosition, CVector *normal)
{
	*waterPosition = FLOATER_IN_WATER;
	CVector xWaterLevel = Multiply3x3(m_matrix, *waterLevel);
	CWaterLevel::GetWaterLevel(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y, m_position.z,
		&waterLevel->z, true);
	waterLevel->z -= xWaterLevel.z + zpos.z;	// make local
	if(waterLevel->z >= m_dimMin.z)
		*normal = CWaterLevel::GetWaterNormal(xWaterLevel.x + m_position.x, xWaterLevel.y + m_position.y);
	if(waterLevel->z > m_dimMax.z){
		waterLevel->z = m_dimMax.z;
		*waterPosition = FLOATER_UNDER_WATER;
	}else if(waterLevel->z < m_dimMin.z){
		waterLevel->z = m_dimMin.z;
		*waterPosition = FLOATER_ABOVE_WATER;
	}
}

bool
cBuoyancy::CalcBuoyancyForce(CPhysical *phys, CVector *point, CVector *impulse)
{
	if(!m_haveVolume)
		return false;

	*point = Multiply3x3(m_matrix, m_impulsePoint);
	*impulse = CVector(0.0f, 0.0f, m_volumeUnderWater*m_buoyancy*CTimer::GetTimeStep());
	return true;
}
