//
// 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.
//

//
// Implement the top-level of interface to the compiler,
// as defined in ShaderLang.h
//

#include "GLSLANG/ShaderLang.h"

#include "compiler/translator/Compiler.h"
#include "compiler/translator/InitializeDll.h"
#include "compiler/translator/length_limits.h"
#include "compiler/translator/TranslatorHLSL.h"
#include "compiler/translator/VariablePacker.h"
#include "angle_gl.h"

namespace
{

enum ShaderVariableType
{
    SHADERVAR_UNIFORM,
    SHADERVAR_VARYING,
    SHADERVAR_ATTRIBUTE,
    SHADERVAR_OUTPUTVARIABLE,
    SHADERVAR_INTERFACEBLOCK
};
    
bool isInitialized = false;

//
// This is the platform independent interface between an OGL driver
// and the shading language compiler.
//

static bool CheckVariableMaxLengths(const ShHandle handle,
                                    size_t expectedValue)
{
    size_t activeUniformLimit = 0;
    ShGetInfo(handle, SH_ACTIVE_UNIFORM_MAX_LENGTH, &activeUniformLimit);
    size_t activeAttribLimit = 0;
    ShGetInfo(handle, SH_ACTIVE_ATTRIBUTE_MAX_LENGTH, &activeAttribLimit);
    size_t varyingLimit = 0;
    ShGetInfo(handle, SH_VARYING_MAX_LENGTH, &varyingLimit);
    return (expectedValue == activeUniformLimit &&
            expectedValue == activeAttribLimit &&
            expectedValue == varyingLimit);
}

bool CheckMappedNameMaxLength(const ShHandle handle, size_t expectedValue)
{
    size_t mappedNameMaxLength = 0;
    ShGetInfo(handle, SH_MAPPED_NAME_MAX_LENGTH, &mappedNameMaxLength);
    return (expectedValue == mappedNameMaxLength);
}

template <typename VarT>
const sh::ShaderVariable *ReturnVariable(const std::vector<VarT> &infoList, int index)
{
    if (index < 0 || static_cast<size_t>(index) >= infoList.size())
    {
        return NULL;
    }

    return &infoList[index];
}

const sh::ShaderVariable *GetVariable(const TCompiler *compiler, ShShaderInfo varType, int index)
{
    switch (varType)
    {
      case SH_ACTIVE_ATTRIBUTES:
        return ReturnVariable(compiler->getAttributes(), index);
      case SH_ACTIVE_UNIFORMS:
        return ReturnVariable(compiler->getExpandedUniforms(), index);
      case SH_VARYINGS:
        return ReturnVariable(compiler->getExpandedVaryings(), index);
      default:
        UNREACHABLE();
        return NULL;
    }
}

ShPrecisionType ConvertPrecision(sh::GLenum precision)
{
    switch (precision)
    {
      case GL_HIGH_FLOAT:
      case GL_HIGH_INT:
        return SH_PRECISION_HIGHP;
      case GL_MEDIUM_FLOAT:
      case GL_MEDIUM_INT:
        return SH_PRECISION_MEDIUMP;
      case GL_LOW_FLOAT:
      case GL_LOW_INT:
        return SH_PRECISION_LOWP;
      default:
        return SH_PRECISION_UNDEFINED;
    }
}

template <typename VarT>
const std::vector<VarT> *GetVariableList(const TCompiler *compiler, ShaderVariableType variableType);

template <>
const std::vector<sh::Uniform> *GetVariableList(const TCompiler *compiler, ShaderVariableType)
{
    return &compiler->getUniforms();
}

template <>
const std::vector<sh::Varying> *GetVariableList(const TCompiler *compiler, ShaderVariableType)
{
    return &compiler->getVaryings();
}

template <>
const std::vector<sh::Attribute> *GetVariableList(const TCompiler *compiler, ShaderVariableType variableType)
{
    return (variableType == SHADERVAR_ATTRIBUTE ?
        &compiler->getAttributes() :
        &compiler->getOutputVariables());
}

template <>
const std::vector<sh::InterfaceBlock> *GetVariableList(const TCompiler *compiler, ShaderVariableType)
{
    return &compiler->getInterfaceBlocks();
}

template <typename VarT>
const std::vector<VarT> *GetShaderVariables(const ShHandle handle, ShaderVariableType variableType)
{
    if (!handle)
    {
        return NULL;
    }

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (!compiler)
    {
        return NULL;
    }

    return GetVariableList<VarT>(compiler, variableType);
}

}

//
// Driver must call this first, once, before doing any other compiler operations.
// Subsequent calls to this function are no-op.
//
int ShInitialize()
{
    if (!isInitialized)
    {
        isInitialized = InitProcess();
    }
    return isInitialized ? 1 : 0;
}

//
// Cleanup symbol tables
//
int ShFinalize()
{
    if (isInitialized)
    {
        DetachProcess();
        isInitialized = false;
    }
    return 1;
}

//
// Initialize built-in resources with minimum expected values.
//
void ShInitBuiltInResources(ShBuiltInResources* resources)
{
    // Constants.
    resources->MaxVertexAttribs = 8;
    resources->MaxVertexUniformVectors = 128;
    resources->MaxVaryingVectors = 8;
    resources->MaxVertexTextureImageUnits = 0;
    resources->MaxCombinedTextureImageUnits = 8;
    resources->MaxTextureImageUnits = 8;
    resources->MaxFragmentUniformVectors = 16;
    resources->MaxDrawBuffers = 1;

    // Extensions.
    resources->OES_standard_derivatives = 0;
    resources->OES_EGL_image_external = 0;
    resources->ARB_texture_rectangle = 0;
    resources->EXT_draw_buffers = 0;
    resources->EXT_frag_depth = 0;
    resources->EXT_shader_texture_lod = 0;

    // Disable highp precision in fragment shader by default.
    resources->FragmentPrecisionHigh = 0;

    // GLSL ES 3.0 constants.
    resources->MaxVertexOutputVectors = 16;
    resources->MaxFragmentInputVectors = 15;
    resources->MinProgramTexelOffset = -8;
    resources->MaxProgramTexelOffset = 7;

    // Disable name hashing by default.
    resources->HashFunction = NULL;

    resources->ArrayIndexClampingStrategy = SH_CLAMP_WITH_CLAMP_INTRINSIC;

    resources->MaxExpressionComplexity = 256;
    resources->MaxCallStackDepth = 256;
}

//
// Driver calls these to create and destroy compiler objects.
//
ShHandle ShConstructCompiler(sh::GLenum type, ShShaderSpec spec,
                             ShShaderOutput output,
                             const ShBuiltInResources* resources)
{
    TShHandleBase* base = static_cast<TShHandleBase*>(ConstructCompiler(type, spec, output));
    TCompiler* compiler = base->getAsCompiler();
    if (compiler == 0)
        return 0;

    // Generate built-in symbol table.
    if (!compiler->Init(*resources)) {
        ShDestruct(base);
        return 0;
    }

    return reinterpret_cast<void*>(base);
}

void ShDestruct(ShHandle handle)
{
    if (handle == 0)
        return;

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);

    if (base->getAsCompiler())
        DeleteCompiler(base->getAsCompiler());
}

void ShGetBuiltInResourcesString(const ShHandle handle, size_t outStringLen, char *outString)
{
    if (!handle || !outString)
    {
        return;
    }

    TShHandleBase *base = static_cast<TShHandleBase*>(handle);
    TCompiler *compiler = base->getAsCompiler();
    if (!compiler)
    {
        return;
    }

    strncpy(outString, compiler->getBuiltInResourcesString().c_str(), outStringLen);
    outString[outStringLen - 1] = '\0';
}
//
// Do an actual compile on the given strings.  The result is left
// in the given compile object.
//
// Return:  The return value of ShCompile is really boolean, indicating
// success or failure.
//
int ShCompile(
    const ShHandle handle,
    const char* const shaderStrings[],
    size_t numStrings,
    int compileOptions)
{
    if (handle == 0)
        return 0;

    TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (compiler == 0)
        return 0;

    bool success = compiler->compile(shaderStrings, numStrings, compileOptions);
    return success ? 1 : 0;
}

void ShGetInfo(const ShHandle handle, ShShaderInfo pname, size_t* params)
{
    if (!handle || !params)
        return;

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (!compiler) return;

    switch(pname)
    {
    case SH_INFO_LOG_LENGTH:
        *params = compiler->getInfoSink().info.size() + 1;
        break;
    case SH_OBJECT_CODE_LENGTH:
        *params = compiler->getInfoSink().obj.size() + 1;
        break;
    case SH_ACTIVE_UNIFORMS:
        *params = compiler->getExpandedUniforms().size();
        break;
    case SH_ACTIVE_UNIFORM_MAX_LENGTH:
        *params = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        break;
    case SH_ACTIVE_ATTRIBUTES:
        *params = compiler->getAttributes().size();
        break;
    case SH_ACTIVE_ATTRIBUTE_MAX_LENGTH:
        *params = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        break;
    case SH_VARYINGS:
        *params = compiler->getExpandedVaryings().size();
        break;
    case SH_VARYING_MAX_LENGTH:
        *params = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        break;
    case SH_MAPPED_NAME_MAX_LENGTH:
        // Use longer length than MAX_SHORTENED_IDENTIFIER_SIZE to
        // handle array and struct dereferences.
        *params = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        break;
    case SH_NAME_MAX_LENGTH:
        *params = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        break;
    case SH_HASHED_NAME_MAX_LENGTH:
        if (compiler->getHashFunction() == NULL) {
            *params = 0;
        } else {
            // 64 bits hashing output requires 16 bytes for hex 
            // representation.
            const char HashedNamePrefix[] = HASHED_NAME_PREFIX;
            (void)HashedNamePrefix;
            *params = 16 + sizeof(HashedNamePrefix);
        }
        break;
    case SH_HASHED_NAMES_COUNT:
        *params = compiler->getNameMap().size();
        break;
    case SH_SHADER_VERSION:
        *params = compiler->getShaderVersion();
        break;
    case SH_RESOURCES_STRING_LENGTH:
        *params = compiler->getBuiltInResourcesString().length() + 1;
        break;
    case SH_OUTPUT_TYPE:
        *params = compiler->getOutputType();
        break;
    default: UNREACHABLE();
    }
}

//
// Return any compiler log of messages for the application.
//
void ShGetInfoLog(const ShHandle handle, char* infoLog)
{
    if (!handle || !infoLog)
        return;

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (!compiler) return;

    TInfoSink& infoSink = compiler->getInfoSink();
    strcpy(infoLog, infoSink.info.c_str());
}

//
// Return any object code.
//
void ShGetObjectCode(const ShHandle handle, char* objCode)
{
    if (!handle || !objCode)
        return;

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (!compiler) return;

    TInfoSink& infoSink = compiler->getInfoSink();
    strcpy(objCode, infoSink.obj.c_str());
}

void ShGetVariableInfo(const ShHandle handle,
                       ShShaderInfo varType,
                       int index,
                       size_t* length,
                       int* size,
                       sh::GLenum* type,
                       ShPrecisionType* precision,
                       int* staticUse,
                       char* name,
                       char* mappedName)
{
    if (!handle || !size || !type || !precision || !staticUse || !name)
        return;
    ASSERT((varType == SH_ACTIVE_ATTRIBUTES) ||
           (varType == SH_ACTIVE_UNIFORMS) ||
           (varType == SH_VARYINGS));

    TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (compiler == 0)
        return;

    const sh::ShaderVariable *varInfo = GetVariable(compiler, varType, index);
    if (!varInfo)
    {
        return;
    }

    if (length) *length = varInfo->name.size();
    *size = varInfo->elementCount();
    *type = varInfo->type;
    *precision = ConvertPrecision(varInfo->precision);
    *staticUse = varInfo->staticUse ? 1 : 0;

    // This size must match that queried by
    // SH_ACTIVE_UNIFORM_MAX_LENGTH, SH_ACTIVE_ATTRIBUTE_MAX_LENGTH, SH_VARYING_MAX_LENGTH
    // in ShGetInfo, below.
    size_t variableLength = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
    ASSERT(CheckVariableMaxLengths(handle, variableLength));
    strncpy(name, varInfo->name.c_str(), variableLength);
    name[variableLength - 1] = 0;
    if (mappedName)
    {
        // This size must match that queried by
        // SH_MAPPED_NAME_MAX_LENGTH in ShGetInfo, below.
        size_t maxMappedNameLength = 1 + GetGlobalMaxTokenSize(compiler->getShaderSpec());
        ASSERT(CheckMappedNameMaxLength(handle, maxMappedNameLength));
        strncpy(mappedName, varInfo->mappedName.c_str(), maxMappedNameLength);
        mappedName[maxMappedNameLength - 1] = 0;
    }
}

void ShGetNameHashingEntry(const ShHandle handle,
                           int index,
                           char* name,
                           char* hashedName)
{
    if (!handle || !name || !hashedName || index < 0)
        return;

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TCompiler* compiler = base->getAsCompiler();
    if (!compiler) return;

    const NameMap& nameMap = compiler->getNameMap();
    if (index >= static_cast<int>(nameMap.size()))
        return;

    NameMap::const_iterator it = nameMap.begin();
    for (int i = 0; i < index; ++i)
        ++it;

    size_t len = it->first.length() + 1;
    size_t max_len = 0;
    ShGetInfo(handle, SH_NAME_MAX_LENGTH, &max_len);
    if (len > max_len) {
        ASSERT(false);
        len = max_len;
    }
    strncpy(name, it->first.c_str(), len);
    // To be on the safe side in case the source is longer than expected.
    name[len - 1] = '\0';

    len = it->second.length() + 1;
    max_len = 0;
    ShGetInfo(handle, SH_HASHED_NAME_MAX_LENGTH, &max_len);
    if (len > max_len) {
        ASSERT(false);
        len = max_len;
    }
    strncpy(hashedName, it->second.c_str(), len);
    // To be on the safe side in case the source is longer than expected.
    hashedName[len - 1] = '\0';
}

const std::vector<sh::Uniform> *ShGetUniforms(const ShHandle handle)
{
    return GetShaderVariables<sh::Uniform>(handle, SHADERVAR_UNIFORM);
}

const std::vector<sh::Varying> *ShGetVaryings(const ShHandle handle)
{
    return GetShaderVariables<sh::Varying>(handle, SHADERVAR_VARYING);
}

const std::vector<sh::Attribute> *ShGetAttributes(const ShHandle handle)
{
    return GetShaderVariables<sh::Attribute>(handle, SHADERVAR_ATTRIBUTE);
}

const std::vector<sh::Attribute> *ShGetOutputVariables(const ShHandle handle)
{
    return GetShaderVariables<sh::Attribute>(handle, SHADERVAR_OUTPUTVARIABLE);
}

const std::vector<sh::InterfaceBlock> *ShGetInterfaceBlocks(const ShHandle handle)
{
    return GetShaderVariables<sh::InterfaceBlock>(handle, SHADERVAR_INTERFACEBLOCK);
}

int ShCheckVariablesWithinPackingLimits(
    int maxVectors, ShVariableInfo* varInfoArray, size_t varInfoArraySize)
{
    if (varInfoArraySize == 0)
        return 1;
    ASSERT(varInfoArray);
    std::vector<sh::ShaderVariable> variables;
    for (size_t ii = 0; ii < varInfoArraySize; ++ii)
    {
        sh::ShaderVariable var(varInfoArray[ii].type, varInfoArray[ii].size);
        variables.push_back(var);
    }
    VariablePacker packer;
    return packer.CheckVariablesWithinPackingLimits(maxVectors, variables) ? 1 : 0;
}

bool ShGetInterfaceBlockRegister(const ShHandle handle,
                                 const char *interfaceBlockName,
                                 unsigned int *indexOut)
{
    if (!handle || !interfaceBlockName || !indexOut)
    {
        return false;
    }

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TranslatorHLSL* translator = base->getAsTranslatorHLSL();
    if (!translator)
    {
        return false;
    }

    if (!translator->hasInterfaceBlock(interfaceBlockName))
    {
        return false;
    }

    *indexOut = translator->getInterfaceBlockRegister(interfaceBlockName);
    return true;
}

bool ShGetUniformRegister(const ShHandle handle,
                          const char *uniformName,
                          unsigned int *indexOut)
{
    if (!handle || !uniformName || !indexOut)
    {
        return false;
    }

    TShHandleBase* base = static_cast<TShHandleBase*>(handle);
    TranslatorHLSL* translator = base->getAsTranslatorHLSL();
    if (!translator)
    {
        return false;
    }

    if (!translator->hasUniform(uniformName))
    {
        return false;
    }

    *indexOut = translator->getUniformRegister(uniformName);
    return true;
}
