/*
 * Copyright © 2015 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Authored by: Daniel van Vugt <daniel.van.vugt@canonical.com>
 */

#include "anbox/graphics/program_family.h"

#include "anbox/graphics/emugl/DispatchTables.h"

#include <string>
#include <stdexcept>

namespace anbox::graphics {
void ProgramFamily::Shader::init(GLenum type, const GLchar* src) {
  if (!id) {
    id = s_gles2.glCreateShader(type);
    if (id) {
      s_gles2.glShaderSource(id, 1, &src, NULL);
      s_gles2.glCompileShader(id);
      GLint ok;
      s_gles2.glGetShaderiv(id, GL_COMPILE_STATUS, &ok);
      if (!ok) {
        GLchar log[1024];
        s_gles2.glGetShaderInfoLog(id, sizeof log - 1, NULL, log);
        log[sizeof log - 1] = '\0';
        s_gles2.glDeleteShader(id);
        id = 0;
        throw std::runtime_error(std::string("Compile failed: ") + log +
                                 " for:\n" + src);
      }
    }
  }
}

ProgramFamily::~ProgramFamily() noexcept {
  // shader and program lifetimes are managed manually, so that we don't
  // need any reference counting or to worry about how many copy constructions
  // might have been followed by destructor calls during container resizes.

  for (auto& p : program) {
    if (p.second.id) s_gles2.glDeleteProgram(p.second.id);
  }

  for (auto& v : vshader) {
    if (v.second.id) s_gles2.glDeleteShader(v.second.id);
  }

  for (auto& f : fshader) {
    if (f.second.id) s_gles2.glDeleteShader(f.second.id);
  }
}

GLuint ProgramFamily::add_program(const GLchar* const vshader_src,
                                  const GLchar* const fshader_src) {
  auto& v = vshader[vshader_src];
  if (!v.id) v.init(GL_VERTEX_SHADER, vshader_src);

  auto& f = fshader[fshader_src];
  if (!f.id) f.init(GL_FRAGMENT_SHADER, fshader_src);

  auto& p = program[{v.id, f.id}];
  if (!p.id) {
    p.id = s_gles2.glCreateProgram();
    s_gles2.glAttachShader(p.id, v.id);
    s_gles2.glAttachShader(p.id, f.id);
    s_gles2.glLinkProgram(p.id);
    GLint ok;
    s_gles2.glGetProgramiv(p.id, GL_LINK_STATUS, &ok);
    if (!ok) {
      GLchar log[1024];
      s_gles2.glGetProgramInfoLog(p.id, sizeof log - 1, NULL, log);
      log[sizeof log - 1] = '\0';
      s_gles2.glDeleteShader(p.id);
      p.id = 0;
      throw std::runtime_error(std::string("Link failed: ") + log);
    }
  }

  return p.id;
}
}