/* Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *  * Neither the name of NVIDIA CORPORATION nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 *
 * Utility functions for compiling shaders and programs
 *
 * Author: Evan Hart
 * Copyright (c) NVIDIA Corporation. All rights reserved.
 *
 */


#ifndef NV_SHADER_UTILS_H
#define NV_SHADER_UTILS_H

#include <stdio.h>
#include <string.h>

namespace nv {

//
//
////////////////////////////////////////////////////////////
inline GLuint CompileGLSLShader(GLenum target, const char *shader) {
  GLuint object;

  object = glCreateShader(target);

  if (!object) {
    return object;
  }

  glShaderSource(object, 1, &shader, NULL);

  glCompileShader(object);

  // check if shader compiled
  GLint compiled = 0;
  glGetShaderiv(object, GL_COMPILE_STATUS, &compiled);

  if (!compiled) {
#ifdef NV_REPORT_COMPILE_ERRORS
    char temp[256] = "";
    glGetShaderInfoLog(object, 256, NULL, temp);
    fprintf(stderr, "Compile failed:\n%s\n", temp);
#endif
    glDeleteShader(object);
    return 0;
  }

  return object;
}

//
//
////////////////////////////////////////////////////////////
inline GLuint CompileGLSLShaderFromFile(GLenum target, const char *filename) {
  FILE *shaderFile;
  char *text;
  long size;
  size_t fsize = 0;

  // read files as binary to prevent problems from newline translation
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)

  if (fopen_s(&shaderFile, filename, "rb") != 0)
#else
  if ((shaderFile = fopen(filename, "rb")) == 0)
#endif
  {
    return 0;
  }

  // Get the length of the file
  fseek(shaderFile, 0, SEEK_END);
  size = ftell(shaderFile);

  // Read the file contents from the start, then close file and add a null
  // terminator
  fseek(shaderFile, 0, SEEK_SET);
  text = new char[size + 1];
  fsize = fread(text, size, 1, shaderFile);
  fclose(shaderFile);

  if (fsize == 0) {
    printf("CompileGLSLShaderFromFile(), error... fsize = 0\n");
  }

  text[size] = '\0';

  GLuint object = CompileGLSLShader(target, text);

  delete[] text;

  return object;
}

// Create a program composed of vertex and fragment shaders.
inline GLuint LinkGLSLProgram(GLuint vertexShader, GLuint fragmentShader) {
  GLuint program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);

#ifdef NV_REPORT_COMPILE_ERRORS
  // Get error log.
  GLint charsWritten, infoLogLength;
  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

  char *infoLog = new char[infoLogLength];
  glGetProgramInfoLog(program, infoLogLength, &charsWritten, infoLog);
  printf(infoLog);
  delete[] infoLog;
#endif

  // Test linker result.
  GLint linkSucceed = GL_FALSE;
  glGetProgramiv(program, GL_LINK_STATUS, &linkSucceed);

  if (linkSucceed == GL_FALSE) {
    glDeleteProgram(program);
    return 0;
  }

  return program;
}

// Create a program composed of vertex, geometry and fragment shaders.
inline GLuint LinkGLSLProgram(GLuint vertexShader, GLuint geometryShader,
                              GLint inputType, GLint vertexOut,
                              GLint outputType, GLuint fragmentShader) {
  GLuint program = glCreateProgram();
  glAttachShader(program, vertexShader);
  glAttachShader(program, geometryShader);
  glProgramParameteriEXT(program, GL_GEOMETRY_INPUT_TYPE_EXT, inputType);
  glProgramParameteriEXT(program, GL_GEOMETRY_VERTICES_OUT_EXT, vertexOut);
  glProgramParameteriEXT(program, GL_GEOMETRY_OUTPUT_TYPE_EXT, outputType);
  glAttachShader(program, fragmentShader);
  glLinkProgram(program);

#ifdef NV_REPORT_COMPILE_ERRORS
  // Get error log.
  GLint charsWritten, infoLogLength;
  glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);

  char *infoLog = new char[infoLogLength];
  glGetProgramInfoLog(program, infoLogLength, &charsWritten, infoLog);
  printf(infoLog);
  delete[] infoLog;
#endif

  // Test linker result.
  GLint linkSucceed = GL_FALSE;
  glGetProgramiv(program, GL_LINK_STATUS, &linkSucceed);

  if (linkSucceed == GL_FALSE) {
    glDeleteProgram(program);
    return 0;
  }

  return program;
}

//
//
////////////////////////////////////////////////////////////
inline GLuint CompileASMShader(GLenum program_type, const char *code) {
  GLuint program_id;
  glGenProgramsARB(1, &program_id);
  glBindProgramARB(program_type, program_id);
  glProgramStringARB(program_type, GL_PROGRAM_FORMAT_ASCII_ARB,
                     (GLsizei)strlen(code), (GLubyte *)code);

  GLint error_pos;
  glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &error_pos);

  if (error_pos != -1) {
#ifdef NV_REPORT_COMPILE_ERRORS
    const GLubyte *error_string;
    error_string = glGetString(GL_PROGRAM_ERROR_STRING_ARB);
    fprintf(stderr, "Program error at position: %d\n%s\n", (int)error_pos,
            error_string);
#endif
    return 0;
  }

  return program_id;
}

//
//
////////////////////////////////////////////////////////////
inline GLuint CompileASMShaderFromFile(GLenum target, const char *filename) {
  FILE *shaderFile;
  char *text;
  long size;
  size_t fsize = 0;

  // read files as binary to prevent problems from newline translation
#if defined(WIN32) || defined(_WIN32) || defined(WIN64) || defined(_WIN64)

  if (fopen_s(&shaderFile, filename, "rb") != 0)
#else
  if ((shaderFile = fopen(filename, "rb")) == 0)
#endif
  {
    return 0;
  }

  // Get the length of the file
  fseek(shaderFile, 0, SEEK_END);
  size = ftell(shaderFile);

  // Read the file contents from the start, then close file and add a null
  // terminator
  fseek(shaderFile, 0, SEEK_SET);
  text = new char[size + 1];
  fsize = fread(text, size, 1, shaderFile);
  fclose(shaderFile);

  if (fsize == 0) {
    printf("CompileGLSLShaderFromFile(), error... fsize = 0\n");
  }

  text[size] = '\0';

  GLuint program_id = CompileASMShader(target, text);

  delete[] text;

  return program_id;
}

}  // namespace nv
#endif
