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

#ifndef SkSLUniformCTypes_DEFINED
#define SkSLUniformCTypes_DEFINED

#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLString.h"
#include "src/sksl/ir/SkSLType.h"
#include "src/sksl/ir/SkSLVariable.h"

namespace SkSL {

// This uses templates to define dirtyExpression(), saveState() and setUniform(). Each template can
// reference token names formatted ${name} that are replaced with the actual values passed into the
// functions.
//
// dirtyExpression() and saveState() support the following tokens:
//  - ${newVar} replaced with value of newValueVarName (1st argument)
//  - ${oldVar} replaced with value of oldValueVarName (2nd argument)
//
// setUniform() supports these tokens:
//  - ${pdman} replaced with value of pdmanName (1st argument)
//  - ${uniform} replaced with value of uniformHandleName (2nd argument)
//  - ${var} replaced with value of valueVarName (3rd argument)
//
// All templates and C++ snippets should produce valid expressions, but do not need to include
// semicolons or newlines, which will be handled by the code generation itself.
class UniformCTypeMapper {
public:
    // Create a templated mapper that does not support state tracking
    UniformCTypeMapper(Layout::CType ctype, const std::vector<String>& skslTypes,
            const char* setUniformFormat)
        : UniformCTypeMapper(ctype, skslTypes, setUniformFormat, false, "", "", "") { }

    // Create a templated mapper that provides extra patterns for the state
    // tracking expressions.
    UniformCTypeMapper(Layout::CType ctype, const std::vector<String>& skslTypes,
            const String& setUniformFormat, const String& defaultValue,
            const String& dirtyExpressionFormat, const String& saveStateFormat)
        : UniformCTypeMapper(ctype, skslTypes, setUniformFormat,
                true, defaultValue, dirtyExpressionFormat, saveStateFormat) { }

    // Returns nullptr if the type and layout are not supported; the returned pointer's ownership
    // is not transfered to the caller.
    //
    // The returned mapper can support tracking even if tracking is disabled based on the flags in
    // the layout.
    static const UniformCTypeMapper* Get(const Context& context, const Type& type,
                                         const Layout& layout);

    static const UniformCTypeMapper* Get(const Context& context, const Variable& variable) {
        return Get(context, variable.fType, variable.fModifiers.fLayout);
    }

    // The C++ type name that this mapper applies to
    Layout::CType ctype() const {
        return fCType;
    }

    // The sksl type names that the mapper's ctype can be mapped to
    const std::vector<String>& supportedTypeNames() const {
        return fSKSLTypes;
    }

    // Whether or not this handler knows how to write state tracking code
    // for the uniform variables
    bool supportsTracking() const {
        return fSupportsTracking;
    }

    // What the C++ class fields are initialized to in the GLSLFragmentProcessor The empty string
    // implies the no-arg constructor is suitable. This is not used if supportsTracking() returns
    // false.
    //
    // The returned snippet will be a valid as the lhs of an assignment.
    const String& defaultValue() const {
        return fDefaultValue;
    }

    // Return a boolean expression that returns true if the variables specified by newValueVarName
    // and oldValueVarName have different values. This is ignored if supportsTracking() returns
    // false.
    //
    // The returned snippet will be a valid expression to be inserted into the condition of an 'if'
    // statement.
    String dirtyExpression(const String& newValueVarName, const String& oldValueVarName) const;

    // Return a statement that stores the value of newValueVarName into the variable specified by
    // oldValueVarName. This is ignored if supportsTracking() returns false.
    //
    // The returned snippet will be a valid expression.
    String saveState(const String& newValueVarName, const String& oldValueVarName) const;

    // Return a statement that invokes the appropriate setX method on the GrGLSLProgramDataManager
    // specified by pdmanName, where the uniform is provided by the expression stored in
    // uniformHandleName, and valueVarName is the variable name pointing to the ctype instance
    // holding the new value.
    //
    // The returned snippet will be a valid expression.
    String setUniform(const String& pdmanName, const String& uniformHandleName,
                      const String& valueVarName) const;

    // True if the setUniform() template only uses the value variable once in its expression. The
    // variable does not necessarily get inlined if this returns true, since a local variable may be
    // needed if state tracking is employed for a particular uniform.
    bool canInlineUniformValue() const {
        return fInlineValue;
    }

private:
    UniformCTypeMapper(Layout::CType ctype, const std::vector<String>& skslTypes,
            const String& setUniformFormat, bool enableTracking, const String& defaultValue,
            const String& dirtyExpressionFormat, const String& saveStateFormat);

    Layout::CType fCType;
    std::vector<String> fSKSLTypes;
    String fUniformTemplate;
    bool fInlineValue; // Cached value calculated from fUniformTemplate

    bool fSupportsTracking;
    String fDefaultValue;
    String fDirtyExpressionTemplate;
    String fSaveStateTemplate;
};

}  // namespace SkSL

#endif // SkSLUniformCTypes_DEFINED
