/**
* 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_RECEIVER_H_
#define _CMD_RECEIVER_H_

#include "cmd.h"
#include "ui_cmd.h"
#include "game_stats.h"

#include "pq_extend.h"
#include <map>
#include <functional>
// #include "Selene.h"

// receive command and record them in the history.
class CmdReceiver;
class GameEnv;

// A command is either durative or immediate.
// For immediate command, it has immediate effects.
// For durative command, it will send downstream sub-commands
//
custom_enum(CmdReturn, CMD_SUCCESS = 0, CMD_FAILED, CMD_QUEUE_RELOADED);

typedef int VerboseChoice;
#define CR_NO_VERBOSE 0
#define CR_DURATIVE 1
#define CR_IMMEDIATE 2
#define CR_DERIVED 4
#define CR_NODERIVED 8
#define CR_ALL ( CR_DURATIVE | CR_IMMEDIATE | CR_DERIVED | CR_NODERIVED )

// receive command and record them in the history.
// The cmds are ordered so that the lowest level is executed first.
class CmdReceiver {
private:
    Tick _tick;
    int _cmd_next_id;

    GameStats _stats;

    p_queue<CmdIPtr> _immediate_cmd_queue;
    p_queue<CmdDPtr> _durative_cmd_queue;

    vector<CmdBPtr> _cmd_history;

    // Record current state of each unit. Note that this pointer does not own anything.
    // When the command is destroyed, we should manually delete the entry as well.
    map<UnitId, CmdDurative *> _unit_durative_cmd;

    // Use to dump SendCmd.
    unique_ptr<ostream> _cmd_dumper;

    // Whether we save the current issued command to the history buffer.
    bool _save_to_history;

    // For player id, talk a bit more.
    // id == INVALID means verbose to all players.
    int _verbose_player_id;
    VerboseChoice _verbose_choice;
    bool _path_planning_verbose;
    bool _use_cmd_comment;

    template <typename CmdType>
    bool show_prompt_cond(const string &prompt, const unique_ptr<CmdType> &cmd, bool force_verbose = false) const {
        if (force_verbose) {
            cout << prompt << " " << cmd->PrintInfo() << endl;
            return true;
        }
        /* if (_verbose_choice == CR_NO_VERBOSE) return false;
           if (_verbose_player_id == INVALID || player_id == _verbose_player_id) {
           if ( (_verbose_choice & CR_DURATIVE) && ! _cmd_template.IsDurativeCmd(cmd.cmd)) return false;
           if ( (_verbose_choice & CR_IMMEDIATE) && ! _cmd_template.IsImmediateCmd(cmd.cmd)) return false;
           cout << "[" << player_id << "] " << prompt << ": " << cmd << endl;
           return true;
           }
        */
        else return false;
    }

public:
    CmdReceiver()
        : _tick(0), _cmd_next_id(0), _cmd_dumper(nullptr), _save_to_history(true),
          _verbose_player_id(INVALID), _verbose_choice(CR_NO_VERBOSE), _path_planning_verbose(false), _use_cmd_comment(false)  {
    }

    const GameStats &GetGameStats() const { return _stats; }
    GameStats &GetGameStats() { return _stats; }

    Tick GetTick() const { return _tick; }
    Tick GetNextTick() const { return _tick + 1; }
    inline void IncTick() { _tick ++; _stats.IncTick(); }
    inline void ResetTick() { _tick = 0; _stats.Reset(); }

    void SetCmdDumper(const string &cmd_dumper_filename);

    void SetUseCmdComment(bool use_comment) { _use_cmd_comment = use_comment; }
    bool GetUseCmdComment() const { return _use_cmd_comment; }
    void SetPathPlanningVerbose(bool verbose) { _path_planning_verbose = verbose; }
    bool GetPathPlanningVerbose() const { return _path_planning_verbose; }

    void SetVerbose(VerboseChoice choice, PlayerId player_id) {
        _verbose_choice = choice;
        _verbose_player_id = player_id;
    }
    void ClearCmd() {
        while (! _immediate_cmd_queue.empty()) _immediate_cmd_queue.pop();
        while (! _durative_cmd_queue.empty()) _durative_cmd_queue.pop();
        _cmd_history.clear();
        _unit_durative_cmd.clear();
        _cmd_next_id = 0;
    }

    // Send command with the current tick.
    bool SendCmd(CmdBPtr &&cmd);
    // Send command with a specific tick.
    bool SendCmdWithTick(CmdBPtr &&cmd, Tick tick);

    // Set this to be true to prevent any command to be recorded in the history.
    void SetSaveToHistory(bool v) { _save_to_history = v; }
    bool IsSaveToHistory() const { return _save_to_history; }

    // Start a durative cmd specified by the pointer.
    bool StartDurativeCmd(CmdDurative *);

    // Finish the durative command for unit id.
    bool FinishDurativeCmd(UnitId id);
    bool FinishDurativeCmdIfDone(UnitId id);

    const CmdDurative *GetUnitDurativeCmd(UnitId id) const;
    vector<CmdDurative*> GetHistoryAtCurrentTick() const;

    // Save replay to a file
    bool SaveReplay(const string& replay_filename) const;

    // Execute Durative Commands. This will not change the game environment.
    void ExecuteDurativeCmds(const GameEnv &env, bool force_verbose);
    // Execute Immediate Commands. This will change the game environment.
    void ExecuteImmediateCmds(GameEnv *env, bool force_verbose);

    // CmdReceiver has its specialized Save and Load function.
    // No SERIALIZER(...) is needed.
    void SaveCmdReceiver(serializer::saver &saver) const;
    void LoadCmdReceiver(serializer::loader &loader);

    ~CmdReceiver() { }
};

#endif
