//----------------------------------------------------------------------------//
//                                                                            //
// ozz-animation is hosted at http://github.com/guillaumeblanc/ozz-animation  //
// and distributed under the MIT License (MIT).                               //
//                                                                            //
// Copyright (c) Guillaume Blanc                                              //
//                                                                            //
// Permission is hereby granted, free of charge, to any person obtaining a    //
// copy of this software and associated documentation files (the "Software"), //
// to deal in the Software without restriction, including without limitation  //
// the rights to use, copy, modify, merge, publish, distribute, sublicense,   //
// and/or sell copies of the Software, and to permit persons to whom the      //
// Software is furnished to do so, subject to the following conditions:       //
//                                                                            //
// The above copyright notice and this permission notice shall be included in //
// all copies or substantial portions of the Software.                        //
//                                                                            //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   //
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL    //
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING    //
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER        //
// DEALINGS IN THE SOFTWARE.                                                  //
//                                                                            //
//----------------------------------------------------------------------------//

#define OZZ_INCLUDE_PRIVATE_HEADER  // Allows to include private headers.

#include "shader.h"

#include <cassert>
#include <cstdio>
#include <limits>

#include "ozz/base/log.h"
#include "ozz/base/maths/simd_math.h"

namespace ozz {
namespace sample {
namespace internal {

#ifdef EMSCRIPTEN
// WebGL requires to specify floating point precision
static const char* kPlatformSpecivicVSHeader = "#version 300 es\n precision mediump float;\n";
static const char* kPlatformSpecivicFSHeader = "#version 300 es\n precision mediump float;\n";
#else   // EMSCRIPTEN
static const char* kPlatformSpecivicVSHeader = "#version 330\n";
static const char* kPlatformSpecivicFSHeader = "#version 330\n";
#endif  // EMSCRIPTEN

Shader::Shader() : program_(0), vertex_(0), fragment_(0) {}

Shader::~Shader() {
  if (vertex_) {
    GL(DetachShader(program_, vertex_));
    GL(DeleteShader(vertex_));
  }
  if (fragment_) {
    GL(DetachShader(program_, fragment_));
    GL(DeleteShader(fragment_));
  }
  if (program_) {
    GL(DeleteProgram(program_));
  }
}

namespace {
GLuint CompileShader(GLenum _type, int _count, const char** _src) {
  GLuint shader = glCreateShader(_type);
  GL(ShaderSource(shader, _count, _src, nullptr));
  GL(CompileShader(shader));

  int infolog_length = 0;
  GL(GetShaderiv(shader, GL_INFO_LOG_LENGTH, &infolog_length));
  if (infolog_length > 1) {
    char* info_log = reinterpret_cast<char*>(
        memory::default_allocator()->Allocate(infolog_length, alignof(char)));
    int chars_written = 0;
    glGetShaderInfoLog(shader, infolog_length, &chars_written, info_log);
    log::Err() << info_log << std::endl;
    memory::default_allocator()->Deallocate(info_log);
  }

  int status;
  GL(GetShaderiv(shader, GL_COMPILE_STATUS, &status));
  if (status) {
    return shader;
  }

  GL(DeleteShader(shader));
  return 0;
}
}  // namespace

bool Shader::BuildFromSource(int _vertex_count, const char** _vertex,
                             int _fragment_count, const char** _fragment) {
  // Tries to compile shaders.
  GLuint vertex_shader = 0;
  if (_vertex) {
    vertex_shader = CompileShader(GL_VERTEX_SHADER, _vertex_count, _vertex);
    if (!vertex_shader) {
      return false;
    }
  }
  GLuint fragment_shader = 0;
  if (_fragment) {
    fragment_shader =
        CompileShader(GL_FRAGMENT_SHADER, _fragment_count, _fragment);
    if (!fragment_shader) {
      if (vertex_shader) {
        GL(DeleteShader(vertex_shader));
      }
      return false;
    }
  }

  // Shaders are compiled, builds program.
  program_ = glCreateProgram();
  vertex_ = vertex_shader;
  fragment_ = fragment_shader;
  GL(AttachShader(program_, vertex_shader));
  GL(AttachShader(program_, fragment_shader));
  GL(LinkProgram(program_));

  int infolog_length = 0;
  GL(GetProgramiv(program_, GL_INFO_LOG_LENGTH, &infolog_length));
  if (infolog_length > 1) {
    char* info_log = reinterpret_cast<char*>(
        memory::default_allocator()->Allocate(infolog_length, alignof(char)));
    int chars_written = 0;
    glGetProgramInfoLog(program_, infolog_length, &chars_written, info_log);
    log::Err() << info_log << std::endl;
    memory::default_allocator()->Deallocate(info_log);
  }

  return true;
}

bool Shader::BindUniform(const char* _semantic) {
  if (!program_) {
    return false;
  }
  GLint location = glGetUniformLocation(program_, _semantic);
  if (glGetError() != GL_NO_ERROR || location == -1) {  // _semantic not found.
    return false;
  }
  uniforms_.push_back(location);
  return true;
}

bool Shader::FindAttrib(const char* _semantic) {
  if (!program_) {
    return false;
  }
  GLint location = glGetAttribLocation(program_, _semantic);
  if (glGetError() != GL_NO_ERROR || location == -1) {  // _semantic not found.
    return false;
  }
  attribs_.push_back(location);
  return true;
}

void Shader::UnbindAttribs() {
  for (size_t i = 0; i < attribs_.size(); ++i) {
    GL(DisableVertexAttribArray(attribs_[i]));
  }
}

void Shader::Unbind() {
  UnbindAttribs();
  GL(UseProgram(0));
}

ozz::unique_ptr<ImmediatePCShader> ImmediatePCShader::Build() {
  bool success = true;

  const char* kSimplePCVS =
      "uniform mat4 u_mvp;\n"
      "in vec3 a_position;\n"
      "in vec4 a_color;\n"
      "out vec4 v_vertex_color;\n"
      "void main() {\n"
      "  vec4 vertex = vec4(a_position.xyz, 1.);\n"
      "  gl_Position = u_mvp * vertex;\n"
      "  v_vertex_color = a_color;\n"
      "}\n";
  const char* kSimplePCPS =
      "in vec4 v_vertex_color;\n"
      "out vec4 o_color;\n"
      "void main() {\n"
      "  o_color = v_vertex_color;\n"
      "}\n";

  const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePCVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePCPS};

  ozz::unique_ptr<ImmediatePCShader> shader = make_unique<ImmediatePCShader>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_color");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");

  if (!success) {
    shader.reset();
  }

  return shader;
}

void ImmediatePCShader::Bind(const math::Float4x4& _model,
                             const math::Float4x4& _view_proj,
                             GLsizei _pos_stride, GLsizei _pos_offset,
                             GLsizei _color_stride, GLsizei _color_offset) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint color_attrib = attrib(1);
  GL(EnableVertexAttribArray(color_attrib));
  GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                         _color_stride, GL_PTR_OFFSET(_color_offset)));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(0);
  const ozz::math::Float4x4 mvp = _view_proj * _model;
  float values[16];
  math::StorePtrU(mvp.cols[0], values + 0);
  math::StorePtrU(mvp.cols[1], values + 4);
  math::StorePtrU(mvp.cols[2], values + 8);
  math::StorePtrU(mvp.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
}

ozz::unique_ptr<ImmediatePTCShader> ImmediatePTCShader::Build() {
  bool success = true;

  const char* kSimplePCVS =
      "uniform mat4 u_mvp;\n"
      "in vec3 a_position;\n"
      "in vec2 a_tex_coord;\n"
      "in vec4 a_color;\n"
      "out vec4 v_vertex_color;\n"
      "out vec2 v_texture_coord;\n"
      "void main() {\n"
      "  vec4 vertex = vec4(a_position.xyz, 1.);\n"
      "  gl_Position = u_mvp * vertex;\n"
      "  v_vertex_color = a_color;\n"
      "  v_texture_coord = a_tex_coord;\n"
      "}\n";
  const char* kSimplePCPS =
      "uniform sampler2D u_texture;\n"
      "in vec4 v_vertex_color;\n"
      "in vec2 v_texture_coord;\n"
      "out vec4 o_color;\n"
      "void main() {\n"
      "  vec4 tex_color = texture(u_texture, v_texture_coord);\n"
      "  o_color = v_vertex_color * tex_color;\n"
      "  if(o_color.a < .01) discard;\n"  // Implements alpha testing.
      "}\n";

  const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePCVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePCPS};

  ozz::unique_ptr<ImmediatePTCShader> shader =
      make_unique<ImmediatePTCShader>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_tex_coord");
  success &= shader->FindAttrib("a_color");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");
  success &= shader->BindUniform("u_texture");

  if (!success) {
    shader.reset();
  }

  return shader;
}

void ImmediatePTCShader::Bind(const math::Float4x4& _model,
                              const math::Float4x4& _view_proj,
                              GLsizei _pos_stride, GLsizei _pos_offset,
                              GLsizei _tex_stride, GLsizei _tex_offset,
                              GLsizei _color_stride, GLsizei _color_offset) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint tex_attrib = attrib(1);
  GL(EnableVertexAttribArray(tex_attrib));
  GL(VertexAttribPointer(tex_attrib, 2, GL_FLOAT, GL_FALSE, _tex_stride,
                         GL_PTR_OFFSET(_tex_offset)));

  const GLint color_attrib = attrib(2);
  GL(EnableVertexAttribArray(color_attrib));
  GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                         _color_stride, GL_PTR_OFFSET(_color_offset)));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(0);
  const ozz::math::Float4x4 mvp = _view_proj * _model;
  float values[16];
  math::StorePtrU(mvp.cols[0], values + 0);
  math::StorePtrU(mvp.cols[1], values + 4);
  math::StorePtrU(mvp.cols[2], values + 8);
  math::StorePtrU(mvp.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));

  // Binds texture
  const GLint texture = uniform(1);
  GL(Uniform1i(texture, 0));
}

ozz::unique_ptr<PointsShader> PointsShader::Build() {
  bool success = true;

  const char* kSimplePointsVS =
      "uniform mat4 u_mvp;\n"
      "in vec3 a_position;\n"
      "in vec4 a_color;\n"
      "in float a_size;\n"
      "in float a_screen_space;\n"
      "out vec4 v_vertex_color;\n"
      "void main() {\n"
      "  vec4 vertex = vec4(a_position.xyz, 1.);\n"
      "  gl_Position = u_mvp * vertex;\n"
      "  gl_PointSize = a_screen_space == 0. ? a_size / gl_Position.w : "
      "a_size;\n"
      "  v_vertex_color = a_color;\n"
      "}\n";
  const char* kSimplePointsPS =
      "in vec4 v_vertex_color;\n"
      "out vec4 o_color;\n"
      "void main() {\n"
      "  o_color = v_vertex_color;\n"
      "}\n";

  const char* vs[] = {kPlatformSpecivicVSHeader, kSimplePointsVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kSimplePointsPS};

  ozz::unique_ptr<PointsShader> shader = make_unique<PointsShader>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_color");
  success &= shader->FindAttrib("a_size");
  success &= shader->FindAttrib("a_screen_space");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");

  if (!success) {
    shader.reset();
  }

  return shader;
}

PointsShader::GenericAttrib PointsShader::Bind(
    const math::Float4x4& _model, const math::Float4x4& _view_proj,
    GLsizei _pos_stride, GLsizei _pos_offset, GLsizei _color_stride,
    GLsizei _color_offset, GLsizei _size_stride, GLsizei _size_offset,
    bool _screen_space) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint color_attrib = attrib(1);
  if (_color_stride) {
    GL(EnableVertexAttribArray(color_attrib));
    GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                           _color_stride, GL_PTR_OFFSET(_color_offset)));
  }
  const GLint size_attrib = attrib(2);
  if (_size_stride) {
    GL(EnableVertexAttribArray(size_attrib));
    GL(VertexAttribPointer(size_attrib, 1, GL_FLOAT, GL_TRUE, _size_stride,
                           GL_PTR_OFFSET(_size_offset)));
  }
  const GLint screen_space_attrib = attrib(3);
  GL(VertexAttrib1f(screen_space_attrib, 1.f * _screen_space));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(0);
  const ozz::math::Float4x4 mvp = _view_proj * _model;
  float values[16];
  math::StorePtrU(mvp.cols[0], values + 0);
  math::StorePtrU(mvp.cols[1], values + 4);
  math::StorePtrU(mvp.cols[2], values + 8);
  math::StorePtrU(mvp.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));

  return {_color_stride ? -1 : color_attrib, _size_stride ? -1 : size_attrib};
}

namespace {
const char* kPassUv =
    "in vec2 a_uv;\n"
    "out vec2 v_vertex_uv;\n"
    "void PassUv() {\n"
    "  v_vertex_uv = a_uv;\n"
    "}\n";
const char* kPassNoUv =
    "void PassUv() {\n"
    "}\n";
const char* kShaderUberVS =
    "uniform mat4 u_mvp;\n"
    "in vec3 a_position;\n"
    "in vec3 a_normal;\n"
    "in vec4 a_color;\n"
    "out vec3 v_world_normal;\n"
    "out vec4 v_vertex_color;\n"
    "void main() {\n"
    "  mat4 world_matrix = GetWorldMatrix();\n"
    "  vec4 vertex = vec4(a_position.xyz, 1.);\n"
    "  gl_Position = u_mvp * world_matrix * vertex;\n"
    "  mat3 cross_matrix = mat3(\n"
    "    cross(world_matrix[1].xyz, world_matrix[2].xyz),\n"
    "    cross(world_matrix[2].xyz, world_matrix[0].xyz),\n"
    "    cross(world_matrix[0].xyz, world_matrix[1].xyz));\n"
    "  float invdet = 1.0 / dot(cross_matrix[2], world_matrix[2].xyz);\n"
    "  mat3 normal_matrix = cross_matrix * invdet;\n"
    "  v_world_normal = normal_matrix * a_normal;\n"
    "  v_vertex_color = a_color;\n"
    "  PassUv();\n"
    "}\n";
const char* kShaderAmbientFct =
    "vec4 GetAmbient(vec3 _world_normal) {\n"
    "  vec3 normal = normalize(_world_normal);\n"
    "  vec3 alpha = (normal + 1.) * .5;\n"
    "  vec2 bt = mix(vec2(.3, .7), vec2(.4, .8), alpha.xz);\n"
    "  vec3 ambient = mix(vec3(bt.x, .3, bt.x), vec3(bt.y, .8, bt.y), "
    "alpha.y);\n"
    "  return vec4(ambient, 1.);\n"
    "}\n";
const char* kShaderAmbientFS =
    "in vec3 v_world_normal;\n"
    "in vec4 v_vertex_color;\n"
    "out vec4 o_color;\n"
    "void main() {\n"
    "  vec4 ambient = GetAmbient(v_world_normal);\n"
    "  o_color = ambient *\n"
    "                 v_vertex_color;\n"
    "}\n";
const char* kShaderAmbientTexturedFS =
    "uniform sampler2D u_texture;\n"
    "in vec3 v_world_normal;\n"
    "in vec4 v_vertex_color;\n"
    "in vec2 v_vertex_uv;\n"
    "out vec4 o_color;\n"
    "void main() {\n"
    "  vec4 ambient = GetAmbient(v_world_normal);\n"
    "  o_color = ambient *\n"
    "                 v_vertex_color *\n"
    "                 texture(u_texture, v_vertex_uv);\n"
    "}\n";
}  // namespace

void SkeletonShader::Bind(const math::Float4x4& _model,
                          const math::Float4x4& _view_proj, GLsizei _pos_stride,
                          GLsizei _pos_offset, GLsizei _normal_stride,
                          GLsizei _normal_offset, GLsizei _color_stride,
                          GLsizei _color_offset) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint normal_attrib = attrib(1);
  GL(EnableVertexAttribArray(normal_attrib));
  GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_FALSE, _normal_stride,
                         GL_PTR_OFFSET(_normal_offset)));

  const GLint color_attrib = attrib(2);
  GL(EnableVertexAttribArray(color_attrib));
  GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                         _color_stride, GL_PTR_OFFSET(_color_offset)));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(0);
  const ozz::math::Float4x4 mvp = _view_proj * _model;
  float values[16];
  math::StorePtrU(mvp.cols[0], values + 0);
  math::StorePtrU(mvp.cols[1], values + 4);
  math::StorePtrU(mvp.cols[2], values + 8);
  math::StorePtrU(mvp.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
}

ozz::unique_ptr<JointShader> JointShader::Build() {
  bool success = true;

  const char* vs_joint_to_world_matrix =
      "mat4 GetWorldMatrix() {\n"
      "  // Rebuilds joint matrix.\n"
      "  mat4 joint_matrix;\n"
      "  joint_matrix[0] = vec4(normalize(joint[0].xyz), 0.);\n"
      "  joint_matrix[1] = vec4(normalize(joint[1].xyz), 0.);\n"
      "  joint_matrix[2] = vec4(normalize(joint[2].xyz), 0.);\n"
      "  joint_matrix[3] = vec4(joint[3].xyz, 1.);\n"

      "  // Rebuilds bone properties.\n"
      "  vec3 bone_dir = vec3(joint[0].w, joint[1].w, joint[2].w);\n"
      "  float bone_len = length(bone_dir);\n"

      "  // Setup rendering world matrix.\n"
      "  mat4 world_matrix;\n"
      "  world_matrix[0] = joint_matrix[0] * bone_len;\n"
      "  world_matrix[1] = joint_matrix[1] * bone_len;\n"
      "  world_matrix[2] = joint_matrix[2] * bone_len;\n"
      "  world_matrix[3] = joint_matrix[3];\n"
      "  return world_matrix;\n"
      "}\n";
  const char* vs[] = {kPlatformSpecivicVSHeader, kPassNoUv,
                      GL_ARB_instanced_arrays_supported
                          ? "in mat4 joint;\n"
                          : "uniform mat4 joint;\n",
                      vs_joint_to_world_matrix, kShaderUberVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
                      kShaderAmbientFS};

  ozz::unique_ptr<JointShader> shader = make_unique<JointShader>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_normal");
  success &= shader->FindAttrib("a_color");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");

  if (GL_ARB_instanced_arrays_supported) {
    success &= shader->FindAttrib("joint");
  } else {
    success &= shader->BindUniform("joint");
  }

  if (!success) {
    shader.reset();
  }

  return shader;
}

ozz::unique_ptr<BoneShader>
BoneShader::Build() {  // Builds a world matrix from joint uniforms,
                       // sticking bone model between
  bool success = true;

  // parent and child joints.
  const char* vs_joint_to_world_matrix =
      "mat4 GetWorldMatrix() {\n"
      "  // Rebuilds bone properties.\n"
      "  // Bone length is set to zero to disable leaf rendering.\n"
      "  float is_bone = joint[3].w;\n"
      "  vec3 bone_dir = vec3(joint[0].w, joint[1].w, joint[2].w) * is_bone;\n"
      "  float bone_len = length(bone_dir);\n"

      "  // Setup rendering world matrix.\n"
      "  float dot1 = dot(joint[2].xyz, bone_dir);\n"
      "  float dot2 = dot(joint[0].xyz, bone_dir);\n"
      "  vec3 binormal = abs(dot1) < abs(dot2) ? joint[2].xyz : joint[0].xyz;\n"

      "  mat4 world_matrix;\n"
      "  world_matrix[0] = vec4(bone_dir, 0.);\n"
      "  world_matrix[1] = \n"
      "    vec4(bone_len * normalize(cross(binormal, bone_dir)), 0.);\n"
      "  world_matrix[2] =\n"
      "    vec4(bone_len * normalize(cross(bone_dir, world_matrix[1].xyz)), "
      "0.);\n"
      "  world_matrix[3] = vec4(joint[3].xyz, 1.);\n"
      "  return world_matrix;\n"
      "}\n";
  const char* vs[] = {kPlatformSpecivicVSHeader, kPassNoUv,
                      GL_ARB_instanced_arrays_supported
                          ? "in mat4 joint;\n"
                          : "uniform mat4 joint;\n",
                      vs_joint_to_world_matrix, kShaderUberVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
                      kShaderAmbientFS};

  ozz::unique_ptr<BoneShader> shader = make_unique<BoneShader>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_normal");
  success &= shader->FindAttrib("a_color");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");

  if (GL_ARB_instanced_arrays_supported) {
    success &= shader->FindAttrib("joint");
  } else {
    success &= shader->BindUniform("joint");
  }

  if (!success) {
    shader.reset();
  }

  return shader;
}

ozz::unique_ptr<AmbientShader> AmbientShader::Build() {
  const char* vs[] = {
      kPlatformSpecivicVSHeader, kPassNoUv,
      "uniform mat4 u_mw;\n mat4 GetWorldMatrix() {return u_mw;}\n",
      kShaderUberVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
                      kShaderAmbientFS};

  ozz::unique_ptr<AmbientShader> shader = make_unique<AmbientShader>();
  bool success =
      shader->InternalBuild(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  if (!success) {
    shader.reset();
  }

  return shader;
}

bool AmbientShader::InternalBuild(int _vertex_count, const char** _vertex,
                                  int _fragment_count, const char** _fragment) {
  bool success = true;

  success &=
      BuildFromSource(_vertex_count, _vertex, _fragment_count, _fragment);

  // Binds default attributes
  success &= FindAttrib("a_position");
  success &= FindAttrib("a_normal");
  success &= FindAttrib("a_color");

  // Binds default uniforms
  success &= BindUniform("u_mw");
  success &= BindUniform("u_mvp");

  return success;
}

void AmbientShader::Bind(const math::Float4x4& _model,
                         const math::Float4x4& _view_proj, GLsizei _pos_stride,
                         GLsizei _pos_offset, GLsizei _normal_stride,
                         GLsizei _normal_offset, GLsizei _color_stride,
                         GLsizei _color_offset) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint normal_attrib = attrib(1);
  GL(EnableVertexAttribArray(normal_attrib));
  GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_TRUE, _normal_stride,
                         GL_PTR_OFFSET(_normal_offset)));

  const GLint color_attrib = attrib(2);
  GL(EnableVertexAttribArray(color_attrib));
  GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                         _color_stride, GL_PTR_OFFSET(_color_offset)));

  // Binds mw uniform
  float values[16];
  const GLint mw_uniform = uniform(0);
  math::StorePtrU(_model.cols[0], values + 0);
  math::StorePtrU(_model.cols[1], values + 4);
  math::StorePtrU(_model.cols[2], values + 8);
  math::StorePtrU(_model.cols[3], values + 12);
  GL(UniformMatrix4fv(mw_uniform, 1, false, values));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(1);
  math::StorePtrU(_view_proj.cols[0], values + 0);
  math::StorePtrU(_view_proj.cols[1], values + 4);
  math::StorePtrU(_view_proj.cols[2], values + 8);
  math::StorePtrU(_view_proj.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
}

ozz::unique_ptr<AmbientShaderInstanced> AmbientShaderInstanced::Build() {
  bool success = true;

  const char* vs[] = {kPlatformSpecivicVSHeader, kPassNoUv,
                      "in mat4 a_mw;\n mat4 GetWorldMatrix() {return a_mw;}\n",
                      kShaderUberVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
                      kShaderAmbientFS};

  ozz::unique_ptr<AmbientShaderInstanced> shader =
      make_unique<AmbientShaderInstanced>();
  success &=
      shader->BuildFromSource(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  // Binds default attributes
  success &= shader->FindAttrib("a_position");
  success &= shader->FindAttrib("a_normal");
  success &= shader->FindAttrib("a_color");
  success &= shader->FindAttrib("a_mw");

  // Binds default uniforms
  success &= shader->BindUniform("u_mvp");

  if (!success) {
    shader.reset();
  }

  return shader;
}

void AmbientShaderInstanced::Bind(GLsizei _models_offset,
                                  const math::Float4x4& _view_proj,
                                  GLsizei _pos_stride, GLsizei _pos_offset,
                                  GLsizei _normal_stride,
                                  GLsizei _normal_offset, GLsizei _color_stride,
                                  GLsizei _color_offset) {
  GL(UseProgram(program()));

  const GLint position_attrib = attrib(0);
  GL(EnableVertexAttribArray(position_attrib));
  GL(VertexAttribPointer(position_attrib, 3, GL_FLOAT, GL_FALSE, _pos_stride,
                         GL_PTR_OFFSET(_pos_offset)));

  const GLint normal_attrib = attrib(1);
  GL(EnableVertexAttribArray(normal_attrib));
  GL(VertexAttribPointer(normal_attrib, 3, GL_FLOAT, GL_TRUE, _normal_stride,
                         GL_PTR_OFFSET(_normal_offset)));

  const GLint color_attrib = attrib(2);
  GL(EnableVertexAttribArray(color_attrib));
  GL(VertexAttribPointer(color_attrib, 4, GL_UNSIGNED_BYTE, GL_TRUE,
                         _color_stride, GL_PTR_OFFSET(_color_offset)));
  if (_color_stride == 0) {
    GL(VertexAttribDivisor_(color_attrib,
                            std::numeric_limits<unsigned int>::max()));
  }

  // Binds mw uniform
  const GLint models_attrib = attrib(3);
  GL(EnableVertexAttribArray(models_attrib + 0));
  GL(EnableVertexAttribArray(models_attrib + 1));
  GL(EnableVertexAttribArray(models_attrib + 2));
  GL(EnableVertexAttribArray(models_attrib + 3));
  GL(VertexAttribDivisor_(models_attrib + 0, 1));
  GL(VertexAttribDivisor_(models_attrib + 1, 1));
  GL(VertexAttribDivisor_(models_attrib + 2, 1));
  GL(VertexAttribDivisor_(models_attrib + 3, 1));
  GL(VertexAttribPointer(models_attrib + 0, 4, GL_FLOAT, GL_FALSE,
                         sizeof(math::Float4x4),
                         GL_PTR_OFFSET(0 + _models_offset)));
  GL(VertexAttribPointer(models_attrib + 1, 4, GL_FLOAT, GL_FALSE,
                         sizeof(math::Float4x4),
                         GL_PTR_OFFSET(16 + _models_offset)));
  GL(VertexAttribPointer(models_attrib + 2, 4, GL_FLOAT, GL_FALSE,
                         sizeof(math::Float4x4),
                         GL_PTR_OFFSET(32 + _models_offset)));
  GL(VertexAttribPointer(models_attrib + 3, 4, GL_FLOAT, GL_FALSE,
                         sizeof(math::Float4x4),
                         GL_PTR_OFFSET(48 + _models_offset)));

  // Binds mvp uniform
  const GLint mvp_uniform = uniform(0);
  float values[16];
  math::StorePtrU(_view_proj.cols[0], values + 0);
  math::StorePtrU(_view_proj.cols[1], values + 4);
  math::StorePtrU(_view_proj.cols[2], values + 8);
  math::StorePtrU(_view_proj.cols[3], values + 12);
  GL(UniformMatrix4fv(mvp_uniform, 1, false, values));
}

void AmbientShaderInstanced::Unbind() {
  const GLint color_attrib = attrib(2);
  GL(VertexAttribDivisor_(color_attrib, 0));

  const GLint models_attrib = attrib(3);
  GL(DisableVertexAttribArray(models_attrib + 0));
  GL(DisableVertexAttribArray(models_attrib + 1));
  GL(DisableVertexAttribArray(models_attrib + 2));
  GL(DisableVertexAttribArray(models_attrib + 3));
  GL(VertexAttribDivisor_(models_attrib + 0, 0));
  GL(VertexAttribDivisor_(models_attrib + 1, 0));
  GL(VertexAttribDivisor_(models_attrib + 2, 0));
  GL(VertexAttribDivisor_(models_attrib + 3, 0));
  Shader::Unbind();
}

ozz::unique_ptr<AmbientTexturedShader> AmbientTexturedShader::Build() {
  const char* vs[] = {
      kPlatformSpecivicVSHeader, kPassUv,
      "uniform mat4 u_mw;\n mat4 GetWorldMatrix() {return u_mw;}\n",
      kShaderUberVS};
  const char* fs[] = {kPlatformSpecivicFSHeader, kShaderAmbientFct,
                      kShaderAmbientTexturedFS};

  ozz::unique_ptr<AmbientTexturedShader> shader =
      make_unique<AmbientTexturedShader>();
  bool success =
      shader->InternalBuild(OZZ_ARRAY_SIZE(vs), vs, OZZ_ARRAY_SIZE(fs), fs);

  success &= shader->FindAttrib("a_uv");

  if (!success) {
    shader.reset();
  }

  return shader;
}

void AmbientTexturedShader::Bind(const math::Float4x4& _model,
                                 const math::Float4x4& _view_proj,
                                 GLsizei _pos_stride, GLsizei _pos_offset,
                                 GLsizei _normal_stride, GLsizei _normal_offset,
                                 GLsizei _color_stride, GLsizei _color_offset,
                                 GLsizei _uv_stride, GLsizei _uv_offset) {
  AmbientShader::Bind(_model, _view_proj, _pos_stride, _pos_offset,
                      _normal_stride, _normal_offset, _color_stride,
                      _color_offset);

  const GLint uv_attrib = attrib(3);
  GL(EnableVertexAttribArray(uv_attrib));
  GL(VertexAttribPointer(uv_attrib, 2, GL_FLOAT, GL_FALSE, _uv_stride,
                         GL_PTR_OFFSET(_uv_offset)));
}
}  // namespace internal
}  // namespace sample
}  // namespace ozz
