#ifndef PROGRAM
#define PROGRAM

#include <fstream>
#include <iostream>
#include <string>

#include <GL/gl.h>

class Program
{
  public:
    Program()
    {
    }

    ~Program()
    {
        glDeleteProgram(m_prog);
    }

    void createProgram(const char *vs, const char *fs)
    {
        GLuint vert = createShader(vs, GL_VERTEX_SHADER);
        GLuint frag = createShader(fs, GL_FRAGMENT_SHADER);

        if ((vert == 0) || (frag == 0))
        {
            return;
        }

        m_prog = glCreateProgram();
        glAttachShader(m_prog, vert);
        glAttachShader(m_prog, frag);
        glLinkProgram(m_prog);

        glDeleteShader(vert);
        glDeleteShader(frag);
    }

    void createProgram(const char *vs, const char *gs, const char *fs)
    {
        GLuint vert = createShader(vs, GL_VERTEX_SHADER);
        GLuint geom = createShader(gs, GL_GEOMETRY_SHADER);
        GLuint frag = createShader(fs, GL_FRAGMENT_SHADER);

        if ((vert == 0) || (frag == 0) || (geom == 0))
        {
            return;
        }

        m_prog = glCreateProgram();
        glAttachShader(m_prog, vert);
        glAttachShader(m_prog, geom);
        glAttachShader(m_prog, frag);
        glLinkProgram(m_prog);

        glDeleteShader(vert);
        glDeleteShader(geom);
        glDeleteShader(frag);
    }

    void createProgram(const std::string vs, const std::string fs)
    {
        createProgram(vs.c_str(), fs.c_str());
    }

    GLuint getProgram()
    {
        return m_prog;
    }

  private:
    inline std::string loadShaderText(const char *path)
    {
        std::ifstream is(path);
        std::string s_save;

        if (is.is_open())
        {
            s_save.assign(std::istreambuf_iterator<char>(is), std::istreambuf_iterator<char>());
            is.close();
        }
        else
        {
            std::cout << "could not open " << path << std::endl;
        }

        return s_save;
    }

    inline GLuint createShader(const char *path, GLenum shadertype)
    {
        std::string s_source = loadShaderText(path);

        if (s_source.empty())
        {
            return 0;
        }

        const char *c_source = s_source.c_str();

        GLuint shader = glCreateShader(shadertype);
        glShaderSource(shader, 1, (const char **)&c_source, NULL);
        glCompileShader(shader);

        GLint shaderCompiled = GL_FALSE;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &shaderCompiled);
        if (shaderCompiled != GL_TRUE)
        {
            GLint log_length = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);

            char *log = new char[log_length + 1];
            log[log_length] = '\0';

            glGetShaderInfoLog(shader, (GLsizei)log_length, NULL, log);

            std::cout << "error compiling shader( " << path << " ): " << std::endl
                      << log << std::endl;

            delete[] log;

            glDeleteShader(shader);
            shader = 0;
        }

        return shader;
    }

    GLuint m_prog;
};

#endif // PROGRAM
