/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.

* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree.
*/

#ifndef _CMD_H_
#define _CMD_H_

#include "common.h"
#include <sstream>

/*  Common commands for all games, specified in cmd.def.
    Header is generated by a python file in cmd_type.inl and cmd.inl
    Note that all commands will have access to current unit's id, and other information provided in the arguments.

    CMD_IMMEDIATE(TacticalMove, PointF, p);
        Move a unit to a target point p.
    CMD_IMMEDIATE(Create, UnitType, build_type, PointF, p, PlayerId, player_id, int, resource_used = 0);
        Create a unit at a point p, for player_id and use resource_used.
    CMD_IMMEDIATE(Remove);
        Remove current unit.
    CMD_IMMEDIATE(EmitBullet, UnitId, target, PointF, p, int, att, float, speed);
        Emit a bullet at target at point f. Used by ranged attack.
        When the bullet reaches target, it issues a melee attack.
    CMD_IMMEDIATE(SaveMap, std::string, s);
        Save map.
    CMD_IMMEDIATE(LoadMap, std::string, s);
        Load map.
    CMD_IMMEDIATE(RandomSeed, uint64_t, seed);
        Set random seed.
    CMD_IMMEDIATE(Comment, std::string, comment);
        Send a comment to p=replay file.
    CMD_IMMEDIATE(CDStart, CDType, cd_type);
        Start cooldown for cd_type.
*/
typedef int CmdType;

#define INVALID_CMD -1
#define CMD_BASE 0

class CmdReceiver;
class GameEnv;

// Base class for commands.
class CmdBase {
protected:
    int _cmd_id;
    Tick _tick, _start_tick;

    // Main id.
    UnitId _id;

public:
    explicit CmdBase(UnitId iid = INVALID) {
        _tick = _start_tick = INVALID;
        _id = iid;
        _cmd_id = -1;
    }
    explicit CmdBase(Tick t, UnitId iid) : CmdBase(iid) {
        _tick = _start_tick = t;
    }

    Tick tick() const { return _tick; }
    Tick start_tick() const { return _start_tick; }
    void set_tick_and_start_tick(Tick t) { _tick = _start_tick = t; }
    UnitId id() const { return _id; }
    void set_id(UnitId id) { _id = id; }
    void set_cmd_id(int i) { _cmd_id = i; }

    virtual std::unique_ptr<CmdBase> clone() const { return std::unique_ptr<CmdBase>(new CmdBase(*this)); }
    virtual CmdType type() const { return CMD_BASE; }

    virtual string PrintInfo() const {
        std::stringstream ss;
        ss << "[id:" << _id << "][" << type() << "][" << _start_tick << ":" << _tick << "]";
        return ss.str();
    }

#define SMALLER_FIRST(field) if ((c1.field) > (c2.field)) return true; if ((c1.field) < (c2.field)) return false;
#define LARGER_FIRST(field) if ((c1.field) < (c2.field)) return true; if ((c1.field) > (c2.field)) return false;

    // For ordering.
    friend bool operator<(const CmdBase &c1, const CmdBase &c2) {
        // since priority_queue will always pop the largest element, we swap the meaning of <.
        // Note that we need to make sure Cmd has total order, since priority_queue is not stable.
        //
        // First make sure most recent command ranked first.
        SMALLER_FIRST(_tick);
        // For the same set of command, make sure the most recent command ranks first.
        LARGER_FIRST(_start_tick);

        SMALLER_FIRST(_cmd_id);
        return false;
    }
#undef SMALLER_FIRST
#undef LARGER_FIRST

    virtual ~CmdBase() { }

    SERIALIZER_BASE(CmdBase, _tick, _start_tick, _id, _cmd_id);
    SERIALIZER_ANCHOR(CmdBase);
    UNIQUE_PTR_COMPARE(CmdBase);
};

// Durative commands. They do not directly change the game state, but issue other immediate commands.
class CmdDurative : public CmdBase {
protected:
    bool _done;
    virtual bool run(const GameEnv&, CmdReceiver *) { return true; }

public:
    explicit CmdDurative(UnitId id = INVALID) : CmdBase(id), _done(false) { }
    explicit CmdDurative(Tick t, UnitId id) : CmdBase(t, id), _done(false) { }

    // Check whether this command is done. If so, it will be removed from the current queue.
    bool IsDone() const { return _done; }

    // When other command interrupts the current command, they call SetDone.
    void SetDone() { _done = true; }
    bool Run(const GameEnv& env, CmdReceiver *);

    string PrintInfo() const override {
        std::stringstream ss;
        ss << this->CmdBase::PrintInfo() << "[" << (_done ? "Done" : "Running") << "]";
        return ss.str();
    }

    virtual ~CmdDurative() { }

    SERIALIZER_DERIVED(CmdDurative, CmdBase, _done);
    SERIALIZER_ANCHOR(CmdDurative);
    UNIQUE_PTR_COMPARE(CmdDurative);
};

// / immediate commands. They will directly change the game state.
class CmdImmediate : public CmdBase {
protected:
    virtual bool run(GameEnv*, CmdReceiver *) { return true; }

public:
    explicit CmdImmediate(UnitId id = INVALID) : CmdBase(id) { }
    explicit CmdImmediate(Tick t, UnitId id) : CmdBase(t, id) { }
    bool Run(GameEnv* env, CmdReceiver *receiver){ return run(env, receiver); }

    virtual ~CmdImmediate() { }

    SERIALIZER_DERIVED0(CmdImmediate, CmdBase);
    SERIALIZER_ANCHOR(CmdImmediate);
    UNIQUE_PTR_COMPARE(CmdImmediate);
};

typedef unique_ptr<CmdBase> CmdBPtr;
typedef unique_ptr<CmdDurative> CmdDPtr;
typedef unique_ptr<CmdImmediate> CmdIPtr;
typedef map<UnitId, CmdBPtr> AssignedCmds;

class CmdTypeLookup {
private:
    static std::map<std::string, int> _name2idx;
    static std::map<int, std::string> _idx2name;
    static std::string _null;
    static std::mutex _mutex;

public:
    static void RegCmdType(CmdType type, const std::string &name) {
        std::lock_guard<std::mutex> lock(_mutex);
        _name2idx.insert(make_pair(name, type));
        _idx2name.insert(make_pair(type, name));
    }

    static const std::string &idx2str(CmdType type) {
        std::lock_guard<std::mutex> lock(_mutex);
        auto it = _idx2name.find(type);
        if (it != _idx2name.end()) {
            return it->second;
        } else {
            return _null;
        }
    }

    static CmdType str2idx(const std::string &name) {
        std::lock_guard<std::mutex> lock(_mutex);
        auto it = _name2idx.find(name);
        if (it != _name2idx.end()) {
            return it->second;
        } else {
            return INVALID_CMD;
        }
    }
};

class Unit;
float micro_move(Tick tick, const Unit& u, const GameEnv &env, const PointF& target, CmdReceiver *receiver);

constexpr float kDistEps = 1e-3;

#include "common.h"
#include "gamedef.h"

#endif
