
#include "Tflag_low.h"


// -----------------------
// --- le constructeur ---
// -----------------------

Tflag_low::Tflag_low()
{
    m_flag.tex_image = 0;
    //m_flag.tex_bump = 0;
}

// ----------------------
// --- le destructeur ---
// ----------------------

Tflag_low::~Tflag_low()
{
    _clear();
}

// --------------------
// --- les méthodes ---
// --------------------


void Tflag_low::addModel(std::string flux)
{
    std::vector<std::string> strList;
    size_t idx = m_pos.size();

    ECO_strCut(strList, flux, ' ');

    m_pos.push_back(OVec3_f());
    m_rot.push_back(OVec3_f());
    m_scale.push_back(OVec3_f());
    m_model_multi.push_back(OMat4());

    m_flag.model_multi.push_back(OMat4());

    m_pos[idx].x = ECO_strToFloat(strList[0]);
    m_pos[idx].y = ECO_strToFloat(strList[1]);
    m_pos[idx].z = ECO_strToFloat(strList[2]);
    m_rot[idx].x = ECO_strToFloat(strList[3]);
    m_rot[idx].y = ECO_strToFloat(strList[4]);
    m_rot[idx].z = ECO_strToFloat(strList[5]);
    m_scale[idx].x = ECO_strToFloat(strList[6]);
        m_scale[idx].z = m_scale[idx].y = m_scale[idx].x;

    _setModel(idx);
}




void Tflag_low::_clear()
{
    m_pos.clear();
    m_rot.clear();
    m_scale.clear();
    m_model_multi.clear();

    m_flag.model_multi.clear();

    if (m_flag.tex_image > 0) glDeleteTextures(1, &m_flag.tex_image);
    //if (m_flag.tex_bump > 0) glDeleteTextures(1, &m_flag.tex_bump);

    // defini > obj_nbr_vao_buffer
    if (m_flag.vao_buffer[0] != 0) glDeleteBuffers(nbr_vao_buffer, m_flag.vao_buffer);
    if (m_flag.vao != 0) glDeleteVertexArrays(1, &m_flag.vao);
    m_flag.vao = 0;
}

/*
"0000 deco/ hampe_00.obj / 21 -34 1 0 0 0 1 / ... "
*/
void Tflag_low::create(std::string file)
{
    std::vector<std::string> strList;

    ECO_strCut(strList, file, ' ');

    // ---

    _clear();

    m_pos.push_back(OVec3_f());
    m_rot.push_back(OVec3_f());
    m_scale.push_back(OVec3_f());
    m_model_multi.push_back(OMat4());

    m_flag.model_multi.push_back(OMat4());

    // ---

    m_code = strList[0];
	m_path = strList[1];
	m_file = strList[2];
	// 3
	m_pos[0].x = ECO_strToFloat(strList[4]);
	m_pos[0].y = ECO_strToFloat(strList[5]);
	m_pos[0].z = ECO_strToFloat(strList[6]);
	m_rot[0].x = ECO_strToFloat(strList[7]);
	m_rot[0].y = ECO_strToFloat(strList[8]);
	m_rot[0].z = ECO_strToFloat(strList[9]);
    m_scale[0].x = ECO_strToFloat(strList[10]);
        m_scale[0].z = m_scale[0].y = m_scale[0].x;
	// 11
	m_flag.decal_pos.x = ECO_strToFloat(strList[12]);
	m_flag.decal_pos.y = m_flag.decal_pos.x * -2; // ECO_strToFloat(strList[13]); // compensation du decalage de rotation shader
	m_flag.decal_pos.z = ECO_strToFloat(strList[14]);
    // 15
    m_flag.width       = ECO_strToFloat(strList[16]);
    m_flag.height      = ECO_strToFloat(strList[17]);
    m_flag.nbCol       = ECO_strToInt(strList[18]);
    m_flag.nbRow       = ECO_strToInt(strList[19]);
    //
    m_flag.img_tex     = strList[20];
    //m_flag.img_bump    = strList[21];
    // 21

    _composition();
    _setModel(0);

    // ---

    dshFlag.create();

}

void Tflag_low::_composition()
{
    std::string file;

    OMesh_obj::P_setPath(PATH_MESH);

    if (m_file.find(".obj"))
    {
        if (m_path != "null")
        {
            file = m_path + m_file;
        }
        else
        {
            file = m_file;
        }
        if (!mesh.create(file))
        {
            ECO_error_set("echec chargement %s >Tflag_low::_composition", m_file.c_str());
            return;
        }
    }
    else
    {
        ECO_error_set("erreur fichier %s >Tflag_low", m_file.c_str());
        return;
    }

    // ---

    _generate_flag();
    _make_vao();
}





void Tflag_low::draw(const OMat4& vp, ODshGeo& dshGeo)
{
    static float time = 0.0f;

    glDisable(GL_CULL_FACE);

    // --- pole

    dshGeo.enable();
    dshGeo.setMulti(true);

    for (size_t i_par = 0; i_par < mesh.getPart_size(); i_par++)
    {
        dshGeo.setTextures(mesh.getId_diffuseMap(i_par), mesh.getId_normalMap(i_par), mesh.getId_specularMap(i_par) ) ;
        mesh.draw_multi(i_par);
    }
    mesh.end_draw_part();

    dshGeo.setMulti(false);

    // --- flag

    time++;

    dshFlag.enable();
    dshFlag.setVP(vp);
    dshFlag.setTime(time);
    dshFlag.setTexture(m_flag.tex_image);

    // toujours multi / instanced
    glBindVertexArray(m_flag.vao);
        glDrawElementsInstanced(GL_TRIANGLES, m_flag.indice.size(), GL_UNSIGNED_INT, 0, m_flag.model_multi.size() );
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}





void Tflag_low::_generate_flag()
{
    if (m_flag.tex_image > 0) glDeleteTextures(1, &m_flag.tex_image);
    //if (m_flag.tex_bump > 0) glDeleteTextures(1, &m_flag.tex_bump);

    m_flag.vertex.clear();
    m_flag.normal.clear();
    m_flag.uv.clear();
    m_flag.indice.clear();

    if (m_flag.nbCol < 4 || m_flag.nbRow < 2)
    {
        ECO_error_set("err nbCol nbRow >Tflag_low.generate_flag");
        m_flag.nbCol = 4;
        m_flag.nbRow = 2;
    }
    if (m_flag.width < 0.2f || m_flag.height < 0.2f)
    {
        ECO_error_set("err width height >Tflag_low.generate_flag");
        m_flag.width = 0.2f;
        m_flag.height = 0.2f;
    }

    const float dx = m_flag.width  / (m_flag.nbCol - 1);
    const float dz = m_flag.height / (m_flag.nbRow - 1);
    //const int vertexCount = m_flag.nbCol * m_flag.nbRow;

    // ======================
    // FACE AVANT (+Y)
    // ======================
    for (int row = 0; row < m_flag.nbRow; ++row) {
        for (int col = 0; col < m_flag.nbCol; ++col) {

            float x = col * dx;
            float z = row * dz;

            m_flag.vertex.push_back({ x, 0.0f, z });
            //m_flag.normal.push_back({ 0.0f, 1.0f, 0.0f }); // verticale y
            m_flag.normal.push_back({ 0.0f, 0.0f, 1.0f });

            float u = (float)col / (m_flag.nbCol - 1);
            float v = (float)row / (m_flag.nbRow - 1);
            m_flag.uv.push_back({ u, v });
        }
    }

    // m_flag.indice face avant (CCW)
    for (int row = 0; row < m_flag.nbRow - 1; ++row) {
        for (int col = 0; col < m_flag.nbCol - 1; ++col) {

            int i0 =  row      * m_flag.nbCol + col;
            int i1 =  row      * m_flag.nbCol + col + 1;
            int i2 = (row + 1) * m_flag.nbCol + col;
            int i3 = (row + 1) * m_flag.nbCol + col + 1;

            m_flag.indice.push_back(i0);
            m_flag.indice.push_back(i2);
            m_flag.indice.push_back(i1);

            m_flag.indice.push_back(i1);
            m_flag.indice.push_back(i2);
            m_flag.indice.push_back(i3);
        }
    }

    // ======================
    // FACE ARRIÈRE (–Y)
    // ======================
    /*
    const int offset = vertexCount;

    for (int i = 0; i < vertexCount; ++i) {
        m_flag.vertex.push_back(m_flag.vertex[i]);
        //m_flag.normal.push_back({ 0.0f, -1.0f, 0.0f }); // verticale y
        m_flag.normal.push_back({ 0.0f, 0.0f, -1.0f });
        m_flag.uv.push_back(m_flag.uv[i]);
    }

    // m_flag.indice face arrière (CW)
    const int indexCountFront = m_flag.indice.size();

    for (int i = 0; i < indexCountFront; i += 3) {
        int i0 = m_flag.indice[i];
        int i1 = m_flag.indice[i + 1];
        int i2 = m_flag.indice[i + 2];

        // winding inversé
        m_flag.indice.push_back(i0 + offset);
        m_flag.indice.push_back(i2 + offset);
        m_flag.indice.push_back(i1 + offset);
    }
*/
    // ---

    std::string file0, file1;
    if (m_path != "null")
    {
        file0 = PATH_MESH + m_path + m_flag.img_tex;
        //file1 = PATH_MESH + m_path + m_flag.img_bump;
    }
    else
    {
        file0 = PATH_MESH + m_flag.img_tex;
        //file1 = PATH_MESH + m_flag.img_bump;
    }

    m_flag.tex_image = _load_texture(file0);
	//m_flag.tex_bump  = _load_texture(file1);

}





uint32_t Tflag_low::_load_texture(std::string file)
{
	OSurface surface;
    uint32_t texture;

	surface.load(file);
	if (!surface.exist())
	{
		ECO_error_set("echec chargement %s >Tflag_low.load_texture", file.c_str());
		return 0;
	}

    //activation des textures
    glEnable(GL_TEXTURE_2D);
    //if (texture) glDeleteTextures(1,&texture);
    //generation de la texture
    glGenTextures(1, &texture);
    //selection de la texture
    glBindTexture(GL_TEXTURE_2D, texture);
	// génération
	glTexImage2D(GL_TEXTURE_2D, 0, surface.getFormat(), surface.getWidth(), surface.getHeight(), 0, surface.getFormat(), GL_UNSIGNED_BYTE, surface.getPixels() );
	// on applique les filtres usuels
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

	//déverrouillage libération
    glBindTexture(GL_TEXTURE_2D, 0);

    return texture;
}




void Tflag_low::_make_vao()
{
    //génération de l'identifiant du VAO
    glGenVertexArrays(1, &m_flag.vao);
    //activation du VAO
    glBindVertexArray(m_flag.vao);

    // créations des buffers
    glGenBuffers(nbr_vao_buffer, m_flag.vao_buffer);

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[0]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(OVec3_f) * m_flag.vertex.size(), &m_flag.vertex[0], GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(LOCATION_POSITION);
        glVertexAttribPointer(LOCATION_POSITION, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[1]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(OVec3_f) * m_flag.normal.size(), &m_flag.normal[0], GL_DYNAMIC_DRAW);
        glEnableVertexAttribArray(LOCATION_NORMAL);
        glVertexAttribPointer(LOCATION_NORMAL, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[2]);
        glBufferData(GL_ARRAY_BUFFER, sizeof(OVec2_f) * m_flag.uv.size(), &m_flag.uv[0], GL_STATIC_DRAW);
        glEnableVertexAttribArray(LOCATION_TEXCOORD);
        glVertexAttribPointer(LOCATION_TEXCOORD, 2, GL_FLOAT, GL_FALSE, 0, 0);

    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[3]);
        for (int i = 0; i < 4 ; i++)
        {
            glEnableVertexAttribArray(LOCATION_MODEL + i);
            glVertexAttribPointer(LOCATION_MODEL + i, 4, GL_FLOAT, GL_FALSE, sizeof(OMat4), (const void*)(sizeof(float) * i * 4));
            glVertexAttribDivisor(LOCATION_MODEL + i, 1);
        }

    //---

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_flag.vao_buffer[7]);
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(unsigned) * m_flag.indice.size(), &m_flag.indice[0], GL_STATIC_DRAW);

    // ---

    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);
}




void Tflag_low::_setModel(size_t idx)
{
    OMat4 model;

    model.loadIdentity();
    model.translate(m_pos[idx]);
    model.rotate(m_rot[idx].z, 0, 0, 1);
    model.rotate(m_rot[idx].y, 0, 1, 0);
    model.rotate(m_rot[idx].x, 1, 0, 0);
    model.scale(m_scale[idx]);

    m_model_multi[idx] = model;
    m_model_multi[idx].transpose(); // car ma matrice est horizontale

    mesh.setModelMulti(m_model_multi); // passe au vao du mesh

    // ---

    m_flag.model_multi[idx] = model;
    m_flag.model_multi[idx].translate(m_flag.decal_pos);
    m_flag.model_multi[idx].transpose();

    // = setModelMulti
    glBindVertexArray(m_flag.vao);
    glBindBuffer(GL_ARRAY_BUFFER, m_flag.vao_buffer[LOCATION_MODEL]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(OMat4) * m_flag.model_multi.size(), &m_flag.model_multi[0], GL_DYNAMIC_DRAW);
    //désactivation du VAO
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindVertexArray(0);

}

void Tflag_low::setWind(float speed, float dir)
{
    dshFlag.enable();
        dshFlag.setWind(speed, dir);
    dshFlag.disable();
}

// --------------
// --- shader ---
// --------------


static const char* pVS = " \n\
#version 330 core \n\
 \n\
layout (location = 0) in vec3 in_vertex; \n\
layout (location = 1) in vec3 in_normal; \n\
layout (location = 2) in vec2 in_uv; \n\
layout (location = 3) in mat4 in_model; \n\
// in_model = 3 4 5 6 \n\
 \n\
uniform mat4  u_vp; \n\
uniform float u_time; \n\
uniform float u_wind_speed = 20.0; // km/h \n\
uniform float u_wind_dir = 0.0; // radians \n\
uniform float u_amplitudefactor; \n\
 \n\
out vec2 var_uv; \n\
out vec3 var_normal; \n\
out vec3 var_model; \n\
 \n\
// toujours multi instanced \n\
void main() \n\
{ \n\
    vec3 pos = in_vertex; \n\
    float s = sin(u_wind_dir);\n\
    float c = cos(u_wind_dir);\n\
    mat2 rot = mat2(c, -s, s, c);\n\
    pos.xy = rot * pos.xy; // z verticale \n\
    // y doit etre egal à x * -2 \n\
\n\
    float amplitude = (in_uv.x*0.05) * u_wind_speed; \n\
    // Phase propagée le long du drapeau \n\
    float time = u_time * (u_wind_speed*0.5); \n\
    float wave = \n\
        sin(pos.x * 5.0 - time * 3.0) \n\
        + 0.5 * sin(pos.x * 10.0 - time * 5.0); \n\
    // Déplacement principal (hors plan) \n\
    pos.y += wave * amplitude * u_amplitudefactor; \n\
    // Légère torsion verticale (z) \n\
    pos.z += amplitude * 0.01; \n\
    vec4 worldPos = in_model * vec4(pos, 1.0); \n\
\n\
    gl_Position = u_vp * worldPos; \n\
    \n\
    var_uv = in_uv; \n\
    var_model = worldPos.xyz; \n\
    //var_normal = (in_model * vec4(in_normal, 0.0)).xyz; \n\
    // gere le scale de in_model \n\
    mat3 normalMatrix = transpose(inverse(mat3(in_model))); \n\
    var_normal = normalize(normalMatrix * in_normal); \n\
}";


static const char* pFS = " \n\
#version 330 core \n\
 \n\
in vec2 var_uv; \n\
in vec3 var_normal; \n\
in vec3 var_model; \n\
 \n\
layout (location = 0) out vec3 positionMap; \n\
layout (location = 1) out vec4 colorMap; \n\
layout (location = 2) out vec3 normalMap; \n\
 \n\
uniform sampler2D u_colorMap; \n\
 \n\
void main(){ \n\
    vec4 color = texture(u_colorMap, var_uv); \n\
	float alpha = smoothstep(0.6, 0.95, color.a); \n\
    if (alpha < 0.05) discard; \n\
    \n\
    positionMap = var_model; \n\
    color.a = 0.5; \n\
    colorMap = color; \n\
    normalMap = var_normal; \n\
}";



TDshFlag_low::TDshFlag_low()
{
	//
}

bool TDshFlag_low::create()
{
    if (!ODshader::init())
    {
        ECO_error_set("echec init TDshFlag_low");
        return false;
    }
    if (!addShader(GL_VERTEX_SHADER, pVS))
    {
        ECO_error_set("echec add vertex TDshFlag_low");
        return false;
    }
    if (!addShader(GL_FRAGMENT_SHADER, pFS))
    {
        ECO_error_set("echec add fragment TDshFlag_low");
        return false;
    }
    if (!finalize())
    {
        ECO_error_set("echec finalize TDshFlag_low");
        return false;
    }

    m_vp = getUniformLocation("u_vp");
    m_colorMap = getUniformLocation("u_colorMap");
    m_time = getUniformLocation("u_time");
    m_wind_speed = getUniformLocation("u_wind_speed");
    m_wind_dir = getUniformLocation("u_wind_dir");
    m_amplitude_factor = getUniformLocation("u_amplitudefactor");

/*
    // phase de test
	if (m_vp == INVALID_UNIFORM_LOCATION
    || m_model == INVALID_UNIFORM_LOCATION
    || m_colorMap == INVALID_UNIFORM_LOCATION
    || m_time == INVALID_UNIFORM_LOCATION
    || m_wind_speed == INVALID_UNIFORM_LOCATION
    || m_wind_dir == INVALID_UNIFORM_LOCATION
    )
    {
        ECO_error_set("Echec TDshFlag_low");
        return false;
    }
*/
    return true;
}

void TDshFlag_low::setVP(const OMat4& vp)
{
    glUniformMatrix4fv(m_vp, 1, GL_TRUE, vp.getFv());
}

void TDshFlag_low::setTexture(uint32_t texture)
{
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(m_colorMap, 0);

    //glActiveTexture(GL_TEXTURE0);
}

// +1.0f
void TDshFlag_low::setTime(float value)
{
    glUniform1f(m_time, value*0.01f);
}

void TDshFlag_low::setWind(float speed, float dir)
{
    glUniform1f(m_wind_speed, speed);
    glUniform1f(m_wind_dir, (360.0f - dir) * ECO_DEGTORAD);
    glUniform1f(m_amplitude_factor, 5.0f/speed);
}

void TDshFlag_low::setAmplitude_factor(float value)
{
    glUniform1f(m_amplitude_factor, value);
}
