/*
  File name: Enemy.cpp
  Copyright: (C) 2008 - 2009 Tomasz Kazmierczak

  Creation date: 11.04.2008
  Last modification date: 13.06.2009

  This file is part of Cytadela

 *   This program 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.                                   *
 *                                                                         *
 *   This program 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 this program. If not, see <http://www.gnu.org/licenses/>.  *

*/

#include <math.h>
#include "Enemy.h"
#include "../CCytadelaConfig.h"
#include "../CSDLClass.h"

Enemy::Enemy(Mesh3Dg *mesh) :
	GOSprite(mesh, GOTYPE_ENEMY),
	mBurning(false),
	mBurningTime(14000),
	mFireSoundChannel(-1),
	mDyingTime(750),
	mBasicMaterialNumber(mesh->materialNumber),
	mAnimType(ANIM_WALK),
	mAnimTime(0),
	mAnimFrame1(0),
	mAnimFrame2(0),
	mDirection(1.0f, 0.0f, 0.0f),
	mActualVelocity(0.0f, 0.0f, 0.0f),
	mPosition(0.0f, 0.0f, 0.0f),
	mSpeed(0.0f),
//	mSomething(0),
	mAgression(0),
	mAmmoXPosShift(0.0f),
	mAmmoYPosShift(0.0f)
{
	assert(mesh != 0);
	mPosition = mesh->geomCenter();
}

EnemyUpdateResult Enemy::update(uint32_t timeDiff, Vector3Dg &playerPosition)
{
	CSDLClass *sdl = CSDLClass::instance();
	//animation handling
	mAnimTime += timeDiff;
	bool updateAnim = false;
	if(mAnimTime % 500 > 250) {
		mAnimTime = mAnimTime % 500 - 250;
		mAnimFrame1 = (mAnimFrame1 + 1)%2;
		mAnimFrame2 = (mAnimFrame2 + 1)%4;
		updateAnim = true;
	}

	//vector from enemy to player
	Vector3Dg enemyToPlayer = playerPosition - mPosition;
	//make sure that the Y coord is 0.0
	enemyToPlayer.y = 0.0f;
	//calculate the distaince
	float playerDistaince = enemyToPlayer.length();

	//if burning, then decrease the burning time
	if(mBurning) {
		mBurningTime -= timeDiff <= mBurningTime ? timeDiff : mBurningTime;

		//animate the burn
		if(updateAnim)
			getMesh()->materialNumber = mBasicMaterialNumber + 20 + mAnimFrame1;

		//set the new volume for the sound
		if(mFireSoundChannel > -1)
			sdl->setSoundVolume(mFireSoundChannel, calculateVolume(playerDistaince));
		//if burnt
		if(mBurningTime == 0) {
			//decrease the HP below 0 (for isAlive() function)
			mHP = -1;
			//no dying time - the enemy has suffered enough while burning
			mDyingTime = 0;
			die();
			mBurning = false;
			if(mFireSoundChannel > -1)
				sdl->stopSound(mFireSoundChannel, 0);
			return EUR_BURNT;
		}
		//random walk - change the direction randomly
		randomDirection();
		if(updateAnim) {
			float speed = 12.5f - (rand() / (double)RAND_MAX) * 25.0f;
			mActualVelocity = mDirection * speed;
		}
		//the enemy can't do anything more than walking around while burning
		return EUR_BURNING;
	}

	//if dead
	if(not isAlive()) {
		if(mDyingTime > 0) {
			mDyingTime -= timeDiff <= mDyingTime ? timeDiff : mDyingTime;
			if(mDyingTime == 0) {
				getMesh()->materialNumber = mBasicMaterialNumber + 24;
				finalTransformation();
			} else if(mDyingTime < 500) {
				if(mDyingTime < 250) {
					getMesh()->materialNumber = mBasicMaterialNumber + 23;
				} else getMesh()->materialNumber = mBasicMaterialNumber + 22;
			}
		}
		return EUR_DEAD;
	}

	switch(mAnimType) {
		case ANIM_WALK: {
			//the walk speed
			float speed = mSpeed * CCytadelaConfig::instance()->getGameSpeed();
			mActualVelocity = mDirection * speed * timeDiff;

			//further processing only when the animation frame has to be refreshed
			if(not updateAnim)
				return EUR_WALK;

			//try to attack player
			//the algorithm causes the enemy to rotate towards the player more often that
			//in other directions - thanks to that the enemy's walk is not as random and
			//also enemies seem to 'sniff' the player and get closer to him over time;
			//the algorithm uses units from the original Cytadela, so the playerDistaince
			//has to be converted to that units
			int16_t randomNumber = (int16_t)(0xffff * (rand() / (double)RAND_MAX)) & 0x1fff;
			int16_t dist = 10 * (int16_t)playerDistaince;
			//if far away, then soot only with some probability
			if(dist > 7000) {
				if(randomNumber >= 7000) {
					rotateTowardsPlayer(playerPosition);
					return EUR_AIMING;
				}
			//close
			} else {
				if(dist <= randomNumber % (dist + mAgression)) {
					rotateTowardsPlayer(playerPosition);
					return EUR_AIMING;
				}
			}
			//change the direction randomly
			randomDirection();

			//set the texture according to the direction:
			//copy the vector directing from enemy to player
			Vector3Dg vec(enemyToPlayer);
			//normalize and calculate a dot product of this vector and the enemy's direction
			vec.normalize();
			float product = mDirection.dotProduct(vec);
			//calculate the cross product - we will use the sign of the Y coordinate to
			//determine the side of the enemy's turn
			vec = mDirection.crossProduct(vec);

			//dependant on the dot product (which is a cosinus of an angle
			//between the vectors) set the apropriate texture
			if(product > 0.923879f)//-22.5 to 22.5 deg
				getMesh()->materialNumber = mBasicMaterialNumber + (mAnimFrame2+1)*mAnimFrame1/2;
			else if(product <= 0.923879f and product > 0.382683f) {//22.5 to 67.5 deg
				if(vec.y > 0)
					getMesh()->materialNumber = mBasicMaterialNumber + 3 + mAnimFrame1;
				else getMesh()->materialNumber = mBasicMaterialNumber + 8 + mAnimFrame1;
			} else if(product <= 0.382683f and product > -0.707107f) {//67.5 to 135 deg
				if(vec.y > 0)
					getMesh()->materialNumber = mBasicMaterialNumber + 5 + (mAnimFrame2+1)*mAnimFrame1/2;
				else getMesh()->materialNumber = mBasicMaterialNumber + 10 + (mAnimFrame2+1)*mAnimFrame1/2;
			} else getMesh()->materialNumber = mBasicMaterialNumber + 13 + mAnimFrame1;
			return EUR_WALK;
		}
		case ANIM_SHOT: {//15, 16, 17 -> walk
			if(updateAnim) {
				getMesh()->materialNumber++;
				if(getMesh()->materialNumber == mBasicMaterialNumber + 18) {
					//if the shoot animation has finished
					getMesh()->materialNumber = mBasicMaterialNumber;
					mAnimType = ANIM_WALK;
				} else if(getMesh()->materialNumber == mBasicMaterialNumber + 17)
					return EUR_SHOT;
			}
			break;
		}
		case ANIM_HIT: {//18, 19 -> walk
			if(updateAnim) {
				getMesh()->materialNumber++;
				if(getMesh()->materialNumber == mBasicMaterialNumber + 20) {
					//if the hit animation has finished
					getMesh()->materialNumber = mBasicMaterialNumber;
					mAnimType = ANIM_WALK;
				}
			}
			break;
		}
	}
	return EUR_UNDEFINED;
}

int16_t Enemy::hit(int16_t power, Vector3Dg &playerPosition)
{
	//set the direction of the enemy (should look towards the player)
	mDirection = playerPosition - mPosition;
	//make sure that the Y coord is 0.0
	mDirection.y = 0.0f;
	mDirection.normalize();

	//move back a bit
	mActualVelocity = mDirection * -8.0f;

	mAnimType = ANIM_HIT;
	getMesh()->materialNumber = mBasicMaterialNumber + 18;
	mAnimTime = 0;
	return decreaseHP(power);
}

bool Enemy::burn()
{
	//can't burn more than once
	if(canBurn() and not mBurning) {
		mBurning = true;
		return true;
	} else return false;
}

void Enemy::die()
{
	//change texture
	if(isBurning()) {
		getMesh()->materialNumber = mBasicMaterialNumber + 25;
		finalTransformation();
	} else
		getMesh()->materialNumber = mBasicMaterialNumber + 18;
}

void Enemy::move()
{
	getMesh()->addTranslation(mActualVelocity.x, 0.0f, mActualVelocity.z);
	mPosition += mActualVelocity;
	mActualVelocity.x = mActualVelocity.y = mActualVelocity.z = 0.0f;
}

void Enemy::finalTransformation() {
	//scale the mesh (the corps' mesh should be half the size of the enemy's mesh)
	//(it's the first time when scaling is used in this game...;)
	getMesh()->scale(1.0f, 0.5f, 1.0f);

	setPlayerCollisionType(PCTYPE_NONE);
	setAmmoCollisionType(ACTYPE_NONE);

	//place the corps in the middle of the field
	mPosition.x = ((int)mPosition.x/100) * 100.0f + 50.0f;
	mPosition.z = ((int)mPosition.z/100) * 100.0f - 50.0f;
	mPosition.y -= 25.0f;
	getMesh()->setPosition(mPosition.x, mPosition.y, mPosition.z);
}

int32_t Enemy::calculateVolume(float distaince) {
	float max = 800.0f;
	if(distaince > max)
		return 0;
	distaince *= 128.0f / max;
	return 128 - (int32_t)distaince;
}

void Enemy::rotateTowardsPlayer(Vector3Dg &playerPosition)
{
	//set the direction of the enemy (should look towards the player)
	mDirection = playerPosition - mPosition;
	//make sure that the Y coord is 0.0
	mDirection.y = 0.0f;
	mDirection.normalize();
}

void Enemy::shoot()
{
	mAnimType = ANIM_SHOT;
	getMesh()->materialNumber = mBasicMaterialNumber + 15;
	mAnimTime = 0;
}

void Enemy::randomDirection()
{
	float randomAngle = M_PI * (rand() / (float)RAND_MAX);
	int randomNumber = rand();
	if(randomNumber > RAND_MAX / 2) {
		if(randomNumber & 1)
			randomAngle = -randomAngle;
		//rotate the enemy (standard 2D rotation algorithm)
		float newx = mDirection.x * cosf(randomAngle) - mDirection.z * sinf(randomAngle);
		float newz = mDirection.x * sinf(randomAngle) + mDirection.z * cosf(randomAngle);
		mDirection.x = newx;
		mDirection.z = newz;
	}
}
