#include "Mesh.h"
#include <glm/gtc/type_ptr.hpp>
#include <glm/vec3.hpp>
#include <iostream>

Mesh::Mesh(ShaderProgram* shader, std::size_t size) : GLObject(shader),
                                                      _size(size),
                                                      _xs(size),
                                                      _ys(size),
                                                      _normals(2*(size-1)*(size-1)) {
	_vertexCount = _size*_size;
	_indexCount = (_size - 1)*(_size - 1)*6;
	_vertices = std::make_unique<Vertex[]>(_vertexCount);
	_indices = std::make_unique<GLuint[]>(_indexCount);
}

Mesh::~Mesh() {

}

void Mesh::generateVertices() const {
    for(std::size_t j = 0; j < _size; ++j) {
        for (std::size_t i = 0; i < _size; ++i) {
            size_t idx = j*_size + i;
            _vertices[idx].x = (float)i/_size - 0.5f;
            _vertices[idx].y = (float)j/_size - 0.5f;
            _vertices[idx].z = 0.f;
            _vertices[idx].nx = 0.f;
            _vertices[idx].ny = 0.f;
            _vertices[idx].nz = 0.f;
        }
    }
}

inline ptrdiff_t Mesh::normalIndex(ptrdiff_t x, ptrdiff_t y) const {
    return  2*(x + y*(_size - 1));
}

void Mesh::updateVertices() const {
    for(std::size_t j = 0; j < _size; ++j) {
        for(std::size_t i = 0; i < _size; ++i) {
            std::size_t idx = j*_size + i;
            _vertices[idx].z = heightMap(i, j);
        }
    }

    // 1. Calc normals for all triangles
    _normals.clear();
    for(std::size_t j = 0; j < _size - 1; ++j) {
        for(std::size_t i = 0; i < _size - 1; ++i) {
            float x = _xs[i], x2 = _xs[i + 1];
            float y = _ys[j], y2 = _ys[j + 1];

            glm::vec3 p1(x, y, _vertices[j*(_size - 1) + i].z);
            glm::vec3 p2(x2, y, _vertices[j*(_size - 1) + i + 1].z);
            glm::vec3 p3(x, y2, _vertices[(j + 1)*(_size - 1) + i].z);
            glm::vec3 p4(x2, y2, _vertices[(j + 1)*(_size - 1) + i + 1].z);

            glm::vec3 v1 = p1 - p3;
            glm::vec3 v2 = p2 - p3;
            glm::vec3 v3 = p4 - p3;

            glm::vec3 norm1 = glm::cross(v2, v1);
            glm::vec3 norm2 = glm::cross(v3, v2);

            _normals.emplace_back(norm1);
            _normals.emplace_back(norm2);

//            std::cout << "normal: [" << norm1.x << ", " << norm1.y << ", " << norm1.z << "]" << std::endl;
//            std::cout << "normal: [" << norm2.x << ", " << norm2.y << ", " << norm2.z << "]" << std::endl;
//
//            if(norm1.z > 0 || norm2.z > 0) {
//                std::cout << "j = " << j << ", i = " << i << std::endl;
//            }
        }
    }

    // 2. Calc normals for all vertices based on precalculated normals for triangles
    //    Normal of vertex is an average of normals of adjacent triangles
    size_t normalsCount = 2*(_size - 1)*(_size - 1);
    for(std::size_t j = 0; j < _size; ++j) {
        for (std::size_t i = 0; i < _size; ++i) {
            glm::vec3 normal;

            ptrdiff_t idx1 = normalIndex(i - 1, j - 1);
            for(ptrdiff_t k = idx1; k < idx1 + 3; ++k) {
                if(k >= 0 && k < normalsCount) {
                    normal += _normals[k];
                }
            }

            ptrdiff_t idx2 = normalIndex(i - 1, j);
            for(ptrdiff_t k = idx2; k < idx2 + 3; ++k) {
                if(k >= 0 && k < normalsCount) {
                    normal += _normals[k];
                }
            }

//            if(normal.z == 0) {
//                std::cout << "j = " << j << ", i = " << i << std::endl;
//            }
//
//            std::cout << "normal: [" << normal.x << ", " << normal.y << ", " << normal.z << "]" << std::endl;

            size_t idx = j*_size + i;
            _vertices[idx].nx = normal.x;
            _vertices[idx].ny = normal.y;
            _vertices[idx].nz = normal.z;
        }
    }
}

void Mesh::generateIndices() const {
	GLuint u = (GLuint)_size, v = (GLuint)_size;
	for (unsigned i = 0; i < (u - 1); i++)
		for (unsigned j = 0; j < (v - 1); j++)
		{
            GLuint indexa = j*(u - 1) + i;
            GLuint indexb = j*u + i;

			_indices[indexa*6 + 0] = indexb;
			_indices[indexa*6 + 1] = indexb + 1 + u;
			_indices[indexa*6 + 2] = indexb + 1;

			_indices[indexa*6 + 3] = indexb;
			_indices[indexa*6 + 4] = indexb + u;
			_indices[indexa*6 + 5] = indexb + u + 1;
		}
}
