/*
 *  The ManaPlus Client
 *  Copyright (C) 2010  The Mana Developers
 *  Copyright (C) 2011-2012  The ManaPlus Developers
 *
 *  This file is part of The ManaPlus Client.
 *
 *  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 2 of the License, or
 *  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/>.
 */

#ifndef ACTORSPRITE_H
#define ACTORSPRITE_H

#include "actor.h"
#include "compoundsprite.h"
#include "localconsts.h"
#include "map.h"
#include "particlecontainer.h"

#include <SDL_types.h>

#include <set>
#include <list>

class SimpleAnimation;
class StatusEffect;
class ActorSpriteListener;

class ActorSprite : public CompoundSprite, public Actor
{
public:
    enum Type
    {
        UNKNOWN = 0,
        PLAYER,
        NPC,
        MONSTER,
        FLOOR_ITEM,
        PORTAL
    };

    enum TargetCursorSize
    {
        TC_SMALL = 0,
        TC_MEDIUM,
        TC_LARGE,
        NUM_TC
    };

    enum TargetCursorType
    {
        TCT_NONE = -1,
        TCT_NORMAL = 0,
        TCT_IN_RANGE,
        NUM_TCT
    };

    ActorSprite(const int id);

    A_DELETE_COPY(ActorSprite)

    ~ActorSprite();

    int getId() const
    { return mId; }

    void setId(const int id)
    { mId = id; }

    /**
     * Returns the type of the ActorSprite.
     */
    virtual Type getType() const
    { return UNKNOWN; }

    virtual bool draw(Graphics *graphics,
                      int offsetX, int offsetY) const override;

    virtual bool drawSpriteAt(Graphics *const graphics,
                              const int x, const int y) const;

    virtual void logic();

    static void actorLogic();

    void setMap(Map *const map) override;

    /**
     * Gets the way the object blocks pathfinding for other objects
     */
    virtual Map::BlockType getBlockType() const
    { return Map::BLOCKTYPE_NONE; }

    /**
     * Take control of a particle.
     */
    void controlParticle(Particle *const particle);

    /**
     * Returns the required size of a target cursor for this being.
     */
    virtual TargetCursorSize getTargetCursorSize() const
    { return TC_MEDIUM; }

    virtual int getTargetOffsetX() const
    { return 0; }

    virtual int getTargetOffsetY() const
    { return 0; }

    /**
     * Sets the target animation for this actor.
     */
    void setTargetType(const TargetCursorType type);

    /**
     * Untargets the actor.
     */
    void untarget()
    { mUsedTargetCursor = nullptr; }

    /**
     * Triggers a visual effect, such as `level up'. Only draws the visual
     * effect, does not play sound effects.
     *
     * \param effectId ID of the effect to trigger
     */
    virtual void triggerEffect(const int effectId)
    { internalTriggerEffect(effectId, false, true); }

    /**
     * Sets the actor's stun mode. If zero, the being is `normal', otherwise it
     * is `stunned' in some fashion.
     */
    void setStunMode(const uint16_t stunMode)
    {
        if (mStunMode != stunMode)
            updateStunMode(mStunMode, stunMode);
        mStunMode = stunMode;
    }

    void setStatusEffect(const int index, const bool active);

    /**
     * A status effect block is a 16 bit mask of status effects. We assign each
     * such flag a block ID of offset + bitnr.
     *
     * These are NOT the same as the status effect indices.
     */
    void setStatusEffectBlock(const int offset, const uint16_t flags);

    virtual void setAlpha(const float alpha) override
    { CompoundSprite::setAlpha(alpha); }

    virtual float getAlpha() const override
    { return CompoundSprite::getAlpha(); }

    virtual int getWidth() const override
    { return CompoundSprite::getWidth(); }

    virtual int getHeight() const override
    { return CompoundSprite::getHeight(); }

    static void load();

    static void unload();

    /**
     * Add an ActorSprite listener.
     */
    void addActorSpriteListener(ActorSpriteListener *const listener);

    /**
     * Remove an ActorSprite listener.
     */
    void removeActorSpriteListener(ActorSpriteListener *const listener);

protected:
    /**
     * Trigger visual effect, with components
     *
     * \param effectId ID of the effect to trigger
     * \param sfx Whether to trigger sound effects
     * \param gfx Whether to trigger graphical effects
     */
    void internalTriggerEffect(const int effectId, const bool sfx,
                               const bool gfx);

    /**
     * Notify self that the stun mode has been updated. Invoked by
     * setStunMode if something changed.
     */
    virtual void updateStunMode(const int oldMode, const int newMode);

    /**
     * Notify self that a status effect has flipped.
     * The new flag is passed.
     */
    virtual void updateStatusEffect(const int index, const bool newStatus);

    /**
     * Handle an update to a status or stun effect
     *
     * \param The StatusEffect to effect
     * \param effectId -1 for stun, otherwise the effect index
     */
    virtual void handleStatusEffect(StatusEffect *const effect,
                                    const int effectId);

    void setupSpriteDisplay(const SpriteDisplay &display,
                            const bool forceDisplay = true,
                            const int imageType = 0,
                            std::string color = "");

    int mId;
    uint16_t mStunMode;               /**< Stun mode; zero if not stunned */
    std::set<int> mStatusEffects;   /**< set of active status effects */

    ParticleList mStunParticleEffects;
    ParticleVector mStatusParticleEffects;
    ParticleList mChildParticleEffects;

private:
    /** Reset particle status effects on next redraw? */
    bool mMustResetParticles;

    /** Load the target cursors into memory */
    static void initTargetCursor();

    /** Remove the target cursors from memory */
    static void cleanupTargetCursors();

    /**
     * Helper function for loading target cursors
     */
    static void loadTargetCursor(const std::string &filename,
                                 const int width, const int height,
                                 const int type, const int size);

    /** Images of the target cursor. */
    static ImageSet *targetCursorImages[NUM_TCT][NUM_TC];

    /** Animated target cursors. */
    static SimpleAnimation *targetCursor[NUM_TCT][NUM_TC];

    static bool loaded;

    /** Target cursor being used */
    SimpleAnimation *mUsedTargetCursor;

    typedef std::list<ActorSpriteListener*> ActorSpriteListeners;
    typedef ActorSpriteListeners::iterator ActorSpriteListenerIterator;
    ActorSpriteListeners mActorSpriteListeners;
};

#endif // ACTORSPRITE_H