//
// Copyright (c) 2002-2013 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "GLSLANG/ShaderLang.h"

#include <assert.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sstream>
#include <vector>
#include "angle_gl.h"

//
// Return codes from main.
//
enum TFailCode
{
    ESuccess = 0,
    EFailUsage,
    EFailCompile,
    EFailCompilerCreate,
};

static void usage();
static sh::GLenum FindShaderType(const char *fileName);
static bool CompileFile(char *fileName, ShHandle compiler, int compileOptions);
static void LogMsg(const char *msg, const char *name, const int num, const char *logName);
static void PrintActiveVariables(ShHandle compiler);

// If NUM_SOURCE_STRINGS is set to a value > 1, the input file data is
// broken into that many chunks.
const unsigned int NUM_SOURCE_STRINGS = 2;
typedef std::vector<char *> ShaderSource;
static bool ReadShaderSource(const char *fileName, ShaderSource &source);
static void FreeShaderSource(ShaderSource &source);

static bool ParseGLSLOutputVersion(const std::string &, ShShaderOutput *outResult);
static bool ParseIntValue(const std::string &, int emptyDefault, int *outValue);

//
// Set up the per compile resources
//
void GenerateResources(ShBuiltInResources *resources)
{
    ShInitBuiltInResources(resources);

    resources->MaxVertexAttribs = 8;
    resources->MaxVertexUniformVectors = 128;
    resources->MaxVaryingVectors = 8;
    resources->MaxVertexTextureImageUnits = 0;
    resources->MaxCombinedTextureImageUnits = 8;
    resources->MaxTextureImageUnits = 8;
    resources->MaxFragmentUniformVectors = 16;
    resources->MaxDrawBuffers = 1;
    resources->MaxDualSourceDrawBuffers     = 1;

    resources->OES_standard_derivatives = 0;
    resources->OES_EGL_image_external = 0;
}

int main(int argc, char *argv[])
{
    TFailCode failCode = ESuccess;

    int compileOptions = 0;
    int numCompiles = 0;
    ShHandle vertexCompiler = 0;
    ShHandle fragmentCompiler = 0;
    ShShaderSpec spec = SH_GLES2_SPEC;
    ShShaderOutput output = SH_GLSL_OUTPUT;

    ShInitialize();

    ShBuiltInResources resources;
    GenerateResources(&resources);

    argc--;
    argv++;
    for (; (argc >= 1) && (failCode == ESuccess); argc--, argv++)
    {
        if (argv[0][0] == '-')
        {
            switch (argv[0][1])
            {
              case 'o': compileOptions |= SH_OBJECT_CODE; break;
              case 's':
                if (argv[0][2] == '=')
                {
                    switch (argv[0][3])
                    {
                      case 'e':
                        {
                            spec = SH_GLES2_SPEC;
                        }
                        break;
                      default: failCode = EFailUsage;
                    }
                }
                else
                {
                    failCode = EFailUsage;
                }
                break;
              case 'b':
                if (argv[0][2] == '=')
                {
                    switch (argv[0][3])
                    {
                      case 'g':
                          if (!ParseGLSLOutputVersion(&argv[0][sizeof("-b=g") - 1], &output))
                          {
                              failCode = EFailUsage;
                          }
                          break;
                      default: failCode = EFailUsage;
                    }
                }
                else
                {
                    failCode = EFailUsage;
                }
                break;
              case 'x':
                if (argv[0][2] == '=')
                {
                    switch (argv[0][3])
                    {
                      case 'i': resources.OES_EGL_image_external = 1; break;
                      case 'd': resources.OES_standard_derivatives = 1; break;
                      case 'r': resources.ARB_texture_rectangle = 1; break;
                      case 'b':
                          if (ParseIntValue(&argv[0][sizeof("-x=b") - 1], 1,
                                            &resources.MaxDualSourceDrawBuffers))
                          {
                              resources.EXT_blend_func_extended = 1;
                          }
                          else
                          {
                              failCode = EFailUsage;
                          }
                          break;
                      case 'w':
                          if (ParseIntValue(&argv[0][sizeof("-x=w") - 1], 1,
                                            &resources.MaxDrawBuffers))
                          {
                              resources.EXT_draw_buffers = 1;
                          }
                          else
                          {
                              failCode = EFailUsage;
                          }
                          break;
                      case 'g':
                          resources.EXT_frag_depth = 1;
                          break;
                      case 'l': resources.EXT_shader_texture_lod = 1; break;
                      case 'f': resources.EXT_shader_framebuffer_fetch = 1; break;
                      case 'n': resources.NV_shader_framebuffer_fetch = 1; break;
                      case 'a': resources.ARM_shader_framebuffer_fetch = 1; break;
                      default: failCode = EFailUsage;
                    }
                }
                else
                {
                    failCode = EFailUsage;
                }
                break;
              default: failCode = EFailUsage;
            }
        }
        else
        {
            ShHandle compiler = 0;
            switch (FindShaderType(argv[0]))
            {
              case GL_VERTEX_SHADER:
                if (vertexCompiler == 0)
                {
                    vertexCompiler = ShConstructCompiler(
                        GL_VERTEX_SHADER, spec, output, &resources);
                }
                compiler = vertexCompiler;
                break;
              case GL_FRAGMENT_SHADER:
                if (fragmentCompiler == 0)
                {
                    fragmentCompiler = ShConstructCompiler(
                        GL_FRAGMENT_SHADER, spec, output, &resources);
                }
                compiler = fragmentCompiler;
                break;
              default: break;
            }
            if (compiler)
            {
                bool compiled = CompileFile(argv[0], compiler, compileOptions);

                if (compiled && (compileOptions & SH_OBJECT_CODE))
                {
                    LogMsg("BEGIN", "COMPILER", numCompiles, "OBJ CODE");
                    std::string code = ShGetObjectCode(compiler);
                    puts(code.c_str());
                    LogMsg("END", "COMPILER", numCompiles, "OBJ CODE");
                    printf("\n\n");
                }
                if (!compiled)
                  failCode = EFailCompile;
                ++numCompiles;
            }
            else
            {
                failCode = EFailCompilerCreate;
            }
        }
    }

    if ((vertexCompiler == 0) && (fragmentCompiler == 0))
        failCode = EFailUsage;
    if (failCode == EFailUsage)
        usage();

    if (vertexCompiler)
        ShDestruct(vertexCompiler);
    if (fragmentCompiler)
        ShDestruct(fragmentCompiler);
    ShFinalize();

    return failCode;
}

//
//   print usage to stdout
//
void usage()
{
    printf(
        "Usage: translate [-i -o -u -l -e -t -d -p -b=e -b=g -b=h9 -x=i -x=d] file1 file2 ...\n"
        "Where: filename : filename ending in .frag or .vert\n"
        "       -i       : print intermediate tree\n"
        "       -o       : print translated code\n"
        "       -u       : print active attribs, uniforms, varyings and program outputs\n"
        "       -l       : unroll for-loops with integer indices\n"
        "       -e       : emulate certain built-in functions (workaround for driver bugs)\n"
        "       -t       : enforce experimental timing restrictions\n"
        "       -d       : print dependency graph used to enforce timing restrictions\n"
        "       -p       : use precision emulation\n"
        "       -s=e2    : use GLES2 spec (this is by default)\n"
        "       -s=e3    : use GLES3 spec (in development)\n"
        "       -s=w     : use WebGL spec\n"
        "       -s=w2    : use WebGL 2 spec (in development)\n"
        "       -s=c     : use CSS Shaders spec\n"
        "       -b=e     : output GLSL ES code (this is by default)\n"
        "       -b=g     : output GLSL code (compatibility profile)\n"
        "       -b=g[NUM]: output GLSL code (NUM can be 130, 140, 150, 330, 400, 410, 420, 430, "
        "440, 450)\n"
        "       -b=h9    : output HLSL9 code\n"
        "       -b=h11   : output HLSL11 code\n"
        "       -x=i     : enable GL_OES_EGL_image_external\n"
        "       -x=d     : enable GL_OES_EGL_standard_derivatives\n"
        "       -x=r     : enable ARB_texture_rectangle\n"
        "       -x=b[NUM]: enable EXT_blend_func_extended (NUM default 1)\n"
        "       -x=w[NUM]: enable EXT_draw_buffers (NUM default 1)\n"
        "       -x=l     : enable EXT_shader_texture_lod\n"
        "       -x=f     : enable EXT_shader_framebuffer_fetch\n"
        "       -x=n     : enable NV_shader_framebuffer_fetch\n"
        "       -x=a     : enable ARM_shader_framebuffer_fetch\n");
}

//
//   Deduce the shader type from the filename.  Files must end in one of the
//   following extensions:
//
//   .frag*    = fragment shader
//   .vert*    = vertex shader
//
sh::GLenum FindShaderType(const char *fileName)
{
    assert(fileName);

    const char *ext = strrchr(fileName, '.');

    if (ext && strcmp(ext, ".sl") == 0)
        for (; ext > fileName && ext[0] != '.'; ext--);

    ext = strrchr(fileName, '.');
    if (ext)
    {
        if (strncmp(ext, ".frag", 4) == 0) return GL_FRAGMENT_SHADER;
        if (strncmp(ext, ".vert", 4) == 0) return GL_VERTEX_SHADER;
    }

    return GL_FRAGMENT_SHADER;
}

//
//   Read a file's data into a string, and compile it using ShCompile
//
bool CompileFile(char *fileName, ShHandle compiler, int compileOptions)
{
    ShaderSource source;
    if (!ReadShaderSource(fileName, source))
        return false;

    int ret = ShCompile(compiler, &source[0], source.size(), compileOptions);

    FreeShaderSource(source);
    return ret ? true : false;
}

void LogMsg(const char *msg, const char *name, const int num, const char *logName)
{
    printf("#### %s %s %d %s ####\n", msg, name, num, logName);
}

static bool ReadShaderSource(const char *fileName, ShaderSource &source)
{
    FILE *in = fopen(fileName, "rb");
    if (!in)
    {
        printf("Error: unable to open input file: %s\n", fileName);
        return false;
    }

    // Obtain file size.
    fseek(in, 0, SEEK_END);
    size_t count = ftell(in);
    rewind(in);

    int len = (int)ceil((float)count / (float)NUM_SOURCE_STRINGS);
    source.reserve(NUM_SOURCE_STRINGS);
    // Notice the usage of do-while instead of a while loop here.
    // It is there to handle empty files in which case a single empty
    // string is added to vector.
    do
    {
        char *data = new char[len + 1];
        size_t nread = fread(data, 1, len, in);
        data[nread] = '\0';
        source.push_back(data);

        count -= nread;
    }
    while (count > 0);

    fclose(in);
    return true;
}

static void FreeShaderSource(ShaderSource &source)
{
    for (ShaderSource::size_type i = 0; i < source.size(); ++i)
    {
        delete [] source[i];
    }
    source.clear();
}

static bool ParseGLSLOutputVersion(const std::string &num, ShShaderOutput *outResult)
{
    if (num.length() == 0)
    {
        *outResult = SH_GLSL_COMPATIBILITY_OUTPUT;
        return true;
    }
    std::istringstream input(num);
    int value;
    if (!(input >> value && input.eof()))
    {
        return false;
    }

    return false;
}

static bool ParseIntValue(const std::string &num, int emptyDefault, int *outValue)
{
    if (num.length() == 0)
    {
        *outValue = emptyDefault;
        return true;
    }

    std::istringstream input(num);
    int value;
    if (!(input >> value && input.eof()))
    {
        return false;
    }
    *outValue = value;
    return true;
}
