#include "include/private/dvr/graphics/shader_program.h"

#include <regex>
#include <sstream>

#include <log/log.h>

namespace {

static bool CompileShader(GLuint shader, const std::string& shader_string) {
  std::string prefix;
  const std::string kVersion = "#version";
  if (shader_string.substr(0, kVersion.size()) != kVersion) {
    prefix = "#version 310 es\n";
  }
  std::string string_with_prefix = prefix + shader_string;
  const char* shader_str[] = {string_with_prefix.data()};
  glShaderSource(shader, 1, shader_str, nullptr);
  glCompileShader(shader);

  GLint success;
  glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
  if (!success) {
    GLchar infoLog[512];
    glGetShaderInfoLog(shader, 512, nullptr, infoLog);
    ALOGE("Shader Failed to compile: %s -- %s", *shader_str, infoLog);
    return false;
  }
  return true;
}

static bool LinkProgram(GLuint program, GLuint vertex_shader,
                        GLuint fragment_shader) {
  glAttachShader(program, vertex_shader);
  glAttachShader(program, fragment_shader);
  glLinkProgram(program);

  // Check for linking errors
  GLint success;
  glGetProgramiv(program, GL_LINK_STATUS, &success);
  if (!success) {
    GLchar infoLog[512];
    glGetProgramInfoLog(program, 512, nullptr, infoLog);
    ALOGE("Shader failed to link: %s", infoLog);
    return false;
  }

  return true;
}

static bool LinkProgram(GLuint program, GLuint compute_shader) {
  glAttachShader(program, compute_shader);
  glLinkProgram(program);

  // Check for linking errors
  GLint success;
  glGetProgramiv(program, GL_LINK_STATUS, &success);
  if (!success) {
    GLchar infoLog[512];
    glGetProgramInfoLog(program, 512, nullptr, infoLog);
    ALOGE("Shader failed to link: %s", infoLog);
    return false;
  }

  return true;
}

}  // anonymous namespace

namespace android {
namespace dvr {

ShaderProgram::ShaderProgram() : program_(0) {}

ShaderProgram::ShaderProgram(const std::string& vertext_source,
                             const std::string& fragment_source)
    : program_(0) {
  Link(vertext_source, fragment_source);
}

ShaderProgram::ShaderProgram(ShaderProgram&& to_move) {
  std::swap(program_, to_move.program_);
}

ShaderProgram::~ShaderProgram() {
  if (program_)
    glDeleteProgram(program_);
}

ShaderProgram& ShaderProgram::operator=(ShaderProgram&& to_move) {
  std::swap(program_, to_move.program_);
  return *this;
}

void ShaderProgram::Link(const std::string& vertext_source,
                         const std::string& fragment_source) {
  if (program_)
    glDeleteProgram(program_);
  program_ = glCreateProgram();
  GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER);
  GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);

  bool success = CompileShader(vertex_shader, vertext_source) &&
                 CompileShader(fragment_shader, fragment_source) &&
                 LinkProgram(program_, vertex_shader, fragment_shader);

  glDeleteShader(vertex_shader);
  glDeleteShader(fragment_shader);

  if (!success) {
    glDeleteProgram(program_);
    program_ = 0;
  }
}

void ShaderProgram::Link(const std::string& compute_source) {
  if (program_)
    glDeleteProgram(program_);
  program_ = glCreateProgram();
  GLuint shader = glCreateShader(GL_COMPUTE_SHADER);

  bool success =
      CompileShader(shader, compute_source) && LinkProgram(program_, shader);

  glDeleteShader(shader);

  if (!success) {
    glDeleteProgram(program_);
    program_ = 0;
  }
}

void ShaderProgram::Use() const { glUseProgram(program_); }

std::string ComposeShader(const std::string& shader_code,
                          const std::vector<std::string>& variables) {
  std::stringstream result_stream;
  std::regex expression("%([0-9]*)");
  using reg_iter = std::regex_token_iterator<std::string::const_iterator>;
  reg_iter rend;
  // match the string and number (drop the %)
  std::vector<int> submatches = {-1, 1};
  reg_iter reg(shader_code.begin(), shader_code.end(), expression, submatches);
  bool is_even = true;
  while (reg != rend) {
    if (is_even) {
      // even entries is the code between the %n's
      result_stream << *reg;
    } else {
      // odd entries are the index into the passed in variables.
      size_t i = static_cast<size_t>(std::stoi(*reg));
      if (i < variables.size()) {
        result_stream << variables[i];
      }
    }
    is_even = !is_even;
    ++reg;
  }
  return result_stream.str();
}

}  // namespace dvr
}  // namespace android
