#include <sstream>

#include "client/Game.h"
#include "client/utils/Utils.h"
#include "rendering/Renderer.h"

Game::Game(const Control& control, const Camera& camera, Ray& ray, const Clock& fps, const Clock& tps,
        RenderSettings& renderSettings) :
control(control), camera(camera), ray(ray), fps(fps), tps(tps), renderSettings(renderSettings), lengthAngle(20.0f),
widthAngle(35.0f), texture("resources/textures.png") {
    for(int x = -6; x <= 6; x++) {
        for(int y = -6; y <= 6; y++) {
            for(int z = -6; z <= 6; z++) {
                if(x * x + y * y + z * z < 16) {
                    addCube(x, y, z,
                            x * x + (y - 1) * (y - 1) + z * z >= 16,
                            x * x + (y + 1) * (y + 1) + z * z >= 16 && !(x == 0 && y == 3 && z == 0),
                            (x - 1) * (x - 1) + y * y + z * z >= 16,
                            (x + 1) * (x + 1) + y * y + z * z >= 16,
                            x * x + y * y + (z + 1) * (z + 1) >= 16,
                            x * x + y * y + (z - 1) * (z - 1) >= 16);
                }
            }
        }
    }
    addCube(0, 4, 0, false, true, true, true, true, true);
    pos.set(0, 10, 0);
    m.build();
}

void Game::addCube(float x, float y, float z, bool bottom, bool top, bool left, bool right, bool front, bool back) {
    const float ERROR = 1.0f / 1024.0f;
    if(bottom) {
        m.add( {x - ERROR, y - ERROR, z - ERROR, 0.125f, 0.0f, 0, -1, 0});
        m.add( {x + 1 + ERROR, y - ERROR, z - ERROR, 0.1875f, 0.0f, 0, -1, 0});
        m.add( {x - ERROR, y - ERROR, z + 1 + ERROR, 0.125f, 0.0625f, 0, -1, 0});

        m.add( {x + 1 + ERROR, y - ERROR, z - ERROR, 0.1875f, 0.0f, 0, -1, 0});
        m.add( {x + 1 + ERROR, y - ERROR, z + 1 + ERROR, 0.1875f, 0.0625f, 0, -1, 0});
        m.add( {x - ERROR, y - ERROR, z + 1 + ERROR, 0.125f, 0.0625f, 0, -1, 0});
    }
    if(top) {
        m.add( {x - ERROR, y + 1 + ERROR, z - ERROR, 0.25f, 0.0f, 0, 1, 0});
        m.add( {x - ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.25f, 0.0625f, 0, 1, 0});
        m.add( {x + 1 + ERROR, y + 1 + ERROR, z - ERROR, 0.3125f, 0.0f, 0, 1, 0});

        m.add( {x + 1 + ERROR, y + 1 + ERROR, z - ERROR, 0.3125f, 0.0f, 0, 1, 0});
        m.add( {x - ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.25f, 0.0625f, 0, 1, 0});
        m.add( {x + 1 + ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.3125f, 0.0625f, 0, 1, 0});
    }
    if(left) {
        m.add( {x - ERROR, y - ERROR, z - ERROR, 0.1875f, 0.0625f, -1, 0, 0});
        m.add( {x - ERROR, y - ERROR, z + 1 + ERROR, 0.25f, 0.0625f, -1, 0, 0});
        m.add( {x - ERROR, y + 1 + ERROR, z - ERROR, 0.1875f, 0.0f, -1, 0, 0});

        m.add( {x - ERROR, y - ERROR, z + 1 + ERROR, 0.25f, 0.0625f, -1, 0, 0});
        m.add( {x - ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.25f, 0.0f, -1, 0, 0});
        m.add( {x - ERROR, y + 1 + ERROR, z - ERROR, 0.1875f, 0.0f, -1, 0, 0});
    }
    if(right) {
        m.add( {x + 1 + ERROR, y - ERROR, z - ERROR, 0.25f, 0.0625f, 1, 0, 0});
        m.add( {x + 1 + ERROR, y + 1 + ERROR, z - ERROR, 0.25f, 0.0f, 1, 0, 0});
        m.add( {x + 1 + ERROR, y - ERROR, z + 1 + ERROR, 0.1875f, 0.0625f, 1, 0, 0});

        m.add( {x + 1 + ERROR, y - ERROR, z + 1 + ERROR, 0.1875f, 0.0625f, 1, 0, 0});
        m.add( {x + 1 + ERROR, y + 1 + ERROR, z - ERROR, 0.25f, 0.0f, 1, 0, 0});
        m.add( {x + 1 + ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.1875f, 0.0f, 1, 0, 0});
    }
    if(front) {
        m.add( {x - ERROR, y - ERROR, z + 1 + ERROR, 0.1875f, 0.0625f, 0, 0, 1});
        m.add( {x + 1 + ERROR, y - ERROR, z + 1 + ERROR, 0.25f, 0.0625f, 0, 0, 1});
        m.add( {x - ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.1875f, 0.0f, 0, 0, 1});

        m.add( {x + 1 + ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.25f, 0.0f, 0, 0, 1});
        m.add( {x - ERROR, y + 1 + ERROR, z + 1 + ERROR, 0.1875f, 0.0f, 0, 0, 1});
        m.add( {x + 1 + ERROR, y - ERROR, z + 1 + ERROR, 0.25f, 0.0625f, 0, 0, 1});
    }
    if(back) {
        m.add( {x - ERROR, y - ERROR, z - ERROR, 0.25f, 0.0625f, 0, 0, -1});
        m.add( {x - ERROR, y + 1 + ERROR, z - ERROR, 0.25f, 0.0f, 0, 0, -1});
        m.add( {x + 1 + ERROR, y - ERROR, z - ERROR, 0.1875f, 0.0625f, 0, 0, -1});

        m.add( {x + 1 + ERROR, y + 1 + ERROR, z - ERROR, 0.1875f, 0.0f, 0, 0, -1});
        m.add( {x + 1 + ERROR, y - ERROR, z - ERROR, 0.1875f, 0.0625f, 0, 0, -1});
        m.add( {x - ERROR, y + 1 + ERROR, z - ERROR, 0.25f, 0.0f, 0, 0, -1});
    }
}

float testAngle = 0.0f;

void Game::tick() {
    const float speed = 0.25f;
    if(control.keys.down.isDown()) {
        pos.addMul(camera.getFlatBack(), speed);
    }
    if(control.keys.up.isDown()) {
        pos.addMul(camera.getFlatBack(), -speed);
    }
    if(control.keys.left.isDown()) {
        pos.addMul(camera.getFlatRight(), -speed);
    }
    if(control.keys.right.isDown()) {
        pos.addMul(camera.getFlatRight(), speed);
    }
    if(control.keys.jump.isDown()) {
        pos.addMul(camera.getFlatUp(), speed);
    }
    if(control.keys.sneak.isDown()) {
        pos.addMul(camera.getFlatUp(), -speed);
    }

    const float rotation = 5.0f;
    if(control.keys.camLeft.isDown()) {
        lengthAngle += rotation;
    }
    if(control.keys.camRight.isDown()) {
        lengthAngle -= rotation;
    }
    if(control.keys.camUp.isDown() && widthAngle - rotation > -90.0f) {
        widthAngle -= rotation;
    }
    if(control.keys.camDown.isDown() && widthAngle + rotation < 90.0f) {
        widthAngle += rotation;
    }

    ray.store();
    ray.set(pos, lengthAngle, widthAngle);

    if(control.keys.test5.getDownTime() == 1) {
        renderSettings.shadows = !renderSettings.shadows;
    }
    /*if(control.keys.test.isDown()) {
        renderSettings.testRadius /= 0.95f;
    }
    if(control.keys.test2.isDown()) {
        renderSettings.testRadius *= 0.95f;
    }
    if(control.keys.test3.isDown()) {
        renderSettings.testBias /= 0.95f;
    }
    if(control.keys.test4.isDown()) {
        renderSettings.testBias *= 0.95f;
    }*/

    testAngle += 5.0;
}

void Game::renderWorld(float lag, Renderer& renderer) const {
    (void) lag;
    (void) renderer;
    renderer.translateTo(0.0f, 0.0f, -0.5f)
            .rotateY(interpolate(lag, testAngle - 5.0, testAngle))
            .translate(-0.5f, 0.0f, -0.5f)
            .update();
    texture.bind(0);
    m.draw();
}

void Game::renderTextOverlay(float lag, Renderer& renderer, FontRenderer& fr) const {
    (void) lag;
    renderer.scale(1.0f).update();

    char buffer[50];
    snprintf(buffer, 50, "FPS: %.2f", fps.getUpdatesPerSecond());
    fr.drawString(10, 10, buffer);
    snprintf(buffer, 50, "TPS: %.2f", tps.getUpdatesPerSecond());
    fr.drawString(10, 20, buffer);
    snprintf(buffer, 50, "Bias: %.6f", renderSettings.testBias);
    fr.drawString(10, 30, buffer);
    snprintf(buffer, 50, "Radius: %.6f", renderSettings.testRadius);
    fr.drawString(10, 40, buffer);
    snprintf(buffer, 50, "Shadows: %d", renderSettings.shadows);
    fr.drawString(10, 50, buffer);
}

bool Game::isRunning() const {
    return true;
}