/*
 ** Copyright 2011, 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.
 */

#include <sys/socket.h>
#include <sys/ioctl.h>

#include "header.h"
#include "gtest/gtest.h"
#include "hooks.h"

namespace android
{
extern int serverSock, clientSock;
};

void * glNoop();

class SocketContextTest : public ::testing::Test
{
protected:
    DbgContext* dbg;
    gl_hooks_t hooks;
    int sock;
    char * buffer;
    unsigned int bufferSize;

    SocketContextTest() : sock(-1) {
    }

    virtual ~SocketContextTest() {
    }

    virtual void SetUp() {
        dbg = new DbgContext(1, &hooks, 32);
        ASSERT_TRUE(dbg != NULL);
        for (unsigned int i = 0; i < sizeof(hooks) / sizeof(void *); i++)
            ((void **)&hooks)[i] = (void *)glNoop;

        int socks[2] = {-1, -1};
        ASSERT_EQ(0, socketpair(AF_UNIX, SOCK_STREAM, 0, socks));
        clientSock = socks[0];
        sock = socks[1];

        bufferSize = 128;
        buffer = new char [128];
        ASSERT_NE((char *)NULL, buffer);
    }

    virtual void TearDown() {
        close(sock);
        close(clientSock);
        clientSock = -1;
        delete buffer;
    }

    void Write(glesv2debugger::Message & msg) const {
        msg.set_context_id((int)dbg);
        msg.set_type(msg.Response);
        ASSERT_TRUE(msg.has_context_id());
        ASSERT_TRUE(msg.has_function());
        ASSERT_TRUE(msg.has_type());
        ASSERT_TRUE(msg.has_expect_response());
        static std::string str;
        msg.SerializeToString(&str);
        const uint32_t len = str.length();
        ASSERT_EQ(sizeof(len), send(sock, &len, sizeof(len), 0));
        ASSERT_EQ(str.length(), send(sock, str.data(), str.length(), 0));
    }

    void Read(glesv2debugger::Message & msg) {
        int available = 0;
        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
        ASSERT_GT(available, 0);
        uint32_t len = 0;
        ASSERT_EQ(sizeof(len), recv(sock, &len, sizeof(len), 0));
        if (len > bufferSize) {
            bufferSize = len;
            buffer = new char[bufferSize];
            ASSERT_TRUE(buffer != NULL);
        }
        ASSERT_EQ(len, recv(sock, buffer, len, 0));
        msg.Clear();
        msg.ParseFromArray(buffer, len);
        ASSERT_TRUE(msg.has_context_id());
        ASSERT_TRUE(msg.has_function());
        ASSERT_TRUE(msg.has_type());
        ASSERT_TRUE(msg.has_expect_response());
    }

    void CheckNoAvailable() {
        int available = 0;
        ASSERT_EQ(0, ioctl(sock, FIONREAD, &available));
        ASSERT_EQ(available, 0);
    }
};

TEST_F(SocketContextTest, MessageLoopSkip)
{
    static const int arg0 = 45;
    static const float arg7 = -87.2331f;
    static const int arg8 = -3;
    static const int * ret = (int *)870;

    struct Caller : public FunctionCall {
        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
            msg.set_arg0(arg0);
            msg.set_arg7((int &)arg7);
            msg.set_arg8(arg8);
            return ret;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    dbg->expectResponse.Bit(msg.glFinish, true);

    cmd.set_function(cmd.SKIP);
    cmd.set_expect_response(false);
    Write(cmd);

    EXPECT_NE(ret, MessageLoop(caller, msg, msg.glFinish));

    Read(read);
    EXPECT_EQ(read.glFinish, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());
    EXPECT_NE(arg0, read.arg0());
    EXPECT_NE((int &)arg7, read.arg7());
    EXPECT_NE(arg8, read.arg8());

    CheckNoAvailable();
}

TEST_F(SocketContextTest, MessageLoopContinue)
{
    static const int arg0 = GL_FRAGMENT_SHADER;
    static const int ret = -342;
    struct Caller : public FunctionCall {
        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
            msg.set_ret(ret);
            return (int *)ret;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    dbg->expectResponse.Bit(msg.glCreateShader, true);

    cmd.set_function(cmd.CONTINUE);
    cmd.set_expect_response(false); // MessageLoop should automatically skip after continue
    Write(cmd);

    msg.set_arg0(arg0);
    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateShader));

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());
    EXPECT_EQ(arg0, read.arg0());

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.AfterCall, read.type());
    EXPECT_EQ(ret, read.ret());

    CheckNoAvailable();
}

TEST_F(SocketContextTest, MessageLoopGenerateCall)
{
    static const int ret = -342;
    static unsigned int createShader, createProgram;
    createShader = 0;
    createProgram = 0;
    struct Caller : public FunctionCall {
        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
            const int r = (int)_c->glCreateProgram();
            msg.set_ret(r);
            return (int *)r;
        }
        static GLuint CreateShader(const GLenum type) {
            createShader++;
            return type;
        }
        static GLuint CreateProgram() {
            createProgram++;
            return ret;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    hooks.gl.glCreateShader = caller.CreateShader;
    hooks.gl.glCreateProgram = caller.CreateProgram;
    dbg->expectResponse.Bit(msg.glCreateProgram, true);

    cmd.set_function(cmd.glCreateShader);
    cmd.set_arg0(GL_FRAGMENT_SHADER);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.Clear();
    cmd.set_function(cmd.CONTINUE);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.set_function(cmd.glCreateShader);
    cmd.set_arg0(GL_VERTEX_SHADER);
    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
    Write(cmd);

    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));

    Read(read);
    EXPECT_EQ(read.glCreateProgram, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.AfterGeneratedCall, read.type());
    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());

    Read(read);
    EXPECT_EQ(read.glCreateProgram, read.function());
    EXPECT_EQ(read.AfterCall, read.type());
    EXPECT_EQ(ret, read.ret());

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.AfterGeneratedCall, read.type());
    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());

    EXPECT_EQ(2, createShader);
    EXPECT_EQ(1, createProgram);

    CheckNoAvailable();
}

TEST_F(SocketContextTest, MessageLoopSetProp)
{
    static const int ret = -342;
    static unsigned int createShader, createProgram;
    createShader = 0;
    createProgram = 0;
    struct Caller : public FunctionCall {
        const int * operator()(gl_hooks_t::gl_t const * const _c, glesv2debugger::Message & msg) {
            const int r = (int)_c->glCreateProgram();
            msg.set_ret(r);
            return (int *)r;
        }
        static GLuint CreateShader(const GLenum type) {
            createShader++;
            return type;
        }
        static GLuint CreateProgram() {
            createProgram++;
            return ret;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    hooks.gl.glCreateShader = caller.CreateShader;
    hooks.gl.glCreateProgram = caller.CreateProgram;
    dbg->expectResponse.Bit(msg.glCreateProgram, false);

    cmd.set_function(cmd.SETPROP);
    cmd.set_prop(cmd.ExpectResponse);
    cmd.set_arg0(cmd.glCreateProgram);
    cmd.set_arg1(true);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.Clear();
    cmd.set_function(cmd.glCreateShader);
    cmd.set_arg0(GL_FRAGMENT_SHADER);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.set_function(cmd.SETPROP);
    cmd.set_prop(cmd.CaptureDraw);
    cmd.set_arg0(819);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.Clear();
    cmd.set_function(cmd.CONTINUE);
    cmd.set_expect_response(true);
    Write(cmd);

    cmd.set_function(cmd.glCreateShader);
    cmd.set_arg0(GL_VERTEX_SHADER);
    cmd.set_expect_response(false); // MessageLoop should automatically skip afterwards
    Write(cmd);

    EXPECT_EQ((int *)ret, MessageLoop(caller, msg, msg.glCreateProgram));

    EXPECT_TRUE(dbg->expectResponse.Bit(msg.glCreateProgram));
    EXPECT_EQ(819, dbg->captureDraw);

    Read(read);
    EXPECT_EQ(read.glCreateProgram, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.AfterGeneratedCall, read.type());
    EXPECT_EQ(GL_FRAGMENT_SHADER, read.ret());

    Read(read);
    EXPECT_EQ(read.glCreateProgram, read.function());
    EXPECT_EQ(read.AfterCall, read.type());
    EXPECT_EQ(ret, read.ret());

    Read(read);
    EXPECT_EQ(read.glCreateShader, read.function());
    EXPECT_EQ(read.AfterGeneratedCall, read.type());
    EXPECT_EQ(GL_VERTEX_SHADER, read.ret());

    EXPECT_EQ(2, createShader);
    EXPECT_EQ(1, createProgram);

    CheckNoAvailable();
}

TEST_F(SocketContextTest, TexImage2D)
{
    static const GLenum _target = GL_TEXTURE_2D;
    static const GLint _level = 1, _internalformat = GL_RGBA;
    static const GLsizei _width = 2, _height = 2;
    static const GLint _border = 333;
    static const GLenum _format = GL_RGB, _type = GL_UNSIGNED_SHORT_5_6_5;
    static const short _pixels [_width * _height] = {11, 22, 33, 44};
    static unsigned int texImage2D;
    texImage2D = 0;

    struct Caller {
        static void TexImage2D(GLenum target, GLint level, GLint internalformat,
                               GLsizei width, GLsizei height, GLint border,
                               GLenum format, GLenum type, const GLvoid* pixels) {
            EXPECT_EQ(_target, target);
            EXPECT_EQ(_level, level);
            EXPECT_EQ(_internalformat, internalformat);
            EXPECT_EQ(_width, width);
            EXPECT_EQ(_height, height);
            EXPECT_EQ(_border, border);
            EXPECT_EQ(_format, format);
            EXPECT_EQ(_type, type);
            EXPECT_EQ(0, memcmp(_pixels, pixels, sizeof(_pixels)));
            texImage2D++;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    hooks.gl.glTexImage2D = caller.TexImage2D;
    dbg->expectResponse.Bit(msg.glTexImage2D, false);

    Debug_glTexImage2D(_target, _level, _internalformat, _width, _height, _border,
                       _format, _type, _pixels);
    EXPECT_EQ(1, texImage2D);

    Read(read);
    EXPECT_EQ(read.glTexImage2D, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());
    EXPECT_EQ(_target, read.arg0());
    EXPECT_EQ(_level, read.arg1());
    EXPECT_EQ(_internalformat, read.arg2());
    EXPECT_EQ(_width, read.arg3());
    EXPECT_EQ(_height, read.arg4());
    EXPECT_EQ(_border, read.arg5());
    EXPECT_EQ(_format, read.arg6());
    EXPECT_EQ(_type, read.arg7());

    EXPECT_TRUE(read.has_data());
    uint32_t dataLen = 0;
    const unsigned char * data = dbg->Decompress(read.data().data(),
                                 read.data().length(), &dataLen);
    EXPECT_EQ(sizeof(_pixels), dataLen);
    if (sizeof(_pixels) == dataLen)
        EXPECT_EQ(0, memcmp(_pixels, data, sizeof(_pixels)));

    Read(read);
    EXPECT_EQ(read.glTexImage2D, read.function());
    EXPECT_EQ(read.AfterCall, read.type());

    CheckNoAvailable();
}

TEST_F(SocketContextTest, CopyTexImage2D)
{
    static const GLenum _target = GL_TEXTURE_2D;
    static const GLint _level = 1, _internalformat = GL_RGBA;
    static const GLint _x = 9, _y = 99;
    static const GLsizei _width = 2, _height = 3;
    static const GLint _border = 333;
    static const int _pixels [_width * _height] = {11, 22, 33, 44, 55, 66};
    static unsigned int copyTexImage2D, readPixels;
    copyTexImage2D = 0, readPixels = 0;

    struct Caller {
        static void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat,
                                   GLint x, GLint y, GLsizei width, GLsizei height, GLint border) {
            EXPECT_EQ(_target, target);
            EXPECT_EQ(_level, level);
            EXPECT_EQ(_internalformat, internalformat);
            EXPECT_EQ(_x, x);
            EXPECT_EQ(_y, y);
            EXPECT_EQ(_width, width);
            EXPECT_EQ(_height, height);
            EXPECT_EQ(_border, border);
            copyTexImage2D++;
        }
        static void ReadPixels(GLint x, GLint y, GLsizei width, GLsizei height,
                               GLenum format, GLenum type, GLvoid* pixels) {
            EXPECT_EQ(_x, x);
            EXPECT_EQ(_y, y);
            EXPECT_EQ(_width, width);
            EXPECT_EQ(_height, height);
            EXPECT_EQ(GL_RGBA, format);
            EXPECT_EQ(GL_UNSIGNED_BYTE, type);
            ASSERT_TRUE(pixels != NULL);
            memcpy(pixels, _pixels, sizeof(_pixels));
            readPixels++;
        }
    } caller;
    glesv2debugger::Message msg, read, cmd;
    hooks.gl.glCopyTexImage2D = caller.CopyTexImage2D;
    hooks.gl.glReadPixels = caller.ReadPixels;
    dbg->expectResponse.Bit(msg.glCopyTexImage2D, false);

    Debug_glCopyTexImage2D(_target, _level, _internalformat, _x, _y, _width, _height,
                           _border);
    ASSERT_EQ(1, copyTexImage2D);
    ASSERT_EQ(1, readPixels);

    Read(read);
    EXPECT_EQ(read.glCopyTexImage2D, read.function());
    EXPECT_EQ(read.BeforeCall, read.type());
    EXPECT_EQ(_target, read.arg0());
    EXPECT_EQ(_level, read.arg1());
    EXPECT_EQ(_internalformat, read.arg2());
    EXPECT_EQ(_x, read.arg3());
    EXPECT_EQ(_y, read.arg4());
    EXPECT_EQ(_width, read.arg5());
    EXPECT_EQ(_height, read.arg6());
    EXPECT_EQ(_border, read.arg7());

    EXPECT_TRUE(read.has_data());
    EXPECT_EQ(read.ReferencedImage, read.data_type());
    EXPECT_EQ(GL_RGBA, read.pixel_format());
    EXPECT_EQ(GL_UNSIGNED_BYTE, read.pixel_type());
    uint32_t dataLen = 0;
    unsigned char * const data = dbg->Decompress(read.data().data(),
                                 read.data().length(), &dataLen);
    ASSERT_EQ(sizeof(_pixels), dataLen);
    for (unsigned i = 0; i < sizeof(_pixels) / sizeof(*_pixels); i++)
        EXPECT_EQ(_pixels[i], ((const int *)data)[i]) << "xor with 0 ref is identity";
    free(data);

    Read(read);
    EXPECT_EQ(read.glCopyTexImage2D, read.function());
    EXPECT_EQ(read.AfterCall, read.type());

    CheckNoAvailable();
}