/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2011-2020 Couchbase, Inc.
 *
 *   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 "socktest.h"
using namespace LCBTest;
using std::string;
using std::vector;

class SockConnTest : public SockTest
{
};

TEST_F(SockConnTest, testBasic)
{
    ESocket sock;

    // We can connect
    loop->connect(&sock);
    ASSERT_FALSE(sock.sock == NULL);
    ASSERT_TRUE(sock.creq == NULL);
    ASSERT_EQ(1, sock.sock->refcount);

    // We can send data
    string sendStr("Hello World");
    RecvFuture rf(sendStr.size());
    FutureBreakCondition wbc(&rf);

    sock.conn->setRecv(&rf);
    sock.put(sendStr);
    sock.schedule();
    loop->setBreakCondition(&wbc);
    loop->start();
    rf.wait();
    ASSERT_TRUE(rf.isOk());
    ASSERT_EQ(rf.getString(), sendStr);

    //  We can receive data
    string recvStr("Goodbye World!");
    SendFuture sf(recvStr);
    ReadBreakCondition rbc(&sock, recvStr.size());
    sock.conn->setSend(&sf);
    sock.reqrd(recvStr.size());
    sock.schedule();
    loop->setBreakCondition(&rbc);
    loop->start();
    sf.wait();
    ASSERT_TRUE(sf.isOk());
    ASSERT_EQ(sock.getReceived(), recvStr);

    // Clean it all up
    sock.close();
}

static bool isRefused(lcbio_OSERR err)
{
    if (err == ECONNREFUSED || err == ECONNABORTED) {
        return true;
    }
#ifdef _WIN32
    if (err == WSAECONNREFUSED) {
        return true;
    }
#endif
    return false;
}
// Test a connect without an accept
TEST_F(SockConnTest, testRefused)
{
    ESocket sock;
    lcb_host_t host = {0};
    strcpy(host.host, "localhost");
    strcpy(host.port, "1");
    loop->connect(&sock, &host, 100000);
    ASSERT_TRUE(sock.sock == NULL);
    ASSERT_TRUE(isRefused(sock.syserr));
}

TEST_F(SockConnTest, testBadDomain)
{
    ESocket sock;
    lcb_host_t host = {0};
    strcpy(host.host, "domain-should-not-work.nonexist.com");
    strcpy(host.port, "123");
    loop->connect(&sock, &host, 1000);
    ASSERT_TRUE(sock.sock == NULL);
}

TEST_F(SockConnTest, testInvalidPort)
{
    ESocket sock;
    lcb_host_t host = {0};
    strcpy(host.host, "localhost");
    strcpy(host.port, "111111111");
    loop->connect(&sock, &host, 1000);
    ASSERT_TRUE(sock.sock == NULL);
}

TEST_F(SockTest, testEmptyHost)
{
    ESocket sock;
    lcb_host_t host = {0};
    host.host[0] = '\0';
    host.port[0] = '\0';
    loop->connect(&sock, &host, 1000);
    ASSERT_TRUE(sock.sock == NULL);
}

TEST_F(SockConnTest, testCancellation)
{
    ESocket sock;
    lcb_host_t host = {0};
    loop->populateHost(&host);
    sock.creq = lcbio_connect(loop->iot, loop->settings, &host, 100000, NULL, NULL);
    ASSERT_FALSE(sock.creq == NULL);
    lcb::io::ConnectionRequest::cancel(&sock.creq);

    NullBreakCondition nbc;
    loop->setBreakCondition(&nbc);
    loop->start();
}

extern "C" {
static void conncb_1(lcbio_SOCKET *sock, void *arg, lcb_STATUS err, lcbio_OSERR syserr)
{
    ESocket *es = (ESocket *)arg;
    es->creq = NULL;
    es->callCount++;
    es->ctx = NULL;
    es->lasterr = err;
    es->parent->stop();

    // unref is implicit
}
}
TEST_F(SockConnTest, testImmediateUnref)
{
    ESocket sock;
    lcb_host_t host = {0};
    sock.parent = loop;
    loop->populateHost(&host);
    sock.creq = lcbio_connect(loop->iot, loop->settings, &host, 1000000, conncb_1, &sock);
    loop->start();
    ASSERT_EQ(1, sock.callCount);
    ASSERT_TRUE(sock.sock == NULL);
}
