#ifndef _GLSLIFETIMETESTS_HPP
#define _GLSLIFETIMETESTS_HPP
/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL (ES) Module
 * -----------------------------------------------
 *
 * Copyright 2014 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.
 *
 *//*!
 * \file
 * \brief Common object lifetime tests.
 *//*--------------------------------------------------------------------*/

#include "deRandom.hpp"
#include "deUniquePtr.hpp"
#include "tcuSurface.hpp"
#include "tcuTestCase.hpp"
#include "tcuTestContext.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluRenderContext.hpp"
#include "glwDefs.hpp"
#include "glwEnums.hpp"

#include <vector>

namespace deqp
{
namespace gls
{
namespace LifetimeTests
{
namespace details
{

using de::MovePtr;
using de::Random;
using glu::CallLogWrapper;
using glu::RenderContext;
using std::vector;
using tcu::Surface;
using tcu::TestCaseGroup;
using tcu::TestContext;
using tcu::TestLog;
using namespace glw;

typedef void (CallLogWrapper::*BindFunc)(GLenum target, GLuint name);
typedef void (CallLogWrapper::*GenFunc)(GLsizei n, GLuint *names);
typedef void (CallLogWrapper::*DeleteFunc)(GLsizei n, const GLuint *names);
typedef GLboolean (CallLogWrapper::*ExistsFunc)(GLuint name);

class Context
{
public:
    Context(const RenderContext &renderCtx, TestContext &testCtx) : m_renderCtx(renderCtx), m_testCtx(testCtx)
    {
    }
    const RenderContext &getRenderContext(void) const
    {
        return m_renderCtx;
    }
    TestContext &getTestContext(void) const
    {
        return m_testCtx;
    }
    const Functions &gl(void) const
    {
        return m_renderCtx.getFunctions();
    }
    TestLog &log(void) const
    {
        return m_testCtx.getLog();
    }

private:
    const RenderContext &m_renderCtx;
    TestContext &m_testCtx;
};

class ContextWrapper : public CallLogWrapper
{
public:
    const Context &getContext(void) const
    {
        return m_ctx;
    }
    const RenderContext &getRenderContext(void) const
    {
        return m_ctx.getRenderContext();
    }
    TestContext &getTestContext(void) const
    {
        return m_ctx.getTestContext();
    }
    const Functions &gl(void) const
    {
        return m_ctx.gl();
    }
    TestLog &log(void) const
    {
        return m_ctx.log();
    }
    void enableLogging(bool enable)
    {
        CallLogWrapper::enableLogging(enable);
    }

protected:
    ContextWrapper(const Context &ctx);
    const Context m_ctx;
};

class Binder : public ContextWrapper
{
public:
    virtual ~Binder(void)
    {
    }
    virtual void bind(GLuint name)  = 0;
    virtual GLuint getBinding(void) = 0;
    virtual bool genRequired(void) const
    {
        return true;
    }

protected:
    Binder(const Context &ctx) : ContextWrapper(ctx)
    {
    }
};

class SimpleBinder : public Binder
{
public:
    SimpleBinder(const Context &ctx, BindFunc bindFunc, GLenum bindTarget, GLenum bindingParam,
                 bool genRequired_ = false)
        : Binder(ctx)
        , m_bindFunc(bindFunc)
        , m_bindTarget(bindTarget)
        , m_bindingParam(bindingParam)
        , m_genRequired(genRequired_)
    {
    }

    void bind(GLuint name);
    GLuint getBinding(void);
    bool genRequired(void) const
    {
        return m_genRequired;
    }

private:
    const BindFunc m_bindFunc;
    const GLenum m_bindTarget;
    const GLenum m_bindingParam;
    const bool m_genRequired;
};

class Type : public ContextWrapper
{
public:
    virtual ~Type(void)
    {
    }
    virtual GLuint gen(void)          = 0;
    virtual void release(GLuint name) = 0;
    virtual bool exists(GLuint name)  = 0;
    virtual bool isDeleteFlagged(GLuint name)
    {
        DE_UNREF(name);
        return false;
    }
    virtual Binder *binder(void) const
    {
        return DE_NULL;
    }
    virtual const char *getName(void) const = 0;
    virtual bool nameLingers(void) const
    {
        return false;
    }
    virtual bool genCreates(void) const
    {
        return false;
    }

protected:
    Type(const Context &ctx) : ContextWrapper(ctx)
    {
    }
};

class SimpleType : public Type
{
public:
    SimpleType(const Context &ctx, const char *name, GenFunc genFunc, DeleteFunc deleteFunc, ExistsFunc existsFunc,
               Binder *binder_ = DE_NULL, bool genCreates_ = false)
        : Type(ctx)
        , m_getName(name)
        , m_genFunc(genFunc)
        , m_deleteFunc(deleteFunc)
        , m_existsFunc(existsFunc)
        , m_binder(binder_)
        , m_genCreates(genCreates_)
    {
    }

    GLuint gen(void);
    void release(GLuint name)
    {
        (this->*m_deleteFunc)(1, &name);
    }
    bool exists(GLuint name)
    {
        return (this->*m_existsFunc)(name) != GL_FALSE;
    }
    Binder *binder(void) const
    {
        return m_binder;
    }
    const char *getName(void) const
    {
        return m_getName;
    }
    bool nameLingers(void) const
    {
        return false;
    }
    bool genCreates(void) const
    {
        return m_genCreates;
    }

private:
    const char *const m_getName;
    const GenFunc m_genFunc;
    const DeleteFunc m_deleteFunc;
    const ExistsFunc m_existsFunc;
    Binder *const m_binder;
    const bool m_genCreates;
};

class ProgramType : public Type
{
public:
    ProgramType(const Context &ctx) : Type(ctx)
    {
    }
    bool nameLingers(void) const
    {
        return true;
    }
    bool genCreates(void) const
    {
        return true;
    }
    const char *getName(void) const
    {
        return "program";
    }
    GLuint gen(void)
    {
        return glCreateProgram();
    }
    void release(GLuint name)
    {
        glDeleteProgram(name);
    }
    bool exists(GLuint name)
    {
        return glIsProgram(name) != GL_FALSE;
    }
    bool isDeleteFlagged(GLuint name);
};

class ShaderType : public Type
{
public:
    ShaderType(const Context &ctx) : Type(ctx)
    {
    }
    bool nameLingers(void) const
    {
        return true;
    }
    bool genCreates(void) const
    {
        return true;
    }
    const char *getName(void) const
    {
        return "shader";
    }
    GLuint gen(void)
    {
        return glCreateShader(GL_FRAGMENT_SHADER);
    }
    void release(GLuint name)
    {
        glDeleteShader(name);
    }
    bool exists(GLuint name)
    {
        return glIsShader(name) != GL_FALSE;
    }
    bool isDeleteFlagged(GLuint name);
};

class Attacher : public ContextWrapper
{
public:
    virtual void initAttachment(GLuint seed, GLuint attachment) = 0;
    virtual void attach(GLuint element, GLuint container)       = 0;
    virtual void detach(GLuint element, GLuint container)       = 0;
    virtual GLuint getAttachment(GLuint container)              = 0;
    virtual bool canAttachDeleted(void) const
    {
        return true;
    }

    Type &getElementType(void) const
    {
        return m_elementType;
    }
    Type &getContainerType(void) const
    {
        return m_containerType;
    }
    virtual ~Attacher(void)
    {
    }

protected:
    Attacher(const Context &ctx, Type &elementType, Type &containerType)
        : ContextWrapper(ctx)
        , m_elementType(elementType)
        , m_containerType(containerType)
    {
    }

private:
    Type &m_elementType;
    Type &m_containerType;
};

class InputAttacher : public ContextWrapper
{
public:
    Attacher &getAttacher(void) const
    {
        return m_attacher;
    }
    virtual void drawContainer(GLuint container, Surface &dst) = 0;

protected:
    InputAttacher(Attacher &attacher) : ContextWrapper(attacher.getContext()), m_attacher(attacher)
    {
    }
    Attacher &m_attacher;
};

class OutputAttacher : public ContextWrapper
{
public:
    Attacher &getAttacher(void) const
    {
        return m_attacher;
    }
    virtual void setupContainer(GLuint seed, GLuint container)   = 0;
    virtual void drawAttachment(GLuint attachment, Surface &dst) = 0;

protected:
    OutputAttacher(Attacher &attacher) : ContextWrapper(attacher.getContext()), m_attacher(attacher)
    {
    }
    Attacher &m_attacher;
};

class Types : public ContextWrapper
{
public:
    Types(const Context &ctx) : ContextWrapper(ctx)
    {
    }
    virtual Type &getProgramType(void) = 0;
    const vector<Type *> &getTypes(void)
    {
        return m_types;
    }
    const vector<Attacher *> &getAttachers(void)
    {
        return m_attachers;
    }
    const vector<InputAttacher *> &getInputAttachers(void)
    {
        return m_inAttachers;
    }
    const vector<OutputAttacher *> &getOutputAttachers(void)
    {
        return m_outAttachers;
    }
    virtual ~Types(void)
    {
    }

protected:
    vector<Type *> m_types;
    vector<Attacher *> m_attachers;
    vector<InputAttacher *> m_inAttachers;
    vector<OutputAttacher *> m_outAttachers;
};

class FboAttacher : public Attacher
{
public:
    void initAttachment(GLuint seed, GLuint element);

protected:
    FboAttacher(const Context &ctx, Type &elementType, Type &containerType) : Attacher(ctx, elementType, containerType)
    {
    }
    virtual void initStorage(void) = 0;
};

class FboInputAttacher : public InputAttacher
{
public:
    FboInputAttacher(FboAttacher &attacher) : InputAttacher(attacher)
    {
    }
    void drawContainer(GLuint container, Surface &dst);
};

class FboOutputAttacher : public OutputAttacher
{
public:
    FboOutputAttacher(FboAttacher &attacher) : OutputAttacher(attacher)
    {
    }
    void setupContainer(GLuint seed, GLuint container);
    void drawAttachment(GLuint attachment, Surface &dst);
};

class TextureFboAttacher : public FboAttacher
{
public:
    TextureFboAttacher(const Context &ctx, Type &elementType, Type &containerType)
        : FboAttacher(ctx, elementType, containerType)
    {
    }

    void initStorage(void);
    void attach(GLuint element, GLuint container);
    void detach(GLuint element, GLuint container);
    GLuint getAttachment(GLuint container);
};

class RboFboAttacher : public FboAttacher
{
public:
    RboFboAttacher(const Context &ctx, Type &elementType, Type &containerType)
        : FboAttacher(ctx, elementType, containerType)
    {
    }

    void initStorage(void);
    void attach(GLuint element, GLuint container);
    void detach(GLuint element, GLuint container);
    GLuint getAttachment(GLuint container);
};

class ShaderProgramAttacher : public Attacher
{
public:
    ShaderProgramAttacher(const Context &ctx, Type &elementType, Type &containerType)
        : Attacher(ctx, elementType, containerType)
    {
    }

    void initAttachment(GLuint seed, GLuint element);
    void attach(GLuint element, GLuint container);
    void detach(GLuint element, GLuint container);
    GLuint getAttachment(GLuint container);
};

class ShaderProgramInputAttacher : public InputAttacher
{
public:
    ShaderProgramInputAttacher(Attacher &attacher) : InputAttacher(attacher)
    {
    }

    void drawContainer(GLuint container, Surface &dst);
};

class ES2Types : public Types
{
public:
    ES2Types(const Context &ctx);
    Type &getProgramType(void)
    {
        return m_programType;
    }

protected:
    SimpleBinder m_bufferBind;
    SimpleType m_bufferType;
    SimpleBinder m_textureBind;
    SimpleType m_textureType;
    SimpleBinder m_rboBind;
    SimpleType m_rboType;
    SimpleBinder m_fboBind;
    SimpleType m_fboType;
    ShaderType m_shaderType;
    ProgramType m_programType;
    TextureFboAttacher m_texFboAtt;
    FboInputAttacher m_texFboInAtt;
    FboOutputAttacher m_texFboOutAtt;
    RboFboAttacher m_rboFboAtt;
    FboInputAttacher m_rboFboInAtt;
    FboOutputAttacher m_rboFboOutAtt;
    ShaderProgramAttacher m_shaderAtt;
    ShaderProgramInputAttacher m_shaderInAtt;
};

MovePtr<TestCaseGroup> createGroup(TestContext &testCtx, Type &type);
void addTestCases(TestCaseGroup &group, Types &types);

struct Rectangle
{
    Rectangle(GLint x_, GLint y_, GLint width_, GLint height_) : x(x_), y(y_), width(width_), height(height_)
    {
    }
    GLint x;
    GLint y;
    GLint width;
    GLint height;
};

Rectangle randomViewport(const RenderContext &ctx, GLint maxWidth, GLint maxHeight, Random &rnd);
void setViewport(const RenderContext &renderCtx, const Rectangle &rect);
void readRectangle(const RenderContext &renderCtx, const Rectangle &rect, Surface &dst);

} // namespace details

using details::BindFunc;
using details::DeleteFunc;
using details::ExistsFunc;
using details::GenFunc;

using details::Attacher;
using details::Binder;
using details::Context;
using details::ES2Types;
using details::InputAttacher;
using details::OutputAttacher;
using details::SimpleBinder;
using details::SimpleType;
using details::Type;
using details::Types;

using details::addTestCases;
using details::createGroup;

using details::randomViewport;
using details::readRectangle;
using details::Rectangle;
using details::setViewport;

} // namespace LifetimeTests
} // namespace gls
} // namespace deqp

#endif // _GLSLIFETIMETESTS_HPP
