#include "client/Game.h"
#include "client/GameClient.h"
#include "client/World.h"
#include "client/gui/StartGUI.h"
#include "client/rendering/Engine.h"
#include "common/network/Packets.h"
#include "common/network/toserver/PlayerUpdatePacket.h"
#include "utils/Logger.h"
#include "utils/Utils.h"

Controller Game::controller;
Entity Game::player;

typedef void (*State)();
static State tickState;
static State renderState;

static BaseGUI baseGUI;
static StartGUI startGUI;

static void tickConnectedState() {
    Game::player.skip = false;
    World::tick();
    GameClient::consumeEvents();

    Quaternion q = Game::player.getRotation();

    Vector3 up(0.0f, 1.0f, 0.0f);
    Vector3 back = q * Vector3(0.0f, 0.0f, -1.0f);
    back[1] = 0.0f;
    back.normalize();
    Vector3 right = back.cross(up);

    constexpr float rotationSpeed = 4.0f;
    Vector3 force;
    if(Game::controller.down.isDown()) {
        force += back;
    }
    if(Game::controller.up.isDown()) {
        force -= back;
    }
    if(Game::controller.left.isDown()) {
        force -= right;
    }
    if(Game::controller.right.isDown()) {
        force += right;
    }
    if(force.squareLength() > 0.0f) {
        force.normalize();
    }
    Game::player.addForce(force * Game::player.speed);

    if(Game::controller.jump.isDown() && Game::player.isOnGround()) {
        Game::player.jump();
    }
    if(Game::controller.camLeft.isDown()) {
        Game::player.addLengthAngle(-rotationSpeed);
    }
    if(Game::controller.camRight.isDown()) {
        Game::player.addLengthAngle(rotationSpeed);
    }
    if(Game::controller.camUp.isDown()) {
        Game::player.addWidthAngle(-rotationSpeed * 0.5f);
    }
    if(Game::controller.camDown.isDown()) {
        Game::player.addWidthAngle(rotationSpeed * 0.5f);
    }

    PlayerUpdatePacket p(Game::player);
    OutPacket out = OutPacket::reliable(PlayerUpdatePacket::getSize());
    p.write(out);
    GameClient::send(out);
}

static void renderConnectedState() {
}

static void tickConnectState() {
    startGUI.tick();
    StartGUI::Address a;
    if(startGUI.getAddress(a)) {
        Error error = GameClient::connect(a, 11196, 3000);
        if(error.has()) {
            LOG_INFO(error.message);
        } else {
            LOG_INFO("connected");
            tickState = tickConnectedState;
            renderState = renderConnectedState;
        }
    }
}

static void renderConnectState() {
    startGUI.render();
}

bool Game::init() {
    Block::init();
    if(World::init()) {
        return true;
    }
    controller.init();
    tickState = tickConnectState;
    renderState = renderConnectState;

    Game::player.setPosition(Vector3(0.0f, 2.0f, 0.0f));
    World::addEntity(&player);
    return false;
}

void Game::tick() {
    tickState();
}

void Game::renderWorld() {
    Engine::matrix.update(player.getRenderPosition(Engine::lag),
                          player.getRenderRotation(Engine::lag));
    World::render();
}

void Game::renderOverlay() {
    renderState();
    Engine::matrix.identity().scale(2.0f).update();
    StringBuffer<100> s;
    s.append("FPS: &074")
        .append(Engine::getFrameClock().getUpdatesPerSecond())
        .append(" &999TPS: &722")
        .append(Engine::getTickClock().getUpdatesPerSecond());
    Engine::renderer.renderString(Vector2(10.0f, 10.0f), s);
    s.clear().append("a = ").append(player.acceleration);
    Engine::renderer.renderString(Vector2(10.0f, 20.0f), s);
    s.clear().append("v = ").append(player.velocity);
    Engine::renderer.renderString(Vector2(10.0f, 30.0f), s);
}

void Game::onEntityUpdate(EntityUpdatePacket& p) {
    LOG_DEBUG("set");
    player.setPosition(p.position);
    player.velocity = p.velocity;
}