// mesh.h  Klassen Mesh und Model mit Einlesen von Dateien

#pragma once
#include <vector>
#include <fstream>

#include "libs/glm/glm.hpp"
#include "libs/glm/gtx/rotate_vector.hpp"
#include "shader.h"
#include "vertex_buffer.h"
#include "index_buffer.h"
#include "kleinkram.h"

struct Material {
 glm::vec3 ambient;
 glm::vec3 diffuse;
 glm::vec3 specular;
 glm::vec3 emissive;
 float shininess;
 int texturflag=0;
 GLuint diffuseMap=0; //Textur die mit diffuse multipliziert wird (0=keine Textur)
 GLuint normalMap=0; //TODO: Textur fuer Normalen-Vektor
};

struct MaterialOhneTextur {
 glm::vec3 ambient;
 glm::vec3 diffuse;
 glm::vec3 specular;
 glm::vec3 emissive;
 float shininess;
};

struct oldMaterial { //zum Einlesen von alten BMF-Dateien noch benoetigt
 glm::vec3 diffuse;
 glm::vec3 specular;
 glm::vec3 emissive;
 float shininess;
};

class Mesh {
private:
 VertexBuffer* vertexBuffer;
 IndexBuffer* indexBuffer;
 Shader* shader;
 Material material;
 int linienflag;
 uint64 numIndices;
 int ambientLocation;
 int diffuseLocation;
 int specularLocation;
 int emissiveLocation;
 int shininessLocation;
 int texturflagLocation;
 int diffuseMapLocation;
 int normalMapLocation;
public:
 Mesh(std::vector<Vertex>& vertices, uint64 numVertices, std::vector<uint32>& indices,
      uint64 numIndices, Material material, Shader* shader, int linienflag=0) {
  this->linienflag = linienflag;
  this->material = material;
  this->shader = shader;
  this->numIndices = numIndices;
  
  vertexBuffer = new VertexBuffer(vertices.data(), numVertices);
  indexBuffer = new IndexBuffer(indices.data(), numIndices, sizeof(indices[0]));

  ambientLocation = glGetUniformLocation(shader->getShaderId(), "u_material.ambient");
  diffuseLocation = glGetUniformLocation(shader->getShaderId(), "u_material.diffuse");
  specularLocation = glGetUniformLocation(shader->getShaderId(), "u_material.specular");
  emissiveLocation = glGetUniformLocation(shader->getShaderId(), "u_material.emissive");
  shininessLocation = glGetUniformLocation(shader->getShaderId(), "u_material.shininess");
  texturflagLocation = glGetUniformLocation(shader->getShaderId(), "u_material.texturflag");
  diffuseMapLocation = glGetUniformLocation(shader->getShaderId(), "u_diffuse_map");
  normalMapLocation = glGetUniformLocation(shader->getShaderId(), "u_normal_map");//TODO
 }

 ~Mesh() {
  delete vertexBuffer;
  delete indexBuffer;
 }
 
 inline void render() {
  vertexBuffer->bind();
  indexBuffer->bind();
  glUniform3fv(ambientLocation, 1, (float*)&material.ambient);
  glUniform3fv(diffuseLocation, 1, (float*)&material.diffuse);
  glUniform3fv(specularLocation, 1, (float*)&material.specular);
  glUniform3fv(emissiveLocation, 1, (float*)&material.emissive);
  glUniform1f(shininessLocation, material.shininess);
  glUniform1i(texturflagLocation, material.texturflag);
  if(material.texturflag&1)
   {
    glActiveTexture(GL_TEXTURE0);//TODO
    glBindTexture(GL_TEXTURE_2D, material.diffuseMap);
    glUniform1i(diffuseMapLocation, 0);
   }
  if(material.texturflag&2) //TODO
   {
    glActiveTexture(GL_TEXTURE1);//TODO
    glBindTexture(GL_TEXTURE_2D, material.normalMap);
    glUniform1i(normalMapLocation, 1);
    //TODO: Unterstuetzung im Shader und tangent bitangent (was ist das?)
   }
  if(linienflag)
   {
    glLineWidth(liniendicke); //je nach Bildschirmaufloesung 1.0, 2.0 oder 4.0
    glDrawElements(GL_LINE_STRIP, numIndices, GL_UNSIGNED_INT, 0);
   }
  else glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, 0);
 }
};

class Modeldata;
class Klon;

class Model {
private:
 uint64 numMeshes;
 std::vector<Mesh*> meshes;
public:
 Model() {numMeshes=0;}
 void init(const char* filename, Shader *shader, uint32 meshId);
 void init2(Modeldata* modeldata, Shader *shader);
 //void init3(Modeldata* modeldata, Klon* klon, Shader *shader);

 void render() {
  //for(Mesh* mesh : meshes) {mesh->render();}
  for(uint64 i=0; i<numMeshes; i++) {meshes[i]->render();}
 }

 ~Model() {
  for(Mesh* mesh : meshes) {delete mesh;}
 }
 
 void animateFigur(const char* datastart, Shader *shader,
		   uint32 figurId, uint32 frameId, glm::vec3 animOffset, float animRotate);
};

void Model::init(const char* filename, Shader *shader, uint32 meshId)
{
  //wenn meshId>0 dann nur diesen Mesh einlesen, sonst alle
  std::ifstream input = std::ifstream(filename, std::ios::in | std::ios::binary);
  if(!input.is_open()) {printf("Error in Model::init(\"%s\", ...)\n",filename); return;}
  printf("Model::init(%s, ...)\n",filename);//test

  uint64 numMesh;
  input.read((char*)&numMesh, sizeof(uint64));

  for(uint64 j=0; j<numMesh; j++)
   {
    Material material;
    std::vector<Vertex> vertices;
    uint64 numVertices; //Anzahl Vertexe vom aktuellen Mesh
    std::vector<uint32> indices;
    uint64 numIndices; //Anzahl Indexe
  
    input.read((char*)&material, sizeof(Material));
    input.read((char*)&numVertices, sizeof(uint64));
    input.read((char*)&numIndices, sizeof(uint64));
    for(uint64 i=0; i<numVertices; i++)
	 {
	  Vertex vertex;
	  input.read((char*)&vertex.position.x, sizeof(float));
	  input.read((char*)&vertex.position.y, sizeof(float));
	  input.read((char*)&vertex.position.z, sizeof(float));
	  input.read((char*)&vertex.normal.x, sizeof(float));
	  input.read((char*)&vertex.normal.y, sizeof(float));
	  input.read((char*)&vertex.normal.z, sizeof(float));
	  vertices.push_back(vertex);
	 }
    for(uint64 i=0; i<numIndices; i++)
	 {
	  uint32 index;
	  input.read((char*)&index, sizeof(uint32));
	  indices.push_back(index);
	 }
    if(meshId==0 || meshId==j+1)
     {
      Mesh* mesh = new Mesh(vertices, numVertices, indices, numIndices, material, shader);
      meshes.push_back(mesh);
      numMeshes++;
     }
   }
}

void Model::animateFigur(const char* datastart, Shader *shader,
		   uint32 figurId, uint32 frameId, glm::vec3 animOffset, float animRotate)
{
 if(figurId<1 || figurId>numMeshes)
   {printf("Error in animateFigur() figurId=%d numMeshes=%ld\n",figurId,(long)numMeshes); return;}
 uint64 id=figurId-1;
 delete meshes[id];
 uint64 numMesh; //Anzahl Meshes in dieser Datei, es soll nur das mit frameId geladen werden

 const char *data = datastart;
 data_read(data, (char*)&numMesh, sizeof(uint64));
 for(uint64 j=0; j<numMesh; j++)
   {
    Material material;
    std::vector<Vertex> vertices;
    uint64 numVertices; //Anzahl Vertexe vom aktuellen Mesh
    std::vector<uint32> indices;
    uint64 numIndices; //Anzahl Indexe
    
    data_read(data, (char*)&material, sizeof(Material));
    data_read(data, (char*)&numVertices, sizeof(uint64));
    //printf("numVertices=%ld\n",(long)numVertices);//test
    data_read(data, (char*)&numIndices, sizeof(uint64));
    //printf("numIndices=%ld\n",(long)numIndices);//test
    for(uint64 i=0; i<numVertices; i++)
     {
      Vertex vertex;
      data_read(data, (char*)&vertex.position.x, sizeof(float));
      data_read(data, (char*)&vertex.position.y, sizeof(float));
      data_read(data, (char*)&vertex.position.z, sizeof(float));
      data_read(data, (char*)&vertex.normal.x, sizeof(float));
      data_read(data, (char*)&vertex.normal.y, sizeof(float));
      data_read(data, (char*)&vertex.normal.z, sizeof(float));
      if(j==frameId)
       {
	vertex.normal = rotateY(vertex.normal, animRotate);
	vertex.position = rotateY(vertex.position, animRotate);
	vertex.position += animOffset;
	vertices.push_back(vertex);
       }
     }
    for(uint64 i=0; i<numIndices; i++)
     {
      uint32 index;
      data_read(data, (char*)&index, sizeof(uint32));
      if(j==frameId) indices.push_back(index);
     }
    if(j==frameId)
     {
      meshes[id] = new Mesh(vertices, numVertices, indices, numIndices, material, shader);
     }
   }
}
