/*
 * BattleAnimations.h, part of VCMI engine
 *
 * Authors: listed in file AUTHORS in main folder
 *
 * License: GNU General Public License v2.0 or later
 * Full text of license available in license.txt file, in main folder
 *
 */
#pragma once

#include "../../lib/battle/BattleHex.h"
#include "../../lib/CSoundBase.h"
#include "../widgets/Images.h"

VCMI_LIB_NAMESPACE_BEGIN

class CStack;

VCMI_LIB_NAMESPACE_END

class BattleInterface;
class CreatureAnimation;
class CBattleAnimation;
struct CatapultProjectileInfo;
struct StackAttackedInfo;

/// Base class of battle animations
class CBattleAnimation
{

protected:
	BattleInterface & owner;
	bool initialized;

	std::vector<CBattleAnimation *> & pendingAnimations();
	std::shared_ptr<CreatureAnimation> stackAnimation(const CStack * stack) const;
	bool stackFacingRight(const CStack * stack);
	void setStackFacingRight(const CStack * stack, bool facingRight);

	virtual bool init() = 0; //to be called - if returned false, call again until returns true
	bool checkInitialConditions(); //determines if this animation is earliest of all

public:
	ui32 ID; //unique identifier

	bool isInitialized();
	bool tryInitialize();
	virtual void nextFrame() {} //call every new frame
	virtual ~CBattleAnimation();

	CBattleAnimation(BattleInterface & owner);
};

/// Sub-class which is responsible for managing the battle stack animation.
class CBattleStackAnimation : public CBattleAnimation
{
public:
	std::shared_ptr<CreatureAnimation> myAnim; //animation for our stack, managed by CBattleInterface
	const CStack * stack; //id of stack whose animation it is

	CBattleStackAnimation(BattleInterface & owner, const CStack * _stack);

	void shiftColor(const ColorShifter * shifter);
	void rotateStack(BattleHex hex);
};

/// This class is responsible for managing the battle attack animation
class CAttackAnimation : public CBattleStackAnimation
{
	bool soundPlayed;

protected:
	BattleHex dest; //attacked hex
	bool shooting;
	CCreatureAnim::EAnimType group; //if shooting is true, print this animation group
	const CStack *attackedStack;
	const CStack *attackingStack;
	int attackingStackPosBeforeReturn; //for stacks with return_after_strike feature

	const CCreature * getCreature() const;
public:
	void nextFrame() override;
	bool checkInitialConditions();

	CAttackAnimation(BattleInterface & owner, const CStack *attacker, BattleHex _dest, const CStack *defender);
	~CAttackAnimation();
};

/// Animation of a defending unit
class CDefenceAnimation : public CBattleStackAnimation
{
	CCreatureAnim::EAnimType getMyAnimType();
	std::string getMySound();

	void startAnimation();

	const CStack * attacker; //attacking stack
	bool rangedAttack; //if true, stack has been attacked by shooting
	bool killed; //if true, stack has been killed

	float timeToWait; // for how long this animation should be paused
public:
	bool init() override;
	void nextFrame() override;

	CDefenceAnimation(StackAttackedInfo _attackedInfo, BattleInterface & owner);
	~CDefenceAnimation();
};

class CDummyAnimation : public CBattleAnimation
{
private:
	int counter;
	int howMany;
public:
	bool init() override;
	void nextFrame() override;

	CDummyAnimation(BattleInterface & owner, int howManyFrames);
};

/// Hand-to-hand attack
class CMeleeAttackAnimation : public CAttackAnimation
{
public:
	bool init() override;

	CMeleeAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex _dest, const CStack * _attacked);
};

/// Base class for all animations that play during stack movement
class CStackMoveAnimation : public CBattleStackAnimation
{
public:
	BattleHex currentHex;

protected:
	CStackMoveAnimation(BattleInterface & owner, const CStack * _stack, BattleHex _currentHex);
};

/// Move animation of a creature
class CMovementAnimation : public CStackMoveAnimation
{
private:
	std::vector<BattleHex> destTiles; //full path, includes already passed hexes
	ui32 curentMoveIndex; // index of nextHex in destTiles

	BattleHex oldPos; //position of stack before move

	double begX, begY; // starting position
	double distanceX, distanceY; // full movement distance, may be negative if creture moves topleft

	double timeToMove; // full length of movement animation
	double progress; // range 0 -> 1, indicates move progrees. 0 = movement starts, 1 = move ends

public:
	bool init() override;
	void nextFrame() override;

	CMovementAnimation(BattleInterface & owner, const CStack *_stack, std::vector<BattleHex> _destTiles, int _distance);
	~CMovementAnimation();
};

/// Move end animation of a creature
class CMovementEndAnimation : public CStackMoveAnimation
{
public:
	bool init() override;

	CMovementEndAnimation(BattleInterface & owner, const CStack * _stack, BattleHex destTile);
	~CMovementEndAnimation();
};

/// Move start animation of a creature
class CMovementStartAnimation : public CStackMoveAnimation
{
public:
	bool init() override;

	CMovementStartAnimation(BattleInterface & owner, const CStack * _stack);
};

/// Class responsible for animation of stack chaning direction (left <-> right)
class CReverseAnimation : public CStackMoveAnimation
{
public:
	bool priority; //true - high, false - low
	bool init() override;

	void setupSecondPart();

	CReverseAnimation(BattleInterface & owner, const CStack * stack, BattleHex dest, bool _priority);
	~CReverseAnimation();
};

class CRangedAttackAnimation : public CAttackAnimation
{

	void setAnimationGroup();
	void initializeProjectile();
	void emitProjectile();
	void emitExplosion();

protected:
	bool projectileEmitted;

	virtual CCreatureAnim::EAnimType getUpwardsGroup() const = 0;
	virtual CCreatureAnim::EAnimType getForwardGroup() const = 0;
	virtual CCreatureAnim::EAnimType getDownwardsGroup() const = 0;

	virtual void createProjectile(const Point & from, const Point & dest) const = 0;
	virtual uint32_t getAttackClimaxFrame() const = 0;

public:
	CRangedAttackAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);
	~CRangedAttackAnimation();

	bool init() override;
	void nextFrame() override;
};

/// Shooting attack
class CShootingAnimation : public CRangedAttackAnimation
{
	CCreatureAnim::EAnimType getUpwardsGroup() const override;
	CCreatureAnim::EAnimType getForwardGroup() const override;
	CCreatureAnim::EAnimType getDownwardsGroup() const override;

	void createProjectile(const Point & from, const Point & dest) const override;
	uint32_t getAttackClimaxFrame() const override;

public:
	CShootingAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender);

};

/// Catapult attack
class CCatapultAnimation : public CShootingAnimation
{
private:
	bool explosionEmitted;
	int catapultDamage;

public:
	CCatapultAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest, const CStack * defender, int _catapultDmg = 0);

	void createProjectile(const Point & from, const Point & dest) const override;
	void nextFrame() override;
};

class CCastAnimation : public CRangedAttackAnimation
{
	const CSpell * spell;

	CCreatureAnim::EAnimType findValidGroup( const std::vector<CCreatureAnim::EAnimType> candidates ) const;
	CCreatureAnim::EAnimType getUpwardsGroup() const override;
	CCreatureAnim::EAnimType getForwardGroup() const override;
	CCreatureAnim::EAnimType getDownwardsGroup() const override;

	void createProjectile(const Point & from, const Point & dest) const override;
	uint32_t getAttackClimaxFrame() const override;

public:
	CCastAnimation(BattleInterface & owner, const CStack * attacker, BattleHex dest_, const CStack * defender, const CSpell * spell);
};

struct CPointEffectParameters
{
	std::vector<Point> positions;
	std::vector<BattleHex> tiles;
	std::string animation;

	soundBase::soundID sound = soundBase::invalid;
	BattleHex boundHex = BattleHex::INVALID;
	bool aligntoBottom = false;
	bool waitForSound = false;
	bool screenFill = false;
};

/// Class that plays effect at one or more positions along with (single) sound effect
class CPointEffectAnimation : public CBattleAnimation
{
	soundBase::soundID sound;
	bool soundPlayed;
	bool soundFinished;
	bool effectFinished;
	int effectFlags;

	std::shared_ptr<CAnimation>	animation;
	std::vector<Point> positions;
	std::vector<BattleHex> battlehexes;

	bool alignToBottom() const;
	bool waitForSound() const;
	bool forceOnTop() const;
	bool screenFill() const;

	void onEffectFinished();
	void onSoundFinished();
	void clearEffect();

	void playSound();
	void playEffect();

public:
	enum EEffectFlags
	{
		ALIGN_TO_BOTTOM = 1,
		WAIT_FOR_SOUND  = 2,
		FORCE_ON_TOP    = 4,
		SCREEN_FILL     = 8,
	};

	/// Create animation with screen-wide effect
	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, int effects = 0);

	/// Create animation positioned at point(s). Note that positions must be are absolute, including battleint position offset
	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos                 , int effects = 0);
	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<Point> pos    , int effects = 0);

	/// Create animation positioned at certain hex(es)
	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, BattleHex hex             , int effects = 0);
	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, std::vector<BattleHex> hex, int effects = 0);

	CPointEffectAnimation(BattleInterface & owner, soundBase::soundID sound, std::string animationName, Point pos, BattleHex hex,   int effects = 0);
	 ~CPointEffectAnimation();

	bool init() override;
	void nextFrame() override;
};

/// Base class (e.g. for use in dynamic_cast's) for "animations" that wait for certain event
class CWaitingAnimation : public CBattleAnimation
{
protected:
	CWaitingAnimation(BattleInterface & owner);
public:
	void nextFrame() override;
};

/// Class that waits till projectile of certain shooter hits a target
class CWaitingProjectileAnimation : public CWaitingAnimation
{
	const CStack * shooter;
public:
	CWaitingProjectileAnimation(BattleInterface & owner, const CStack * shooter);

	bool init() override;
};