///////////////////////////////////////////////////////////////////
//
// ANIMATION QUEUES
//
// An animation queue is a fifo queue of animation steps.  New animations are
// pushed on the back, and old ones are popped from the front.
//
// An animation step has an "action" which is usually the name of an animation,
// or it's a special token like "walk" or "warp." Each animation step shows the
// resulting AnimState that the player finds himself in after executing the
// action.
//
// The first step in an animation queue always has id=0 and action="". This step
// represents the initial state of the sprite before any animations or
// movements.
//
// To add new items to the AnimQueue, use this process: first, call add(id,
// action).  This adds a new step to the queue.  Then, call set_xyz, set_facing,
// set_plane, or an other setter. These setters are meant to only be used
// immediately after calling 'add' to populate the new step. 
//
// VERSION NUMBERS
//
// The version number field: if the version number in the synchronous model is
// equal to the version number in the master model, then the two animqueues are
// guaranteed to be equal. Here's how we achieve that invariant:
//
// * In the master model, the version number starts at 1 and is auto-incremented
//   every time the animation queue is mutated.
//
// * In the synchronous model, the version number is set to zero every time the
//   animation queue is mutated.  Note that master version numbers are never
//   zero. Applying a patch also sets the version number to zero.
//
// * Serializing and deserializing causes the version number to be saved and
//   restored in both master and synchronous models.
//
// * The routine 'update_version' should be called after difference
//   transmission.  This copies the version number from the master to the
//   synchronous model.  This is guaranteed to be safe because we just finished
//   difference transmission, therefore, the queues should match.
//
///////////////////////////////////////////////////////////////////

#ifndef ANIMQUEUE_HPP
#define ANIMQUEUE_HPP

#include "wrap-set.hpp"
#include "wrap-string.hpp"
#include "wrap-deque.hpp"
#include "wrap-unordered-map.hpp"

#include "streambuffer.hpp"
#include "debugcollector.hpp"
#include "util.hpp"

#include <cassert>
#include <ostream>

class AnimStep : public eng::nevernew {
    friend class AnimQueue;
public:
    enum {
        HAS_FACING = 1,
        HAS_X = 2,
        HAS_Y = 4,
        HAS_Z = 8,
        HAS_XYZ = 14,
        HAS_GRAPHIC = 16,
        HAS_PLANE = 32,
        HAS_EVERYTHING = 63,
    };

    AnimStep();
    ~AnimStep();

    int64_t id() const { return id_; }
    int bits() const { return bits_; }

    const eng::string &action() const { return action_; }
    double facing() const { return facing_; }
    float x() const { return xyz_.x; }
    float y() const { return xyz_.y; }
    float z() const { return xyz_.z; }
    const util::XYZ &xyz() const { return xyz_; }
    const eng::string &graphic() const { return graphic_; }
    const eng::string &plane() const { return plane_; }

    bool has_facing() const { return bits_ & HAS_FACING; }
    bool has_x() const { return bits_ & HAS_X; }
    bool has_y() const { return bits_ & HAS_Y; }
    bool has_z() const { return bits_ & HAS_Z; }
    bool has_xyz() const { return (bits_ & HAS_XYZ) == HAS_XYZ; }
    bool has_graphic() const { return bits_ & AnimStep::HAS_GRAPHIC; }
    bool has_plane() const { return bits_ & AnimStep::HAS_PLANE; }

    void set_action(const eng::string &action);
    void set_facing(float f);
    void set_x(float f);
    void set_y(float f);
    void set_z(float z);
    void set_xyz(const util::XYZ &xyz);
    void set_graphic(const eng::string &g);
    void set_plane(const eng::string &p);

    void clear();

    // ExactlyEqual compares all fields.
    bool exactly_equal(const AnimStep &other) const;

    // LogicallyEqual only compares fields whose HAS_XXX bits are set.
    bool logically_equal(const AnimStep &other) const;

    // StateEqual is true if the plane, graphic, facing, and xyz match.
    bool state_equal(const AnimStep &other) const;

    // read/write the step using a stream buffer.
    //
    void write_into(StreamBuffer *sb) const;
    void read_from(StreamBuffer *sb);

    // Create an AnimStep from a lua table.
    //
    // Lua stack must contain a table, which may contain:
    //   action: "action"
    //   facing: 0.0 - 360.0
    //   x: x-coordinate
    //   y: y-coordinate
    //   z: z-coordinate
    //   graphic: "graphic"
    //   plane: "plane"
    //
    // qback: the animation queue back, from which relative
    // moves are computed.
    //
    void from_lua(lua_State *L, int idx, bool ignex, const AnimStep &qback);

    // Make this step into a first-step of an anim queue.
    void keep_state_only();
    
    // For any values that are unchanged in this step,
    // echo the values of the previous step.
    void echo(const AnimStep &prev);

    // Verify that this step echoes the previous step.
    bool echoes(const AnimStep &prev) const;

    // Convert to a string for debugging purposes.
    eng::string debug_string() const;

    // Convert a string to an animstep (for testing only).
    bool from_string(const eng::string &s);

private:
    int64_t id_;
    int16_t bits_;
    eng::string action_;

    float facing_;
    util::XYZ xyz_;
    eng::string graphic_;
    eng::string plane_;

    void from_lua_store_string(lua_State *L, int idx, eng::string *target, int16_t bits, const char *name);
    void from_lua_store_number(lua_State *L, int idx, float *target, float offset, int16_t bits, const char *name);    
};


class AnimQueue : public eng::nevernew {
public:
    // World type determines whether versions increment or autozero
    AnimQueue(util::WorldType wt);
        
    // Simple getters.
    const AnimStep &nth(int n) const { return steps_[n]; }
    size_t size() const { return steps_.size(); }
    int32_t size_limit() const { return size_limit_; }
    int64_t version_number() const { return version_number_; }
    bool version_identical(const AnimQueue &aq) const;

    // Return true if the size limit and steps are identical.
    bool size_and_steps_equal(const AnimQueue &aq) const;

    // Mutator to create a new step.
    void add(int64_t id, const AnimStep &step);
    
    // Clear and set the plane.
    void clear(const eng::string &plane);
    
    // Serialize or deserialize to a StreamBuffer
    //
    // Caution: version numbers are not stored.  On deserialize,
    // version number is set to zero.
    //
    void serialize(StreamBuffer *sb) const;
    void deserialize(StreamBuffer *sb);

    // Difference transmission
    bool diff(const AnimQueue &auth, StreamBuffer *sb) const;
    void patch(StreamBuffer *sb, DebugCollector *dbc);
    void update_version(const AnimQueue &auth);
    
    // Get the final resting place after all animations are complete.
    const AnimStep &back() const;

public:
    ////////////////////////////////////////////////////////////////////////////
    //
    // TESTING SUPPORT
    //
    // The following functions are not designed to be useful for production
    // code, they're designed to be helpful for unit testing.
    //
    ////////////////////////////////////////////////////////////////////////////

    // Change the size limit.
    void full_clear_and_set_limit(int szl);
    void set_limit(int szl);
    
    // Make sure the invariants are preserved.
    bool valid() const;

    // Convert to a string for debugging purposes.
    eng::string steps_debug_string() const;
    eng::string full_debug_string() const;
    
    // Convert to a 

private:
    bool version_autoinc_;
    int32_t size_limit_;
    eng::deque<AnimStep> steps_;
    int64_t version_number_;

    // Called whenever the animation queue is mutated.
    // serialization and deserialization
    void mutated();
};

#endif // ANIMQUEUE_HPP

