/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ANDROID_HWUI_SKIA_SHADER_H
#define ANDROID_HWUI_SKIA_SHADER_H

#include <SkShader.h>
#include <SkXfermode.h>

#include <GLES2/gl2.h>

#include "Extensions.h"
#include "ProgramCache.h"
#include "TextureCache.h"
#include "GradientCache.h"
#include "Snapshot.h"

namespace android {
namespace uirenderer {

///////////////////////////////////////////////////////////////////////////////
// Base shader
///////////////////////////////////////////////////////////////////////////////

/**
 * Represents a Skia shader. A shader will modify the GL context and active
 * program to recreate the original effect.
 */
struct SkiaShader {
    /**
     * Type of Skia shader in use.
     */
    enum Type {
        kNone,
        kBitmap,
        kLinearGradient,
        kCircularGradient,
        kSweepGradient,
        kCompose
    };

    SkiaShader(Type type, SkShader* key, SkShader::TileMode tileX, SkShader::TileMode tileY,
            SkMatrix* matrix, bool blend);
    virtual ~SkiaShader();

    virtual SkiaShader* copy() = 0;
    void copyFrom(const SkiaShader& shader);

    virtual void describe(ProgramDescription& description, const Extensions& extensions);
    virtual void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
            GLuint* textureUnit);

    inline SkShader *getSkShader() {
        return mKey;
    }

    inline bool blend() const {
        return mBlend;
    }

    Type type() const {
        return mType;
    }

    virtual void set(TextureCache* textureCache, GradientCache* gradientCache) {
        mTextureCache = textureCache;
        mGradientCache = gradientCache;
    }

    virtual void updateTransforms(Program* program, const mat4& modelView,
            const Snapshot& snapshot) {
    }

    uint32_t getGenerationId() {
        return mGenerationId;
    }

    void setMatrix(SkMatrix* matrix) {
        updateLocalMatrix(matrix);
        mGenerationId++;
    }

    void updateLocalMatrix(const SkMatrix* matrix) {
        if (matrix) {
            mat4 localMatrix(*matrix);
            mShaderMatrix.loadInverse(localMatrix);
        } else {
            mShaderMatrix.loadIdentity();
        }
    }

    void computeScreenSpaceMatrix(mat4& screenSpace, const mat4& modelView);

protected:
    SkiaShader() {
    }

    /**
     * The appropriate texture unit must have been activated prior to invoking
     * this method.
     */
    inline void bindTexture(Texture* texture, GLenum wrapS, GLenum wrapT);

    Type mType;
    SkShader* mKey;
    SkShader::TileMode mTileX;
    SkShader::TileMode mTileY;
    bool mBlend;

    TextureCache* mTextureCache;
    GradientCache* mGradientCache;

    mat4 mUnitMatrix;
    mat4 mShaderMatrix;

private:
    uint32_t mGenerationId;
}; // struct SkiaShader


///////////////////////////////////////////////////////////////////////////////
// Implementations
///////////////////////////////////////////////////////////////////////////////

/**
 * A shader that draws a bitmap.
 */
struct SkiaBitmapShader: public SkiaShader {
    SkiaBitmapShader(SkBitmap* bitmap, SkShader* key, SkShader::TileMode tileX,
            SkShader::TileMode tileY, SkMatrix* matrix, bool blend);
    SkiaShader* copy();

    void describe(ProgramDescription& description, const Extensions& extensions);
    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
            GLuint* textureUnit);
    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);

private:
    SkiaBitmapShader() {
    }

    /**
     * This method does not work for n == 0.
     */
    inline bool isPowerOfTwo(unsigned int n) {
        return !(n & (n - 1));
    }

    SkBitmap* mBitmap;
    Texture* mTexture;
    GLenum mWrapS;
    GLenum mWrapT;
}; // struct SkiaBitmapShader

/**
 * A shader that draws a linear gradient.
 */
struct SkiaLinearGradientShader: public SkiaShader {
    SkiaLinearGradientShader(float* bounds, uint32_t* colors, float* positions, int count,
            SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
    ~SkiaLinearGradientShader();
    SkiaShader* copy();

    void describe(ProgramDescription& description, const Extensions& extensions);
    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
            GLuint* textureUnit);
    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);

private:
    SkiaLinearGradientShader() {
    }

    float* mBounds;
    uint32_t* mColors;
    float* mPositions;
    int mCount;
}; // struct SkiaLinearGradientShader

/**
 * A shader that draws a sweep gradient.
 */
struct SkiaSweepGradientShader: public SkiaShader {
    SkiaSweepGradientShader(float x, float y, uint32_t* colors, float* positions, int count,
            SkShader* key, SkMatrix* matrix, bool blend);
    ~SkiaSweepGradientShader();
    SkiaShader* copy();

    virtual void describe(ProgramDescription& description, const Extensions& extensions);
    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
            GLuint* textureUnit);
    void updateTransforms(Program* program, const mat4& modelView, const Snapshot& snapshot);

protected:
    SkiaSweepGradientShader(Type type, float x, float y, uint32_t* colors, float* positions,
            int count, SkShader* key, SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
    SkiaSweepGradientShader() {
    }

    uint32_t* mColors;
    float* mPositions;
    int mCount;
}; // struct SkiaSweepGradientShader

/**
 * A shader that draws a circular gradient.
 */
struct SkiaCircularGradientShader: public SkiaSweepGradientShader {
    SkiaCircularGradientShader(float x, float y, float radius, uint32_t* colors, float* positions,
            int count, SkShader* key,SkShader::TileMode tileMode, SkMatrix* matrix, bool blend);
    SkiaShader* copy();

    void describe(ProgramDescription& description, const Extensions& extensions);

private:
    SkiaCircularGradientShader() {
    }
}; // struct SkiaCircularGradientShader

/**
 * A shader that draws two shaders, composited with an xfermode.
 */
struct SkiaComposeShader: public SkiaShader {
    SkiaComposeShader(SkiaShader* first, SkiaShader* second, SkXfermode::Mode mode, SkShader* key);
    ~SkiaComposeShader();
    SkiaShader* copy();

    void set(TextureCache* textureCache, GradientCache* gradientCache);

    void describe(ProgramDescription& description, const Extensions& extensions);
    void setupProgram(Program* program, const mat4& modelView, const Snapshot& snapshot,
            GLuint* textureUnit);

private:
    SkiaComposeShader(): mCleanup(false) {
    }

    void cleanup() {
        mCleanup = true;
    }

    SkiaShader* mFirst;
    SkiaShader* mSecond;
    SkXfermode::Mode mMode;

    bool mCleanup;
}; // struct SkiaComposeShader

}; // namespace uirenderer
}; // namespace android

#endif // ANDROID_HWUI_SKIA_SHADER_H