/**
* 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.
*/

#include "engine/cmd.h"
#include "engine/game_env.h"
#include "engine/cmd_receiver.h"

// Derived class. Note that the definition is automatically generated by a python file.
#include "engine/cmd.gen.h"
#include "engine/cmd_specific.gen.h"
#include "cmd_specific.gen.h"

static constexpr float kFlagDist = 0.5;

#define _CREATE(...) receiver->SendCmd(CmdIPtr(new CmdCreate(INVALID, __VA_ARGS__)))
#define _CHANGE_RES(...) receiver->SendCmd(CmdIPtr(new CmdChangePlayerResource(INVALID, __VA_ARGS__)))

bool CmdFlagSetHandicap::run(GameEnv *env, CmdReceiver*) {
    if (_level <= -5) {
        cout << "Handicap level cannot be smaller than -5" << endl;
        return false;
    }
    const int _player_id = 0;
    Units &units = env->GetUnits();
    for (auto it = units.begin(); it != units.end(); ++it) {
        Unit *u = it->second.get();
        if  (u->GetPlayerId() == _player_id) {
            // increase movement speed, attack and health by 20% * _level
            UnitProperty &p = u->GetProperty();
            p._speed = p._speed * (5 + _level) / 5;
            p._att = p._att * (5 + _level) / 5;
            p._max_hp = p._max_hp * (5 + _level) / 5;
            p._hp = p._hp * (5 + _level) / 5;
        }
    }
    return true;
}

bool CmdCaptureFlagGameStart::run(GameEnv *env, CmdReceiver* receiver) {
    const PlayerId player_id = 0;
    const PlayerId enemy_id = 1;
    const PlayerId dummy_id = 2;
    auto f = env->GetRandomFunc();
    auto gen_loc = [&] (int player_id, int i) -> PointF {
        int x = player_id * 15 + i;
        int y = f(2) * 11;
        y += f(9);
        return PointF(x, y);
    };
    env->GenerateImpassable(_num_obstacles);
    _CREATE(FLAG, PointF(9.5, 9.5), dummy_id);
    _CREATE(FLAG_BASE, PointF(1, 9.5), player_id);
    _CREATE(FLAG_BASE, PointF(18, 9.5), enemy_id);
    for (PlayerId player_id = 0; player_id < 2; player_id++) {
        for (int i = 0; i < 5; i++) {
            _CREATE(FLAG_ATHLETE, gen_loc(player_id, i), player_id);
        }
    }
    return true;
}

bool CmdReviveAthlete::run(GameEnv * env, CmdReceiver *receiver) {
    UnitId base_id = env->FindClosestBase(_player_id);
    receiver->SendCmdWithTick(CmdDPtr(new CmdBuild(base_id, FLAG_ATHLETE)), receiver->GetTick() + 100);
    return true;
}

bool CmdPickUpFlag::run(GameEnv * env, CmdReceiver*) {
    Unit *u = env->GetUnit(_id);
    if (u == nullptr) return false;
    const Unit *flag = env->GetUnit(_flag);
    if (flag == nullptr) return false;
    UnitProperty &p = u->GetProperty();
    env->RemoveUnit(_flag);
    // When carrying a flag, movement speed is greatly reduced.
    p._speed /= 3;
    p._has_flag = 1;
    return true;
}

bool CmdScoreFlag::run(GameEnv * env, CmdReceiver* receiver) {
    Unit *u = env->GetUnit(_id);
    if (u == nullptr) return false;
    UnitProperty &p = u->GetProperty();
    // Reset attribute of athletes
    p._speed *= 3;
    p._has_flag = 0;
    PlayerId player_id = u->GetPlayerId();
    _CHANGE_RES(player_id, 1);

    // return flag to the center of arena
    /*const auto& _units =  env->GetUnits();
    for (auto it = _units.begin(); it != _units.end(); ++it) {
        const Unit *u = it->second.get();
        cout << u->GetPointF() << endl;
    }
    */
    auto f = env->GetRandomFunc();
    auto map = env->GetMap();
    while (1) {
        PointF new_p = PointF(9, f(10) + 5);
        if (map.CanPass(new_p, INVALID)) {
            _CREATE(FLAG, new_p, 2);
            break;
        }
    }
    return true;
}

bool CmdGetFlag::run(const GameEnv& env, CmdReceiver *receiver) {
    const Unit *u = env.GetUnit(_id);
    if (u == nullptr) {
        return false;
    }

    const Unit *flag = env.GetUnit(_flag);
    if (flag == nullptr) {
        return false;
    }
    const PointF& flag_p = flag->GetPointF();
    if (micro_move(_tick, *u, env, flag_p, receiver) < kFlagDist) {
        const Unit *flag = env.GetUnit(_flag);
        if (flag == nullptr) {
            return false;
        } else {
            receiver->SendCmd(CmdIPtr(new CmdPickUpFlag(_id, _flag)));
            _done = true;
            return true;
        }
    }
    return true;
}

bool CmdEscortFlagToBase::run(const GameEnv& env, CmdReceiver *receiver) {
    const Unit *u = env.GetUnit(_id);
    if (u == nullptr || u->HasFlag() == 0) return false;

    UnitId base_id = env.FindClosestBase(u->GetPlayerId());
    const Unit *base = env.GetUnit(base_id);
    if (base == nullptr) {
        return false;
    }
    const PointF& base_p = base->GetPointF();
    if (micro_move(_tick, *u, env, base_p, receiver) < kFlagDist) {
        receiver->SendCmd(CmdIPtr(new CmdScoreFlag(_id)));
    }
    return true;
}

#undef _CHANGE_RES
#undef _CREATE
