#include "stdafx.h"
#include "com.mojang.nbt.h"
#include "net.minecraft.world.entity.h"
#include "net.minecraft.world.entity.player.h"
#include "net.minecraft.world.item.h"
#include "net.minecraft.world.level.h"
#include "net.minecraft.world.level.material.h"
#include "net.minecraft.world.level.tile.h"
#include "net.minecraft.world.phys.h"
#include "net.minecraft.world.damagesource.h"
#include "Boat.h"

const double Boat::MAX_SPEED = 0.35;
const double Boat::MAX_COLLISION_SPEED = MAX_SPEED * 0.75;
const double Boat::MIN_ACCELERATION = 0.07;
const double Boat::MAX_ACCELERATION = 0.35;

// 4J - added for common ctor code
void Boat::_init()
{
	doLerp = true;
	acceleration = MIN_ACCELERATION;

	lSteps = 0;
	lx = ly = lz = lyr = lxr = 0.0;
	lxd = lyd = lzd = 0.0;

	blocksBuilding = true;
	setSize(1.5f, 0.6f);
	heightOffset = bbHeight / 2.0f;

	// 4J Stu - This function call had to be moved here from the Entity ctor to ensure that
	// the derived version of the function is called
	this->defineSynchedData();
}



Boat::Boat(Level *level) : Entity( level )
{

	_init();
}


bool Boat::makeStepSound()
{
	return false;
}

void Boat::defineSynchedData()
{
	entityData->define(DATA_ID_HURT, 0);
	entityData->define(DATA_ID_HURTDIR, 1);
	entityData->define(DATA_ID_DAMAGE, 0);
}


AABB *Boat::getCollideAgainstBox(shared_ptr<Entity> entity)
{
	return entity->bb;
}

AABB *Boat::getCollideBox()
{
	return bb;
}

bool Boat::isPushable()
{
	return true;
}

Boat::Boat(Level *level, double x, double y, double z) : Entity( level )
{
	_init();
	setPos(x, y + heightOffset, z);

	xd = 0;
	yd = 0;
	zd = 0;

	xo = x;
	yo = y;
	zo = z;
}

double Boat::getRideHeight()
{
	return bbHeight * 0.0f - 0.3f;
}

bool Boat::hurt(DamageSource *source, int hurtDamage)
{
	if (level->isClientSide || removed) return true;

	// 4J-JEV: Fix for #88212,
	// Untrusted players shouldn't be able to damage minecarts or boats.
	if (dynamic_cast<EntityDamageSource *>(source) != NULL)
	{
		shared_ptr<Entity> attacker = source->getDirectEntity();

		if (dynamic_pointer_cast<Player>(attacker) != NULL && 
			!dynamic_pointer_cast<Player>(attacker)->isAllowedToHurtEntity( shared_from_this() ))
			return false;
	}

	setHurtDir(-getHurtDir());
	setHurtTime(10);

	// 4J Stu - If someone is riding in this, then it can tick multiple times which causes the damage to
	// decrease too quickly. So just make the damage a bit higher to start with for similar behaviour
	// to an unridden one. Only do this change if the riding player is attacking it.
	if( rider.lock() != NULL && rider.lock() == source->getEntity() ) hurtDamage += 1;

	setDamage(getDamage() + hurtDamage * 10);
	markHurt();

	// 4J Stu - Brought froward from 12w36 to fix #46611 - TU5: Gameplay: Minecarts and boat requires more hits than one to be destroyed in creative mode
	shared_ptr<Player> player = dynamic_pointer_cast<Player>(source->getEntity());
	if (player != NULL && player->abilities.instabuild) setDamage(100);

	if (getDamage() > 20 * 2)
	{
		if (rider.lock() != NULL) rider.lock()->ride( shared_from_this() );
		spawnAtLocation(Item::boat_Id, 1, 0);
		remove();
	}
	return true;
}

void Boat::animateHurt()
{
	setHurtDir(-getHurtDir());
	setHurtTime(10);
	setDamage(getDamage() * 11);
}

bool Boat::isPickable()
{
	return !removed;
}


void Boat::lerpTo(double x, double y, double z, float yRot, float xRot, int steps)
{
	if (doLerp)
	{
		lSteps = steps + 5;
	}
	else
	{
		double xdiff = x - this->x;
		double ydiff = y - this->y;
		double zdiff = z - this->z;
		double diff = xdiff * xdiff + ydiff * ydiff + zdiff * zdiff;

		if (diff > 1)
		{
			lSteps = 3;
		}
		else
		{
			return;
		}
	}

	lx = x;
	ly = y;
	lz = z;
	lyr = yRot;
	lxr = xRot;

	this->xd = lxd;
	this->yd = lyd;
	this->zd = lzd;
}

void Boat::lerpMotion(double xd, double yd, double zd)
{
	lxd = this->xd = xd;
	lyd = this->yd = yd;
	lzd = this->zd = zd;
}

void Boat::tick()
{
	Entity::tick();
	if (getHurtTime() > 0) setHurtTime(getHurtTime() - 1);
	if (getDamage() > 0) setDamage(getDamage() - 1);
	xo = x;
	yo = y;
	zo = z;


	int steps = 5;
	double waterPercentage = 0;
	for (int i = 0; i < steps; i++)
	{
		double y0 = bb->y0 + (bb->y1 - bb->y0) * (i + 0) / steps - 2 / 16.0f;
		double y1 = bb->y0 + (bb->y1 - bb->y0) * (i + 1) / steps - 2 / 16.0f;
		AABB *bb2 = AABB::newTemp(bb->x0, y0, bb->z0, bb->x1, y1, bb->z1);
		if (level->containsLiquid(bb2, Material::water))
		{
			waterPercentage += 1.0 / steps;
		}
	}

	double lastSpeed = sqrt(xd * xd + zd * zd);
	if (lastSpeed > MAX_COLLISION_SPEED)
	{
		double xa = cos(yRot * PI / 180);
		double za = sin(yRot * PI / 180);

		for (int i = 0; i < 1 + lastSpeed * 60; i++)
		{

			double side = (random->nextFloat() * 2 - 1);

			double side2 = (random->nextInt(2) * 2 - 1) * 0.7;
			if (random->nextBoolean())
			{
				double xx = x - xa * side * 0.8 + za * side2;
				double zz = z - za * side * 0.8 - xa * side2;
				level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd);
			}
			else
			{
				double xx = x + xa + za * side * 0.7;
				double zz = z + za - xa * side * 0.7;
				level->addParticle(eParticleType_splash, xx, y - 2 / 16.0f, zz, +xd, yd, +zd);
			}
		}
	}

	if (level->isClientSide && doLerp)
	{
		if (lSteps > 0)
		{
			double xt = x + (lx - x) / lSteps;
			double yt = y + (ly - y) / lSteps;
			double zt = z + (lz - z) / lSteps;

			double yrd = Mth::wrapDegrees(lyr - yRot);

			yRot += (float) ( (yrd) / lSteps );
			xRot += (float) ( (lxr - xRot) / lSteps );

			lSteps--;
			this->setPos(xt, yt, zt);
			this->setRot(yRot, xRot);
		}
		else
		{
#if 1
			// Original
			//double xt = x + xd;
			//double yt = y + yd;
			//double zt = z + zd;
			//this->setPos(xt, yt, zt);

			// 4J Stu - Fix for various boat bugs, ensure that we check collision on client-side movement
			this->move(xd,yd,zd);

			if (onGround)
			{
				xd *= 0.5f;
				yd *= 0.5f;
				zd *= 0.5f;
			}
			xd *= 0.99f;
			yd *= 0.95f;
			zd *= 0.99f;
#else
			// 4J Stu - Fix for #8280 - Gameplay : Boats behave erratically when exited next to land.
			// The client shouldn't change the position of the boat
			double xt = x;// + xd;
			double yt = y + yd;
			double zt = z;// + zd;
			this->setPos(xt, yt, zt);

			// 4J Stu - Fix for #9579 - GAMEPLAY: Boats with a player in them slowly sink under the water over time, and with no player in them they float into the sky.
			// Just make the boats bob up and down rather than any other client-side movement when not receiving packets from server
			if (waterPercentage < 1)
			{
				double bob = waterPercentage * 2 - 1;
				yd += 0.04f * bob;
			}
			else
			{
				if (yd < 0) yd /= 2;
				yd += 0.007f;
			}
			//if (onGround)
			//{
			xd *= 0.5f;
			yd *= 0.5f;
			zd *= 0.5f;
			//}
			//xd *= 0.99f;
			//yd *= 0.95f;
			//zd *= 0.99f;
#endif
		}
		return;
	}

	if (waterPercentage < 1)
	{
		double bob = waterPercentage * 2 - 1;
		yd += 0.04f * bob;
	}
	else
	{
		if (yd < 0) yd /= 2;
		yd += 0.007f;
	}


	if (rider.lock() != NULL)
	{
		xd += rider.lock()->xd * acceleration;
		zd += rider.lock()->zd * acceleration;
	}

	double curSpeed = sqrt(xd * xd + zd * zd);

	if (curSpeed > MAX_SPEED)
	{
		double ratio = MAX_SPEED / curSpeed;

		xd *= ratio;
		zd *= ratio;
		curSpeed = MAX_SPEED;
	}

	if (curSpeed > lastSpeed && acceleration < MAX_ACCELERATION)
	{
		acceleration += (MAX_ACCELERATION - acceleration) / 35;
		if (acceleration > MAX_ACCELERATION) acceleration = MAX_ACCELERATION;
	}
	else
	{
		acceleration -= (acceleration - MIN_ACCELERATION) / 35;
		if (acceleration < MIN_ACCELERATION) acceleration = MIN_ACCELERATION;
	}

	if (onGround)
	{
		xd *= 0.5f;
		yd *= 0.5f;
		zd *= 0.5f;
	}
	move(xd, yd, zd);

	if ((horizontalCollision && lastSpeed > 0.20))
	{
		if (!level->isClientSide)
		{
			remove();
			for (int i = 0; i < 3; i++)
			{
				spawnAtLocation(Tile::wood_Id, 1, 0);
			}
			for (int i = 0; i < 2; i++)
			{
				spawnAtLocation(Item::stick->id, 1, 0);
			}
		}
	}
	else
	{
		xd *= 0.99f;
		yd *= 0.95f;
		zd *= 0.99f;
	}

	xRot = 0;
	double yRotT = yRot;
	double xDiff = xo - x;
	double zDiff = zo - z;
	if (xDiff * xDiff + zDiff * zDiff > 0.001)
	{
		yRotT = (float) (atan2(zDiff, xDiff) * 180 / PI);
	}

	double rotDiff = Mth::wrapDegrees(yRotT - yRot);

	if (rotDiff > 20) rotDiff = 20;
	if (rotDiff < -20) rotDiff = -20;

	yRot += (float) rotDiff;
	setRot(yRot, xRot);

	if(level->isClientSide) return;

	vector<shared_ptr<Entity> > *entities = level->getEntities(shared_from_this(), this->bb->grow(0.2f, 0, 0.2f));
	if (entities != NULL && !entities->empty())
	{
		AUTO_VAR(itEnd, entities->end());
		for (AUTO_VAR(it, entities->begin()); it != itEnd; it++)
		{
			shared_ptr<Entity> e = (*it); // entities->at(i);
			if (e != rider.lock() && e->isPushable() && e->GetType() ==  eTYPE_BOAT)
			{
				e->push(shared_from_this());
			}
		}
	}

	for (int i = 0; i < 4; i++)
	{
		int xx = Mth::floor(x + ((i % 2) - 0.5) * 0.8);
		int zz = Mth::floor(z + ((i / 2) - 0.5) * 0.8);

		for (int j = 0; j < 2; j++)
		{
			int yy = Mth::floor(y) + j;
			int tile = level->getTile(xx, yy, zz);
			int data = level->getData(xx, yy, zz);

			if (tile == Tile::topSnow_Id)
			{
				level->setTile(xx, yy, zz, 0);
			}
			else if (tile == Tile::waterLily_Id)
			{
				Tile::waterLily->spawnResources(level, xx, yy, zz, data, 0.3f, 0);
				level->setTile(xx, yy, zz, 0);
			}
		}

	}

	if (rider.lock() != NULL)
	{
		if (rider.lock()->removed) rider = weak_ptr<Entity>();
	}
}

void Boat::positionRider()
{
	if (rider.lock() == NULL) return;

	double xa = cos(yRot * PI / 180) * 0.4;
	double za = sin(yRot * PI / 180) * 0.4;
	rider.lock()->setPos(x + xa, y + getRideHeight() + rider.lock()->getRidingHeight(), z + za);
}


void Boat::addAdditonalSaveData(CompoundTag *base)
{
}

void Boat::readAdditionalSaveData(CompoundTag *base)
{
}


float Boat::getShadowHeightOffs()
{
	return 0;
}

wstring Boat::getName()
{
	return L"Boat";
}

bool Boat::interact(shared_ptr<Player> player)
{
	if (rider.lock() != NULL && dynamic_pointer_cast<Player>(rider.lock())!=NULL && rider.lock() != player) return true;
	if (!level->isClientSide)
	{
		// 4J HEG - Fixed issue with player not being able to dismount boat (issue #4446)
		player->ride( rider.lock() == player ? nullptr : shared_from_this() );
	}
	return true;
}

void Boat::setDamage(int damage)
{
	entityData->set(DATA_ID_DAMAGE, damage);
}

int Boat::getDamage()
{
	return entityData->getInteger(DATA_ID_DAMAGE);
}

void Boat::setHurtTime(int hurtTime)
{
	entityData->set(DATA_ID_HURT, hurtTime);
}

int Boat::getHurtTime()
{
	return entityData->getInteger(DATA_ID_HURT);
}

void Boat::setHurtDir(int hurtDir)
{
	entityData->set(DATA_ID_HURTDIR, hurtDir);
}

int Boat::getHurtDir()
{
	return entityData->getInteger(DATA_ID_HURTDIR);
}

bool Boat::getDoLerp()
{
	return doLerp;
}

void Boat::setDoLerp(bool doLerp)
{
	this->doLerp = doLerp;
}
