/*
 * Copyright 2016 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SKSL_UTIL
#define SKSL_UTIL

#include <cstdarg>
#include <memory>
#include "stdlib.h"
#include "string.h"
#include "assert.h"
#include "SkSLString.h"
#include "SkSLStringStream.h"

#ifndef SKSL_STANDALONE
#include "GrContextOptions.h"
#include "GrShaderCaps.h"
#endif

#ifdef SKSL_STANDALONE
#if defined(_WIN32) || defined(__SYMBIAN32__)
#define SKSL_BUILD_FOR_WIN
#endif
#else
#ifdef SK_BUILD_FOR_WIN
#define SKSL_BUILD_FOR_WIN
#endif // SK_BUILD_FOR_WIN
#endif // SKSL_STANDALONE

namespace SkSL {

#ifdef SKSL_STANDALONE

// we're being compiled standalone, so we don't have access to caps...
enum GrGLSLGeneration {
    k110_GrGLSLGeneration,
    k130_GrGLSLGeneration,
    k140_GrGLSLGeneration,
    k150_GrGLSLGeneration,
    k330_GrGLSLGeneration,
    k400_GrGLSLGeneration,
    k420_GrGLSLGeneration,
    k310es_GrGLSLGeneration,
    k320es_GrGLSLGeneration,
};

#define SKSL_CAPS_CLASS StandaloneShaderCaps
class StandaloneShaderCaps {
public:
    GrGLSLGeneration generation() const {
        return k400_GrGLSLGeneration;
    }

    bool canUseMinAndAbsTogether() const {
        return true;
    }

    bool mustForceNegatedAtanParamToFloat() const {
        return false;
    }

    bool shaderDerivativeSupport() const {
        return true;
    }

    bool usesPrecisionModifiers() const {
        return true;
    }

    bool mustDeclareFragmentShaderOutput() const {
        return true;
    }

    bool fbFetchSupport() const {
        return true;
    }

    bool fbFetchNeedsCustomOutput() const {
        return false;
    }

    bool bindlessTextureSupport() const {
        return false;
    }

    bool dropsTileOnZeroDivide() const {
        return false;
    }

    bool flatInterpolationSupport() const {
        return true;
    }

    bool noperspectiveInterpolationSupport() const {
        return true;
    }

    bool multisampleInterpolationSupport() const {
        return true;
    }

    bool sampleVariablesSupport() const {
        return true;
    }

    bool sampleMaskOverrideCoverageSupport() const {
        return true;
    }

    bool externalTextureSupport() const {
        return true;
    }

    bool texelFetchSupport() const {
        return true;
    }

    bool imageLoadStoreSupport() const {
        return true;
    }

    bool mustEnableAdvBlendEqs() const {
        return false;
    }

    bool mustEnableSpecificAdvBlendEqs() const {
        return false;
    }

    bool canUseAnyFunctionInShader() const {
        return false;
    }

    bool floatIs32Bits() const {
        return true;
    }

    bool integerSupport() const {
        return false;
    }

    const char* shaderDerivativeExtensionString() const {
        return nullptr;
    }

    const char* fragCoordConventionsExtensionString() const {
        return nullptr;
    }

    const char* imageLoadStoreExtensionString() const {
        return nullptr;
    }

    const char* geometryShaderExtensionString() const {
        return nullptr;
    }

    const char* gsInvocationsExtensionString() const {
        return nullptr;
    }

    const char* versionDeclString() const {
        return "";
    }

    bool gsInvocationsSupport() const {
        return true;
    }

    bool canUseFractForNegativeValues() const {
        return true;
    }

    bool canUseFragCoord() const {
        return true;
    }
};

extern StandaloneShaderCaps standaloneCaps;

#else

#define SKSL_CAPS_CLASS GrShaderCaps
// Various sets of caps for use in tests
class ShaderCapsFactory {
public:
    static sk_sp<GrShaderCaps> Default() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fShaderDerivativeSupport = true;
        return result;
    }

    static sk_sp<GrShaderCaps> Version450Core() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 450 core";
        return result;
    }

    static sk_sp<GrShaderCaps> Version110() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 110";
        result->fGLSLGeneration = GrGLSLGeneration::k110_GrGLSLGeneration;
        return result;
    }

    static sk_sp<GrShaderCaps> UsesPrecisionModifiers() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fUsesPrecisionModifiers = true;
        return result;
    }

    static sk_sp<GrShaderCaps> CannotUseMinAndAbsTogether() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fCanUseMinAndAbsTogether = false;
        return result;
    }

    static sk_sp<GrShaderCaps> CannotUseFractForNegativeValues() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fCanUseFractForNegativeValues = false;
        return result;
    }

    static sk_sp<GrShaderCaps> MustForceNegatedAtanParamToFloat() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fMustForceNegatedAtanParamToFloat = true;
        return result;
    }

    static sk_sp<GrShaderCaps> ShaderDerivativeExtensionString() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fShaderDerivativeSupport = true;
        result->fShaderDerivativeExtensionString = "GL_OES_standard_derivatives";
        result->fUsesPrecisionModifiers = true;
        return result;
    }

    static sk_sp<GrShaderCaps> FragCoordsOld() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 110";
        result->fGLSLGeneration = GrGLSLGeneration::k110_GrGLSLGeneration;
        result->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions";
        return result;
    }

    static sk_sp<GrShaderCaps> FragCoordsNew() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fFragCoordConventionsExtensionString = "GL_ARB_fragment_coord_conventions";
        return result;
    }

    static sk_sp<GrShaderCaps> GeometryShaderSupport() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fGeometryShaderSupport = true;
        result->fGSInvocationsSupport = true;
        return result;
    }

    static sk_sp<GrShaderCaps> NoGSInvocationsSupport() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fGeometryShaderSupport = true;
        result->fGSInvocationsSupport = false;
        return result;
    }

    static sk_sp<GrShaderCaps> GeometryShaderExtensionString() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 310es";
        result->fGeometryShaderSupport = true;
        result->fGeometryShaderExtensionString = "GL_EXT_geometry_shader";
        result->fGSInvocationsSupport = true;
        return result;
    }

    static sk_sp<GrShaderCaps> GSInvocationsExtensionString() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fGeometryShaderSupport = true;
        result->fGSInvocationsSupport = true;
        result->fGSInvocationsExtensionString = "GL_ARB_gpu_shader5";
        return result;
    }

    static sk_sp<GrShaderCaps> VariousCaps() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fExternalTextureSupport = true;
        result->fFBFetchSupport = false;
        result->fDropsTileOnZeroDivide = true;
        result->fTexelFetchSupport = true;
        result->fCanUseAnyFunctionInShader = false;
        return result;
    }

    static sk_sp<GrShaderCaps> CannotUseFragCoord() {
        sk_sp<GrShaderCaps> result = sk_make_sp<GrShaderCaps>(GrContextOptions());
        result->fVersionDeclString = "#version 400";
        result->fCanUseFragCoord = false;
        return result;
    }
};
#endif

void write_stringstream(const StringStream& d, OutputStream& out);

#if _MSC_VER
#define NORETURN __declspec(noreturn)
#else
#define NORETURN __attribute__((__noreturn__))
#endif

NORETURN void sksl_abort();

} // namespace

#ifdef SKSL_STANDALONE
#define ASSERT(x) (void)((x) || (ABORT("failed assert(%s): %s:%d\n", #x, __FILE__, __LINE__), 0))
#define ASSERT_RESULT(x) ASSERT(x)
#define SKSL_DEBUGCODE(x) x
#else
#define ASSERT SkASSERT
#define ASSERT_RESULT(x) SkAssertResult(x)
#define SKSL_DEBUGCODE(x) SkDEBUGCODE(x)
#endif

#define SKSL_WARN_UNUSED_RESULT __attribute__((warn_unused_result))

#if defined(__clang__) || defined(__GNUC__)
#define SKSL_PRINTF_LIKE(A, B) __attribute__((format(printf, (A), (B))))
#else
#define SKSL_PRINTF_LIKE(A, B)
#endif

#define ABORT(...) (printf(__VA_ARGS__), sksl_abort())

#endif