#include "socktest.h"
using namespace LCBTest;
using std::string;
using std::vector;

class SockReentrantTest : public SockTest {};

/**
 * This file tests various reentrant actions within the socket handlers.
 */
class ReadAgainAction : public IOActions {
public:
    ReadAgainAction() {
        callCount = 0;
        sf = NULL;
    }

    virtual ~ReadAgainAction(){}

    int callCount;
    SendFuture *sf;
    void onRead(ESocket *s, size_t nr) {
        if (callCount++) {
            s->parent->stop();
            return;
        }

        s->reqrd(nr * 2);
        sf = new SendFuture(string(nr, '$'));
        s->conn->setSend(sf);
        s->schedule();
    }
    void onError(ESocket *) {
        // do nothing
    }
};

class CallCountBreakCondition : public BreakCondition {
public:
    ReadAgainAction *raa;
    CallCountBreakCondition(ReadAgainAction *action) {
        raa = action;
    }
protected:
    bool shouldBreakImpl() { return raa->callCount >= 2; }
};

TEST_F(SockReentrantTest, testReadAgain)
{
    ESocket sock;
    loop->connect(&sock);
    SendFuture sf1(string(100, '#'));
    sock.conn->setSend(&sf1);
    sock.reqrd(100);
    sock.schedule();

    ReadAgainAction raa;
    CallCountBreakCondition bc(&raa);
    sock.setActions(&raa);
    loop->setBreakCondition(&bc);
    loop->start();
    ASSERT_TRUE(raa.callCount == 2);
    ASSERT_TRUE(sock.getUnreadSize() >= 200);
    ASSERT_TRUE(raa.sf != NULL);
    raa.sf->wait();
    delete raa.sf;
}


class CloseReadAction : public IOActions {
public:
    CloseReadAction() { wasCalled = false; }
    virtual ~CloseReadAction() {}
    void onRead(ESocket *s, size_t) {
        EXPECT_FALSE(wasCalled);
        wasCalled = true;
        lcbio_CTX *ctx = s->ctx;
        s->parent->stop();
        s->close();
        ASSERT_TRUE(ctx->procs.cb_read == NULL);
    }
    void onError(ESocket *) {}
    bool wasCalled;
};

class CRABreakCondition : public BreakCondition {
public:
    CloseReadAction *cra;
    CRABreakCondition(CloseReadAction *action) { cra = action; }
protected:
    bool shouldBreakImpl() { return cra->wasCalled; }
};

TEST_F(SockReentrantTest, testCloseOnRead)
{
    ESocket sock;
    loop->connect(&sock);
    SendFuture sf(string(100, '#'));
    sock.conn->setSend(&sf);
    sock.reqrd(1);
    sock.schedule();
    CloseReadAction cra;
    CRABreakCondition bc(&cra);
    sock.setActions(&cra);
    loop->setBreakCondition(&bc);
    loop->start();
    sf.wait();
    ASSERT_TRUE(cra.wasCalled);
}

class CloseWriteAction : public IOActions {
public:
    bool wasCalled;
    CloseWriteAction() { wasCalled = false; }
    virtual ~CloseWriteAction() { }
    void onRead(ESocket *s, size_t) {
        EXPECT_FALSE(wasCalled);
        wasCalled = true;
        for (int ii = 0; ii < 100; ii++) {
            s->put("Hello!");
            s->schedule();
        }
        s->close();
        s->parent->stop();
    }
    void onError(ESocket *){}
};
TEST_F(SockReentrantTest, testCloseOnWrite)
{
    ESocket sock;
    loop->connect(&sock);
    SendFuture sf(string(100, '#'));
    sock.conn->setSend(&sf);
    sock.reqrd(1);
    sock.schedule();
    CloseWriteAction cwa;
    sock.setActions(&cwa);

    RecvFuture rf(1);
    FutureBreakCondition fbc(&rf);
    loop->setBreakCondition(&fbc);
    loop->start();
    sf.wait();
    ASSERT_TRUE(cwa.wasCalled);
}
