///////////////////////////////////////////////////////////////////
//
// ANIMATION QUEUES AND ANIMATION STEPS
//
// See "Animation Queues and Tangible Actors.md" for an overview.
//
// 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 is a set of key-value pairs, where each key is an
// identifier, and each value is either a number, boolean, vector,
// token, or string.  A key-value pair can be either persistent or nonpersistent.
// So a typical animation step might be:
//
//    action=walk  [nonpersistent]
//    xyz=3,4,5    [persistent]
//    facing=320   [persistent]
//    plane=earth  [persistent]
//
// Persistent values are retained from one animation step to the next,
// nonpersistent values exist for one animation step only.
//
// Each animation step has a hash value.  The hash value is generated
// by mixing the hash value of the previous step with the hash value
// of the encoded string of key-value pairs.
//
//
///////////////////////////////////////////////////////////////////
//
// Class AnimStepEditor is used to read+write animation steps.
//
// Note: this class is not used for storage of animation steps.
// Animation steps are stored in class AnimQueue.  AnimStepEditor
// is only used when you want to extract animation steps from
// an AnimQueue, or insert new animation steps into an AnimQueue.
// 
// Class AnimStepEditor is quite simple: it's a map from Key to
// AnimValue.  AnimValue is a container that can hold a number,
// string, vector, token, or boolean.  Class AnimStepEditor provides
// a variety of accessors to set key-value pairs.
// 
// For example, you can populate an animation step by setting
// key-value pairs directly.  You can import key-value pairs
// from a lua table.  You can also merge key-value pairs from
// a different AnimStepEditor.
//
// When importing from a lua_table or a from another AnimStepEditor,
// there are rules for resolving conflicts between any key-value
// pairs that are already in the builder with those being imported.
// See the documentation for those functions for more information.
//
///////////////////////////////////////////////////////////////////
//
// Class AnimQueue stores animation queues.
//
// The entired animation queue is stored in a serialized format,
// as a shared string.  This means that the animation queue can be
// passed to the graphics engine as a single string.  This can be
// accomplished by the function EngineWrapper.get_animation_queues.
//
// The fact that the queue is stored in a serialized format means
// that when manipulating the animation queue, we have to decode it,
// manipulate it, and then reencode it.
//
// From an efficiency perspective, this means that manipulation is
// slower, but passing the strings to the graphics engine is faster
// and simpler.  This is a good tradeoff: we manipulate animation
// queues rarely, compared to how often we pass them to the graphics
// engine.
//
///////////////////////////////////////////////////////////////////
//
// THE SERIALIZED REPRESENTATION
//
// So first, you need to know how to serialize a single animation
// step.  Remember, an animation step consists of a list of key-value
// pairs (see above).  The key-value pairs are serialized as follows:
//
//   for all key-value pairs do:
//      write_string(key)
//      write_bool(persistent)
//      write_lua_value(value)
//
// The encoded string produced by the loop above is called an "encstep".
// That's short for "encoded animation step".  The encstep has a hash
// value, which is a function that accepts the encstep and also the hash
// of the previous encstep.  Note that the hash is not part of the encstep.
//
// A serialized animation queue consists of the following information:
//
//     write_uint8(size_limit);
//     write_uint8(actual_size);
//     for all animation steps, starting with the most recent, do:
//        write_uint64(hash)
//        write_string(encstep)
//
// The encoded string produced by the loop above is called an "encqueue",
// because it encodes everything in the animation queue.
//
// Note that the 'serialize' routine for animation queues just returns
// the encqueue string, which is the whole thing.
//
// Since the steps in an encqueue are stored most-recent first, if you
// want some information about the most recent animation entry, you
// don't need to decode the entire encqueue.  You only need to
// decode the first step.
//
///////////////////////////////////////////////////////////////////

#pragma once

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

#include "base-buffer.hpp"
#include "luastack.hpp"
#include "streambuffer.hpp"
#include "debugcollector.hpp"
#include "util.hpp"

#include <cassert>
#include <ostream>

struct AnimValue : public LuaValue {
    bool persistent;

    AnimValue() { persistent = false; }

    void set_token(LuaToken token) {
        type = LuaValueType::TOKEN;
        s=token.str(); x=y=z=0;
    }

    void set_dxyz(const util::DXYZ &xyz) {
        type = LuaValueType::VECTOR;
        s.clear(); x = xyz.x; y = xyz.y; z = xyz.z;
    }

    bool is_token(const char *t) const {
        return (type == LuaValueType::TOKEN) && (s == t);
    }
};


class AnimStepEditor
{
private:
    using Map = eng::map<eng::string, AnimValue>;
    Map map_;

    // Set the default value, internal
    //
    eng::string add_default(const eng::string &name, const AnimValue &v, const AnimStepEditor *other);

public:
    // Clear everything
    //
    void clear() { map_.clear(); }

    // Set the persistent flag on a single variable.
    // If the variable isn't present, add it.
    //
    void set_persistent(const eng::string &name);

    // Return if it contains a value for the specified name.
    //
    bool contains(const eng::string &name) { return map_.find(name) != map_.end(); }

    // Set a single variable to a value of a specified type.
    // If the variable isn't present, add it.
    //
    void set_string(const eng::string &name, std::string_view v) { map_[name].set_string(v); }
    void set_token(const eng::string &name, LuaToken v) { map_[name].set_token(v); }
    void set_number(const eng::string &name, double v) { map_[name].set_number(v); }
    void set_boolean(const eng::string &name, bool v) { map_[name].set_boolean(v); }
    void set_dxyz(const eng::string &name, const util::DXYZ &v) { map_[name].set_dxyz(v); }

    // Print a debug string into the stringstream.
    //
    void print_debug_string(eng::ostringstream &oss);
    
    // Return the debug string.
    eng::string debug_string();

    // Constructs an empty state.
    //
    AnimStepEditor() {}

    // Convert to an encoded string.
    //
    eng::string encode() const;

    // Decode an encoded string.
    //
    void decode(std::string_view enc);

    // Decode an encoded string, discarding non-persistent data.
    //
    void decode_persistent(std::string_view enc);

    // Add default values for all builtin persistent variables.
    //
    // For each builtin default (plane, xyz, facing, bp)
    //
    //  - Will generate an error if a value is already present,
    //    but the present value is of the wrong type.
    //
    //  - If 'other' is not nullptr, then we look in 'other' for a default
    //    value.  If a valid value of the correct type is present, it is
    //    used as the default value.
    //
    //  - If no default value can be found in 'other', then a hardwired
    //    default value is provided.
    //
    eng::string add_defaults(const AnimStepEditor *other);
    
    // Parse an animstate from a Lua Table.
    //
    // Table keys must be valid lua identifiers. Table values may be string,
    // floats, bool, or coordinates.  Persistent flags are all set the same,
    // from the persistent parameter. Returns empty string on success, or an
    // error message on failure.
    // 
    // If 'allowauto' is true, then the lua table may contain a key-value
    // pair of the form (key, math.auto).  These keys will be stored in the
    // AnimStepEditor map with mapentry.type == LuaValueType::AUTO.
    // This is done to express an intent that the value should be
    // automatically computer later.
    // 
    eng::string from_lua(LuaCoreStack &LS0, LuaSlot tab, bool persistent, bool allowauto); 

    // Generate a merged animstate using a previous state and an update.
    //
    // Keys from both previous and update are combined to create this AnimStepEditor.
    // Values from 'update' override values from 'previous'.  Persistent flags
    // are taken from 'previous'.  If a key exists in both previous and update,
    // and the key is persistent in 'previous', then the types must match,
    // otherwise an error is generated.  Returns empty string on success or
    // an error message on failure.
    //
    // If a key in the 'update' map has type AUTO, then we will attempt to find
    // a rule to compute that value automatically.  Failure to find a rule
    // results in an error.
    //
    eng::string merge(const AnimStepEditor &previous, const AnimStepEditor &update);

    // Convert an animstate to a lua table.
    //
    // You can specify whether you want to include the transient values, the
    // persistent values, or both.
    //
    void to_lua(LuaCoreStack &LS, LuaSlot tab, bool transient, bool persistent);

    // Parse a string, for unit testing.
    //
    // This parses a simple notation designed to facilitate writing unit tests.
    // The notation looks like this:
    //
    //    parse("plane=earth xyz=1,2,3 action:jump");
    //
    // Determining the type of the value is done as follows: First, try
    // interpreting it as boolean true or false.  If that fails,
    // try interpreting it as a number.  If that fails, try interpreting it as
    // a coordinate.  If all else fails, it's a string.  Obviously, this is a
    // limited approach: for example, there's no way to express the string "123"
    // because "123" will get interpreted as a number.  But that's ok, since this
    // is purely intended for unit testing.
    //
    // Key-value pairs can have either an equal sign or a colon.  If it's an equal
    // sign, the persistent bit is set, colon means not persistent.
    //
    void parse(std::string_view s);
    void clear_and_parse(std::string_view s);

    // Constructor from an encoded string.
    //
    AnimStepEditor(std::string_view s) { decode(s); }
};

struct AnimCoreState
{
    util::DXYZ xyz;
    eng::string plane;

    void decode(std::string_view enc);
};

class AnimQueue : public eng::nevernew {
public:
    // Construct an empty animation queue.
    // clears the state to a valid state.
    //
    AnimQueue();

    // Clear the steps to an initial state.
    //
    void clear();
    void clear(const AnimStepEditor &initial);

    // Change the size limit.
    //
    void set_limit(int limit);

    // Add an animation step.
    //
    // Note: add does not automatically compose the step with the previous
    // step, you have to do that yourself.
    //
    void add(const AnimStepEditor &state);

    // Replace the most recent animation step.
    //
    // Note: replace does not automatically compose the step with the previous
    // step, you have to do that yourself.
    //
    void replace(const AnimStepEditor &state);

    // Serialize or deserialize to a StreamBuffer
    //
    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);

    // Check for exactly equal.  This does the full check of all
    // fields, it is used exclusively for debugging.
    //
    bool exactly_equal(const AnimQueue &aq) const;

    // Check for exactly equal (fast).  Compares the size, limit,
    // and hash of the last entry.  If these are equal, then the whole
    // thing should be equal.
    //
    bool exactly_equal_fast(const AnimQueue &aq) const;

    // Debug strings.
    //
    void print_debug_string(eng::ostringstream &oss, bool full) const;
    eng::string steps_debug_string() const;
    eng::string full_debug_string() const;

    // Get the final entry, xyz and plane only.
    //
    AnimCoreState get_final_core_state() const;

    // Get the final entry, all persistent variables.
    //
    AnimStepEditor get_final_persistent() const;

    // Get the final entry, everything persistent and non-persistent.
    //
    AnimStepEditor get_final_everything() const;
    
    // Get a serialized representation of the animation queue.
    //
    // Get the entire animation queue in a serialized format (encqueue).
    // The string returned is a shared string.  No string copy
    // is made during this process.
    //
    util::SharedStdString get_encoded_queue() const { return encqueue_; }

private:
    struct QueueRange {
        int size;
        std::string_view entries;
        QueueRange(int sz, std::string_view ent) : size(sz), entries(ent) {}
    };

    // Get a range of entries from the queue.
    //
    // You must specify a range (lo-hi) of steps.  In this numbering, 0 is the
    // most recent entry in the queue.  The indices lo and hi are automatically
    // clamped to the valid range (0 to actual_size).  If lo >= hi, then an
    // empty range is returned.
    //
    QueueRange get_range(int lo, int hi);

    // Hash a new step given the range of steps that precede it.
    //
    static int64_t hash_encstep(const QueueRange &prev, std::string_view step);

    // Update the animation queue.
    //
    // The range (keeplo to keephi) specifies which old steps should be
    // retained.  The numbers keephi and keeplo are automatically clamped
    // so that they lie inside the actual size of the queue.
    //
    // If add is true, then an additional step is added to the queue.
    // The hash of the new step is calculated automatically.
    //
    // There is no enforcement that you respected the size limit that you
    // specified.  For example, you could say "keep 0-5, and add 1."  That
    // would make 6 entries in the queue.  It is up to the caller to respect
    // the size limit.  The value passed in is just for reporting.
    // 
    void update_encqueue(int limit, bool add, std::string_view add_enc, int keeplo, int keephi);
    
    // Read values from the header of the encqueue.
    //
    int get_size_limit() const;
    int get_actual_size() const;
    int64_t get_final_hash() const;
    std::string_view get_final_encstep() const;

private:
    // Note: this is stored as a std::string, not an eng::string, because the
    // ownership ends up being shared between us and the graphics engine. We
    // can't have the graphics engine affecting the behavior of the engine heap.
    //
    util::SharedStdString encqueue_;

};


