////////////////////////////////////////////////////////////////////////////////
//    Scorched3D (c) 2000-2003
//
//    This file is part of Scorched3D.
//
//    Scorched3D 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 2 of the License, or
//    (at your option) any later version.
//
//    Scorched3D 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 Scorched3D; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
////////////////////////////////////////////////////////////////////////////////

#include <actions/TankMovement.h>
#include <actions/TankMovementEnd.h>
#include <actions/TankFalling.h>
#include <actions/ShotProjectile.h>
#include <engine/ScorchedContext.h>
#include <engine/ActionController.h>
#include <landscape/LandscapeMaps.h>
#include <tank/TankContainer.h>
#include <common/OptionsGame.h>
#include <sound/Sound.h>

static const int NoMovementTransitions = 4;
std::map<unsigned int, TankMovement*> TankMovement::movingTanks;

REGISTER_ACTION_SOURCE(TankMovement);

TankMovement::TankMovement() : 
	timePassed_(0.0f), vPoint_(0), 
	remove_(false), moving_(true), moveSoundSource_(0)
{
}

TankMovement::TankMovement(unsigned int playerId,
	int positionX, int positionY) : 
	playerId_(playerId), 
	positionX_(positionX), positionY_(positionY),
	timePassed_(0.0f), vPoint_(0), 
	remove_(false), moving_(true), moveSoundSource_(0)
{
}

TankMovement::~TankMovement()
{
	if (vPoint_) context_->viewPoints->releaseViewPoint(vPoint_);
	delete moveSoundSource_;
	moveSoundSource_ = 0;
}

void TankMovement::init()
{
	movingTanks[playerId_] = this;

	Tank *tank = context_->tankContainer->getTankById(playerId_);
	if (!tank) return;	

	vPoint_ = context_->viewPoints->getNewViewPoint(playerId_);
	
	// Start the tank movement sound
	if (!context_->serverMode) 
	{
		SoundBuffer *moveSound = 
			Sound::instance()->fetchOrCreateBuffer((char *)
				getDataFile("data/wav/movement/tankmove.wav"));
		moveSoundSource_ = new VirtualSoundSource(VirtualSoundPriority::eAction, true, false);
		moveSoundSource_->setPosition(tank->getPhysics().getTankPosition());
		moveSoundSource_->play(moveSound);
	}

	// As with everything to do with movement
	// The xy position is stored as an unsigned int
	// to save space, z is calculated from the landscape
	// Lower 32 bits = y position
	// Upper 32 bits = x positions
	std::list<unsigned int> positions;
	context_->landscapeMaps->getMMap().calculateForTank(tank, *context_, true);
	
	MovementMap::MovementMapEntry entry =
		context_->landscapeMaps->getMMap().getEntry(positionX_, positionY_);
	if (entry.type == MovementMap::eMovement)
	{
		// Add the end (destination) point to the list of points for the tank
		// to visit
		unsigned int pt = (positionX_ << 16) | (positionY_ & 0xffff);
		positions.push_front(pt);

		// Work backward to the source point and pre-pend them onto the
		// this of points
		while (entry.srcEntry)
		{
			pt = entry.srcEntry;
			unsigned int x = pt >> 16;
			unsigned int y = pt & 0xffff;
			positions.push_front(pt);
			entry = context_->landscapeMaps->getMMap().getEntry(x, y);
		}
	}
	
	// Expand these positions into a interpolated set of positions with
	// x, y and z
	std::list<unsigned int>::iterator itor;
	for (itor = positions.begin();
		itor != positions.end();)
	{
		unsigned int fistpt = (*itor);
		itor++;

		if (itor != positions.end())
		{
			unsigned int secpt = (*itor);

			float firstx = float(fistpt >> 16);
			float firsty = float(fistpt & 0xffff);
			float *firstHeight = 
				&context_->landscapeMaps->getHMap().getHeightRef(int(firstx), int(firsty));
			float secx = float(secpt >> 16);
			float secy = float(secpt & 0xffff);
			float *secondHeight = 
				&context_->landscapeMaps->getHMap().getHeightRef(int(secx), int(secy));

			float diffX = secx - firstx;
			float diffY = secy - firsty;
			float ang = (atan2f(diffY, diffX) / 3.14f * 180.0f) - 90.0f;

			for (int i=0; i<NoMovementTransitions; i++)
			{
				float currentX = firstx + diffX/float(NoMovementTransitions)*float(i+1);
				float currentY = firsty + diffY/float(NoMovementTransitions)*float(i+1);
				expandedPositions_.push_back(
					PositionEntry(
						currentX, currentY, 
						ang, (i==(NoMovementTransitions-1)),
						firstHeight, secondHeight));
			}
		}
	}
}

void TankMovement::simulate(float frameTime, bool &remove)
{
	if (!remove_)
	{
		if (moving_)
		{
			simulationMove(frameTime);
		}
	}
	else
	{
		remove = true;
	}

	if (remove && moveSoundSource_)
	{
		moveSoundSource_->stop();
	}
	
	ActionMeta::simulate(frameTime, remove);
}

void TankMovement::remove()
{
	remove_ = true;

	TankMovement::movingTanks.erase(playerId_);
}

void TankMovement::simulationMove(float frameTime)
{
	Tank *tank = 
		context_->tankContainer->getTankById(playerId_);
	if (tank)
	{
		// Stop moving if the tank is dead
		if (tank->getState().getState() == TankState::sNormal)
		{
			// Check to see if this tank is falling
			// If it is then we wait until the fall is over
			std::map<unsigned int, TankFalling *>::iterator findItor =
				TankFalling::fallingTanks.find(playerId_);
			if (findItor == TankFalling::fallingTanks.end())
			{
				// Move the tank one position every stepsPerFrame seconds
				// i.e. 1/stepsPerFrame positions a second
				timePassed_ += frameTime;
				const float stepsPerFrame = 0.05f;
				while (timePassed_ >= stepsPerFrame)
				{
					timePassed_ -= stepsPerFrame;
					if (!expandedPositions_.empty())
					{
						moveTank(tank);
					}
					else break;
				}

				if (expandedPositions_.empty()) moving_ = false;
			}
		}
		else moving_ = false;
	}
	else moving_ = false;

	if (moving_ == false)
	{
		// If this is the very last movement made
		// Ensure all tanks always end in the same place
		context_->actionController->addAction(
			new TankMovementEnd(tank->getPhysics().getTankPosition(),
				tank->getPlayerId()));
	}
}

void TankMovement::moveTank(Tank *tank)
{
	float x = expandedPositions_.front().x;
	float y = expandedPositions_.front().y;
	float a = expandedPositions_.front().ang;
	bool useF = expandedPositions_.front().useFuel;
	float *firstz = expandedPositions_.front().heighta;
	float *secondz = expandedPositions_.front().heightb;
	float z = context_->landscapeMaps->getHMap().getInterpHeight(x, y);
	expandedPositions_.pop_front();

	// Form the new tank position
	Vector newPos(x, y, z);

	// Check we are not trying to climb to high (this may be due
	// to the landscape changing after we started move)
	if (*secondz - *firstz > context_->optionsGame->getMaxClimbingDistance())
	{
		expandedPositions_.clear();
	}
	else
	{
		// Use up one unit of fuel
		if (useF) tank->getAccessories().getFuel().rmFuel(1);

		// Actually move the tank
		tank->getPhysics().rotateTank(a);
		tank->getPhysics().setTankPosition(newPos);

		// Set viewpoints
		if (vPoint_) vPoint_->setPosition(newPos);
		if (moveSoundSource_) moveSoundSource_->setPosition(newPos);
	}
}

bool TankMovement::writeAction(NetBuffer &buffer)
{
	buffer.addToBuffer(playerId_);
	buffer.addToBuffer(positionX_);
	buffer.addToBuffer(positionY_);
	return true;
}

bool TankMovement::readAction(NetBufferReader &reader)
{
	if (!reader.getFromBuffer(playerId_)) return false;
	if (!reader.getFromBuffer(positionX_)) return false;
	if (!reader.getFromBuffer(positionY_)) return false;
	return true;
}
