#include <cmath>
#include <iostream>

#include "Game.h"
#include "MarchingCubes.h"
#include "gaming-core/utils/Array.h"
#include "gaming-core/utils/List.h"
#include "gaming-core/utils/Random.h"
#include "gaming-core/utils/Utils.h"
#include "gaming-core/wrapper/GL.h"

Game::Game(Shader& shader, Shader& noiceShader, LayeredFramebuffer& buffer,
           Buttons& buttons, const Size& size)
    : shader(shader), noiceShader(noiceShader), noiceBuffer(buffer),
      bricks("resources/bricks.png"), bricksBump("resources/bricks_bump.png"),
      bricksNormal("resources/bricks_normal.png"), buttons(buttons), size(size),
      frustum(60, 0.1f, 1000.0f, size), up(GLFW_KEY_SPACE, "Up"),
      down(GLFW_KEY_LEFT_SHIFT, "Down"), left(GLFW_KEY_A, "left"),
      right(GLFW_KEY_D, "right"), front(GLFW_KEY_W, "front"),
      back(GLFW_KEY_S, "back"), toggle(GLFW_KEY_T, "toggle"),
      scaleUp(GLFW_KEY_G, "scale up"), scaleDown(GLFW_KEY_H, "scale down"),
      stepsUp(GLFW_KEY_Y, "steps up"), stepsDown(GLFW_KEY_U, "steps down"),
      fineStepsUp(GLFW_KEY_I, "fine steps up"),
      fineStepsDown(GLFW_KEY_O, "fine steps down"),
      modeToggle(GLFW_KEY_C, "mode toggle"), oldHeight(0.0f), height(0.0f),
      heightScale(0.01f), steps(1), fineSteps(1), mode(false) {
    buttons.add(up);
    buttons.add(down);
    buttons.add(left);
    buttons.add(right);
    buttons.add(front);
    buttons.add(back);
    buttons.add(toggle);
    buttons.add(scaleUp);
    buttons.add(scaleDown);
    buttons.add(stepsUp);
    buttons.add(stepsDown);
    buttons.add(fineStepsUp);
    buttons.add(fineStepsDown);
    buttons.add(modeToggle);

    bricks.setLinearFilter();
    bricksBump.setLinearFilter();
    bricksNormal.setLinearFilter();

    position = Vector3(-32.0f, 0.0f, -120.0f);

    rectangleBuffer.setAttributes(Attributes().addFloat(2));
    float recData[6][2] = {{-1.0f, -1.0f}, {-1.0, 1.0}, {1.0, -1.0},
                           {1.0f, 1.0f},   {-1.0, 1.0}, {1.0, -1.0}};
    rectangleBuffer.setStaticData(sizeof(recData), recData);
    noiceBuffer.bindTextureTo(0);
}

void Game::render(float lag) {
    GL::setViewport(64, 64);
    noiceShader.use();
    noiceBuffer.bindAndClear();
    float step = 1.0f / 31.5f;
    noiceShader.setFloat("height", oldHeight * step);
    for(int i = 0; i < 64; i++) {
        noiceShader.setFloat("layer", i * step - 1.0f);
        noiceBuffer.bindLayer(i);
        rectangleBuffer.draw(6);
    }

    GL::setViewport(size.width, size.height);
    shader.use();
    GL::bindMainFramebuffer();
    GL::clear();
    shader.setMatrix("proj", frustum.updateProjection().getValues());

    Matrix m;
    m.translate(Utils::interpolate(oldPosition, position, lag));
    m.translateY(-32.0f + (oldHeight - height) * lag);
    shader.setMatrix("view", m.getValues());
    shader.setFloat("height", oldHeight * step * 0.5f);

    shader.setVector("viewPos", -position + Vector3(0.0f, 32.0f, 0.0f));
    shader.setVector("lightPos", Vector3());
    shader.setFloat("heightScale", heightScale);
    shader.setInt("steps", steps);
    shader.setInt("fineSteps", fineSteps);
    shader.setInt("kajetan", mode);

    if(toggle.isDown()) {
        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    }
    noiceBuffer.bindTextureTo(0);
    bricks.bindTo(1);
    bricksBump.bindTo(2);
    bricksNormal.bindTo(3);
    emptyBuffer.drawPoints(64 * 64 * 64);
    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}

void Game::tick() {
    StringBuffer<50>()
        .append(steps)
        .append(" ")
        .append(fineSteps)
        .append(" ")
        .append(heightScale)
        .printLine();
    oldHeight = height;
    oldPosition = position;
    if(up.isDown()) {
        height += 1.0f;
    }
    if(down.isDown()) {
        height -= 1.0f;
    }
    const float speed = 1.15f;
    if(left.isDown()) {
        position += Vector3(speed, 0.0f, 0.0f);
    }
    if(right.isDown()) {
        position -= Vector3(speed, 0.0f, 0.0f);
    }
    if(front.isDown()) {
        position += Vector3(0.0f, 0.0f, speed);
    }
    if(back.isDown()) {
        position -= Vector3(0.0f, 0.0f, speed);
    }

    if(scaleUp.isDown()) {
        heightScale += 0.005f;
    }
    if(scaleDown.isDown()) {
        heightScale -= 0.005f;
        if(heightScale < 0.0f) {
            heightScale = 0.0f;
        }
    }

    if(stepsUp.wasReleased()) {
        steps++;
    }
    if(stepsDown.wasReleased() && steps > 1) {
        steps--;
    }
    if(fineStepsUp.wasReleased()) {
        fineSteps++;
    }
    if(fineStepsDown.wasReleased() && fineSteps > 1) {
        fineSteps--;
    }
    if(modeToggle.wasReleased()) {
        mode = !mode;
    }
}

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