//
// Copyright 2012 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 "compiler/translator/DirectiveHandler.h"

#include <sstream>

#include "angle_gl.h"
#include "common/debug.h"
#include "compiler/translator/Common.h"
#include "compiler/translator/Diagnostics.h"

namespace sh
{

static TBehavior getBehavior(const std::string &str)
{
    const char kRequire[] = "require";
    const char kEnable[]  = "enable";
    const char kDisable[] = "disable";
    const char kWarn[]    = "warn";

    if (str == kRequire)
        return EBhRequire;
    else if (str == kEnable)
        return EBhEnable;
    else if (str == kDisable)
        return EBhDisable;
    else if (str == kWarn)
        return EBhWarn;
    return EBhUndefined;
}

TDirectiveHandler::TDirectiveHandler(TExtensionBehavior &extBehavior,
                                     TDiagnostics &diagnostics,
                                     int &shaderVersion,
                                     sh::GLenum shaderType,
                                     bool debugShaderPrecisionSupported)
    : mExtensionBehavior(extBehavior),
      mDiagnostics(diagnostics),
      mShaderVersion(shaderVersion),
      mShaderType(shaderType),
      mDebugShaderPrecisionSupported(debugShaderPrecisionSupported)
{}

TDirectiveHandler::~TDirectiveHandler() {}

void TDirectiveHandler::handleError(const angle::pp::SourceLocation &loc, const std::string &msg)
{
    mDiagnostics.error(loc, msg.c_str(), "");
}

void TDirectiveHandler::handlePragma(const angle::pp::SourceLocation &loc,
                                     const std::string &name,
                                     const std::string &value,
                                     bool stdgl)
{
    if (stdgl)
    {
        const char kInvariant[] = "invariant";
        const char kAll[]       = "all";

        if (name == kInvariant && value == kAll)
        {
            if (mShaderVersion == 300 && mShaderType == GL_FRAGMENT_SHADER)
            {
                // ESSL 3.00.4 section 4.6.1
                mDiagnostics.error(
                    loc, "#pragma STDGL invariant(all) can not be used in fragment shader",
                    name.c_str());
            }
            mPragma.stdgl.invariantAll = true;
        }
        // The STDGL pragma is used to reserve pragmas for use by future
        // revisions of GLSL.  Do not generate an error on unexpected
        // name and value.
        return;
    }
    else
    {
        const char kOptimize[]             = "optimize";
        const char kDebug[]                = "debug";
        const char kDebugShaderPrecision[] = "webgl_debug_shader_precision";
        const char kOn[]                   = "on";
        const char kOff[]                  = "off";

        bool invalidValue = false;
        if (name == kOptimize)
        {
            if (value == kOn)
                mPragma.optimize = true;
            else if (value == kOff)
                mPragma.optimize = false;
            else
                invalidValue = true;
        }
        else if (name == kDebug)
        {
            if (value == kOn)
                mPragma.debug = true;
            else if (value == kOff)
                mPragma.debug = false;
            else
                invalidValue = true;
        }
        else if (name == kDebugShaderPrecision && mDebugShaderPrecisionSupported)
        {
            if (value == kOn)
                mPragma.debugShaderPrecision = true;
            else if (value == kOff)
                mPragma.debugShaderPrecision = false;
            else
                invalidValue = true;
        }
        else
        {
            mDiagnostics.report(angle::pp::Diagnostics::PP_UNRECOGNIZED_PRAGMA, loc, name);
            return;
        }

        if (invalidValue)
        {
            mDiagnostics.error(loc, "invalid pragma value - 'on' or 'off' expected", value.c_str());
        }
    }
}

void TDirectiveHandler::handleExtension(const angle::pp::SourceLocation &loc,
                                        const std::string &name,
                                        const std::string &behavior)
{
    const char kExtAll[] = "all";

    TBehavior behaviorVal = getBehavior(behavior);
    if (behaviorVal == EBhUndefined)
    {
        mDiagnostics.error(loc, "behavior invalid", name.c_str());
        return;
    }

    if (name == kExtAll)
    {
        if (behaviorVal == EBhRequire)
        {
            mDiagnostics.error(loc, "extension cannot have 'require' behavior", name.c_str());
        }
        else if (behaviorVal == EBhEnable)
        {
            mDiagnostics.error(loc, "extension cannot have 'enable' behavior", name.c_str());
        }
        else
        {
            for (TExtensionBehavior::iterator iter = mExtensionBehavior.begin();
                 iter != mExtensionBehavior.end(); ++iter)
                iter->second = behaviorVal;
        }
        return;
    }

    TExtensionBehavior::iterator iter = mExtensionBehavior.find(GetExtensionByName(name.c_str()));
    if (iter != mExtensionBehavior.end())
    {
        iter->second = behaviorVal;
        // OVR_multiview is implicitly enabled when OVR_multiview2 is enabled
        if (name == "GL_OVR_multiview2")
        {
            const std::string multiview = "GL_OVR_multiview";
            TExtensionBehavior::iterator iterMultiview =
                mExtensionBehavior.find(GetExtensionByName(multiview.c_str()));
            if (iterMultiview != mExtensionBehavior.end())
            {
                iterMultiview->second = behaviorVal;
            }
        }
        return;
    }

    switch (behaviorVal)
    {
        case EBhRequire:
            mDiagnostics.error(loc, "extension is not supported", name.c_str());
            break;
        case EBhEnable:
        case EBhWarn:
        case EBhDisable:
            mDiagnostics.warning(loc, "extension is not supported", name.c_str());
            break;
        default:
            UNREACHABLE();
            break;
    }
}

void TDirectiveHandler::handleVersion(const angle::pp::SourceLocation &loc,
                                      int version,
                                      ShShaderSpec spec)
{
    if (((version == 100 || version == 300 || version == 310 || version == 320) &&
         !IsDesktopGLSpec(spec)) ||
        IsDesktopGLSpec(spec))
    {
        mShaderVersion = version;
    }
    else
    {
        std::stringstream stream = sh::InitializeStream<std::stringstream>();
        stream << version;
        std::string str = stream.str();
        mDiagnostics.error(loc, "client/version number not supported", str.c_str());
    }
}

}  // namespace sh
