#pragma once

/*
 * Common code for texture interactions in morph programs
 *
 * Note: You have to include a header like gl3.h or glext.h etc for the GL types and
 * functions BEFORE including this file.
 *
 * Author: Seb James.
 */

#include <morph/vec.h>
#include <morph/gl/util_nomx.h>
#include <iostream>
#include <stdexcept>

namespace morph {
    namespace gl {

        // Set up a single texture suitable for filling with values within the
        // compute shader. Note: fixed format of GL_RGBA and GL_FLOAT; could set these
        // with template params.
        void setup_texture (const GLuint image_texture_unit, unsigned int& texture_id, morph::vec<GLsizei, 2> dims)
        {
            glGenTextures (1, &texture_id); // generate a texture name and place it in texture_id
            // Bind a texture (GL_TEXTURE_2D) to the texture name
            glBindTexture (GL_TEXTURE_2D, texture_id);
            // Set some parameters for the texture
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            // In 3.1 ES, GL_INVALID_OPERATION is generated by glBindImageTexture if texture_id is not the name of
            // an *immutable* texture object. Calling glTexStorage2D seems to create an immutable texture object.
            glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGBA32F, dims[0], dims[1]);
            morph::gl::Util::checkError (__FILE__, __LINE__);
            glBindImageTexture (image_texture_unit, texture_id, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
            morph::gl::Util::checkError (__FILE__, __LINE__);
        }

        // Set up a shader-read-only texture with the provided rgb image data
        template <bool gles = false>
        void setup_texture (const GLuint image_texture_unit, unsigned int& texture_id,
                            morph::vec<GLsizei, 2> dims, float* rgb_data)
        {
            if constexpr (gles == false) {
                // This may not be perfect, but seemed to work with a non-GLES context
                glGenTextures (1, &texture_id);
                glBindTexture (GL_TEXTURE_2D, texture_id);
                // Because we WRITING image data to this texture, we HAVE TO gtActiveTexture():
                glActiveTexture (GL_TEXTURE0+image_texture_unit);
                glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
                glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                /////////////////////////////// internal format                   pixel format
                glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA32F, dims[0], dims[1], 0, GL_RGB, GL_FLOAT, rgb_data);
                glBindImageTexture (image_texture_unit, texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
                morph::gl::Util::checkError (__FILE__, __LINE__);
            } else {
                throw std::runtime_error ("Fix setup_texture (const GLuint, unsigned int&, morph::vec<GLsizei,2>, float* rgb_data) to work on OpenGL ES");
            }
        }

#ifdef I_HAD_FIGURED_OUT_SETUP_TEXTURE_WITH_DATA_PROPERLY
# define USE_IMMUTABLE_STORAGE 1 // Should use immutable texture storage in order to use glBindImageTexture

        // Set up a shader-read-only texture with the provided rgb image data.
        // Just can't figure out how to make this work in GL 3.1 ES!
        void setup_texture (const GLuint image_texture_unit, unsigned int& texture_id,
                            morph::vec<GLsizei, 2> dims, float* rgb_data)
        {
            std::cout << "setup_texture for READ_ONLY access of an image in shader\n";

            glGenTextures (1, &texture_id); // Generates texture name, placing it in texture_id.
            std::cout << "After glGenTextures, we have texture_id: " << texture_id
                      << " and image_texture_unit is " << image_texture_unit << std::endl;

            // Bind a texture (GL_TEXTURE_2D) to the texture name
            glBindTexture (GL_TEXTURE_2D, texture_id);

# ifndef USE_IMMUTABLE_STORAGE
            // Because we WRITING image data to this texture, we HAVE TO glActiveTexture():
            glActiveTexture (GL_TEXTURE0+image_texture_unit);
# endif
            // Set some parameters
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            morph::gl::Util::checkError (__FILE__, __LINE__);

# ifndef USE_IMMUTABLE_STORAGE
            // This creates a *mutable* texture containing rgb_data
            glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB32F, dims[0], dims[1], 0, GL_RGB, GL_FLOAT, rgb_data);
# else
            // Create *immutable* storage like this, but I *don't know how to subsequently transfer the rgb_data to the texture*.
            glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGB32F, dims[0], dims[1]);
# endif
            morph::gl::Util::checkError (__FILE__, __LINE__);

            GLint imstatus;
            glGetTexParameteriv (GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &imstatus);
            std::cout << "IMMUTABLE_TEXTURE?: " << (imstatus?"Yes! immutable":"No, mutable") << std::endl;

# ifdef USE_IMMUTABLE_STORAGE
            // Transferring/loading data has to do with glBindImageTexture (See the spec.)

            // GL_INVALID_VALUE if:
            // "unit greater than or equal to the value of GL_MAX_IMAGE_UNITS."   Well, unit is 4, GL_MAX_IMAGE_UNITS is 128 or 8.
            // "level or layer is less than zero."                                Both are 0, below.
            // "texture is not the name of an existing texture object"            I filled texture_id with glGenTextures, above.
            // So WHY DO I STILL GET GL_INVALID_VALUE?????
            glBindImageTexture (image_texture_unit, texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGB32F); // GL_INVALID_VALUE
            morph::gl::Util::checkError (__FILE__, __LINE__);
# endif
        }

        // Set up a shader-read-only texture with the provided rgb image data.  This is an
        // attempt to create one mutable texture, and an immutable one and copy the texture from
        // the mutable to the immutable. Doesn't work.
        void setup_texture_alt (const GLuint image_texture_unit, unsigned int& texture_id,
                                morph::vec<GLsizei, 2> dims, float* rgb_data)
        {
            std::cout << "setup_texture for READ_ONLY access of an image in shader\n";

            unsigned int texture_id_tmp = 0;
            glGenTextures (1, &texture_id_tmp);

            glGenTextures (1, &texture_id); // Generates texture name, placing it in texture_id.
            std::cout << "After glGenTextures, we have texture_id: " << texture_id
                      << " and image_texture_unit is " << image_texture_unit << std::endl;

            GLuint image_texture_unit_tmp = image_texture_unit + 10;

            // Bind a texture (GL_TEXTURE_2D) to the texture name
            glBindTexture (GL_TEXTURE_2D, texture_id_tmp);
            morph::gl::Util::checkError (__FILE__, __LINE__);

            // Set some parameters for GL_TEXTURE_2D
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            morph::gl::Util::checkError (__FILE__, __LINE__);

            // Because we WRITING some image data to the temporary texture, we HAVE TO glActiveTexture():
            glActiveTexture (GL_TEXTURE0+image_texture_unit_tmp);
            morph::gl::Util::checkError (__FILE__, __LINE__);
            // This creates a *mutable* texture containing rgb_data for the active texture
            glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB32F, dims[0], dims[1], 0, GL_RGB, GL_FLOAT, rgb_data);
            morph::gl::Util::checkError (__FILE__, __LINE__);

            // Switch to the other texture
            glBindTexture (GL_TEXTURE_2D, texture_id);
            morph::gl::Util::checkError (__FILE__, __LINE__);
            glActiveTexture (GL_TEXTURE0+image_texture_unit);
            morph::gl::Util::checkError (__FILE__, __LINE__);

            GLint imstatus;
            glGetTexParameteriv (GL_TEXTURE_2D, GL_TEXTURE_IMMUTABLE_FORMAT, &imstatus);
            std::cout << "IMMUTABLE_TEXTURE?: " << (imstatus?"Yes! immutable":"No, mutable") << std::endl;

            // Create *immutable* storage like this, but I *don't know how to subsequently transfer the rgb_data to the texture*.
            glTexStorage2D (GL_TEXTURE_2D, 1, GL_RGB32F, dims[0], dims[1]);
            morph::gl::Util::checkError (__FILE__, __LINE__);

            // Transferring/loading data has to do with glBindImageTexture (See the spec.)

            // GL_INVALID_VALUE if:
            // "unit greater than or equal to the value of GL_MAX_IMAGE_UNITS."   Well, unit is 4, GL_MAX_IMAGE_UNITS is 128 or 8.
            // "level or layer is less than zero."                                Both are 0, below.
            // "texture is not the name of an existing texture object"            I filled texture_id with glGenTextures, above.
            // So WHY DO I STILL GET GL_INVALID_VALUE?????
            glBindImageTexture (image_texture_unit_tmp, texture_id, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGB32F); // GL_INVALID_VALUE
            morph::gl::Util::checkError (__FILE__, __LINE__);
        }
#endif // I_HAD_FIGURED_OUT_SETUP_TEXTURE_WITH_DATA_PROPERLY

    } // namespace gl
} // namespace morph
