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

#ifndef GrGLSLShaderVar_DEFINED
#define GrGLSLShaderVar_DEFINED

#include "GrShaderVar.h"
#include "../glsl/GrGLSL.h"
#include "../glsl/GrGLSLCaps.h"

#define USE_UNIFORM_FLOAT_ARRAYS true

/**
 * Represents a variable in a shader
 */
class GrGLSLShaderVar : public GrShaderVar {
public:
    /**
     * Defaults to a float with no precision specifier
     */
    GrGLSLShaderVar()
        : GrShaderVar()
        , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) {
    }

    GrGLSLShaderVar(const char* name, GrSLType type, int arrayCount = kNonArray,
                    GrSLPrecision precision = kDefault_GrSLPrecision)
        : GrShaderVar(name, type, arrayCount, precision)
        , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
        fUseUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS;
    }

    GrGLSLShaderVar(const char* name, GrSLType type, TypeModifier typeModifier,
                    int arrayCount = kNonArray, GrSLPrecision precision = kDefault_GrSLPrecision)
        : GrShaderVar(name, type, typeModifier, arrayCount, precision)
        , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
    }

    GrGLSLShaderVar(const GrShaderVar& var)
        : GrShaderVar(var)
        , fUseUniformFloatArrays(USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != var.getType());
    }

    GrGLSLShaderVar(const GrGLSLShaderVar& var)
        : GrShaderVar(var.c_str(), var.getType(), var.getTypeModifier(),
                      var.getArrayCount(), var.getPrecision())
        , fUseUniformFloatArrays(var.fUseUniformFloatArrays)
        , fLayoutQualifier(var.fLayoutQualifier)
        , fExtraModifiers(var.fExtraModifiers) {
        SkASSERT(kVoid_GrSLType != var.getType());
    }

    /**
     * Values for array count that have special meaning. We allow 1-sized arrays.
     */
    enum {
        kNonArray     =  0, // not an array
        kUnsizedArray = -1, // an unsized array (declared with [])
    };

    /**
     * Sets as a non-array.
     */
    void set(GrSLType type,
             TypeModifier typeModifier,
             const SkString& name,
             GrSLPrecision precision = kDefault_GrSLPrecision,
             const char* layoutQualifier = nullptr,
             const char* extraModifiers = nullptr,
             bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeAcceptsPrecision(type));
        INHERITED::set(type, name, typeModifier, precision);
        fLayoutQualifier = layoutQualifier;
        if (extraModifiers) {
            fExtraModifiers.printf("%s ", extraModifiers);
        }
        fUseUniformFloatArrays = useUniformFloatArrays;
    }

    /**
     * Sets as a non-array.
     */
    void set(GrSLType type,
             TypeModifier typeModifier,
             const char* name,
             GrSLPrecision precision = kDefault_GrSLPrecision,
             const char* layoutQualifier = nullptr,
             const char* extraModifiers = nullptr,
             bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeAcceptsPrecision(type));
        INHERITED::set(type, name, typeModifier, precision);
        fLayoutQualifier = layoutQualifier;
        if (extraModifiers) {
            fExtraModifiers.printf("%s ", extraModifiers);
        }
        fUseUniformFloatArrays = useUniformFloatArrays;
    }

    /**
     * Set all var options
     */
    void set(GrSLType type,
             TypeModifier typeModifier,
             const SkString& name,
             int count,
             GrSLPrecision precision = kDefault_GrSLPrecision,
             const char* layoutQualifier = nullptr,
             const char* extraModifiers = nullptr,
             bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeAcceptsPrecision(type));
        INHERITED::set(type, name, typeModifier, precision, count);
        fLayoutQualifier = layoutQualifier;
        if (extraModifiers) {
            fExtraModifiers.printf("%s ", extraModifiers);
        }
        fUseUniformFloatArrays = useUniformFloatArrays;
    }

    /**
     * Set all var options
     */
    void set(GrSLType type,
             TypeModifier typeModifier,
             const char* name,
             int count,
             GrSLPrecision precision = kDefault_GrSLPrecision,
             const char* layoutQualifier = nullptr,
             const char* extraModifiers = nullptr,
             bool useUniformFloatArrays = USE_UNIFORM_FLOAT_ARRAYS) {
        SkASSERT(kVoid_GrSLType != type);
        SkASSERT(kDefault_GrSLPrecision == precision || GrSLTypeAcceptsPrecision(type));
        INHERITED::set(type, name, typeModifier, precision, count);
        fLayoutQualifier = layoutQualifier;
        if (extraModifiers) {
            fExtraModifiers.printf("%s ", extraModifiers);
        }
        fUseUniformFloatArrays = useUniformFloatArrays;
    }

    /**
     * Set the layout qualifier
     */
    void setLayoutQualifier(const char* layoutQualifier) {
        fLayoutQualifier = layoutQualifier;
    }

    void addModifier(const char* modifier) {
        if (modifier) {
            fExtraModifiers.appendf("%s ", modifier);
        }
    }

    /**
     * Write a declaration of this variable to out.
     */
    void appendDecl(const GrGLSLCaps* glslCaps, SkString* out) const {
        SkASSERT(kDefault_GrSLPrecision == fPrecision || GrSLTypeAcceptsPrecision(fType));
        if (!fLayoutQualifier.isEmpty()) {
            out->appendf("layout(%s) ", fLayoutQualifier.c_str());
        }
        out->append(fExtraModifiers);
        if (this->getTypeModifier() != kNone_TypeModifier) {
            out->append(TypeModifierString(glslCaps, this->getTypeModifier()));
            out->append(" ");
        }
        GrSLType effectiveType = this->getType();
        if (glslCaps->usesPrecisionModifiers() && GrSLTypeAcceptsPrecision(effectiveType)) {
            // Desktop GLSL has added precision qualifiers but they don't do anything.
            out->appendf("%s ", GrGLSLPrecisionString(fPrecision));
        }
        if (this->isArray()) {
            if (this->isUnsizedArray()) {
                out->appendf("%s %s[]",
                             GrGLSLTypeString(effectiveType),
                             this->getName().c_str());
            } else {
                SkASSERT(this->getArrayCount() > 0);
                out->appendf("%s %s[%d]",
                             GrGLSLTypeString(effectiveType),
                             this->getName().c_str(),
                             this->getArrayCount());
            }
        } else {
            out->appendf("%s %s",
                         GrGLSLTypeString(effectiveType),
                         this->getName().c_str());
        }
    }

    void appendArrayAccess(int index, SkString* out) const {
        out->appendf("%s[%d]%s",
                     this->getName().c_str(),
                     index,
                     fUseUniformFloatArrays ? "" : ".x");
    }

    void appendArrayAccess(const char* indexName, SkString* out) const {
        out->appendf("%s[%s]%s",
                     this->getName().c_str(),
                     indexName,
                     fUseUniformFloatArrays ? "" : ".x");
    }

private:
    static const char* TypeModifierString(const GrGLSLCaps* glslCaps, TypeModifier t) {
        GrGLSLGeneration gen = glslCaps->generation();
        switch (t) {
            case kNone_TypeModifier:
                return "";
            case kIn_TypeModifier:
                return "in";
            case kInOut_TypeModifier:
                return "inout";
            case kOut_TypeModifier:
                return "out";
            case kUniform_TypeModifier:
                return "uniform";
            case kAttribute_TypeModifier:
                return k110_GrGLSLGeneration == gen ? "attribute" : "in";
            case kVaryingIn_TypeModifier:
                return k110_GrGLSLGeneration == gen ? "varying" : "in";
            case kVaryingOut_TypeModifier:
                return k110_GrGLSLGeneration == gen ? "varying" : "out";
            default:
                SkFAIL("Unknown shader variable type modifier.");
                return ""; // suppress warning
        }
    }

    /// Work around driver bugs on some hardware that don't correctly
    /// support uniform float []
    bool        fUseUniformFloatArrays;

    SkString    fLayoutQualifier;
    SkString    fExtraModifiers;

    typedef GrShaderVar INHERITED;
};

#endif
