/*
* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:  Unit tests for send and sendReceive functionality
*
*/

#include <pthread.h>
#include <unistd.h>
#include "TestHarness.h"
#include "monitor.h"
#include "logger.h"

#include "comms.h"
#include "commsclientendpoint.h"
#include "commsendpoint.h"
#include "echoserver.h"

using namespace java::comms;
using namespace std;
using java::util::Monitor;

TEST_GROUP(SendReceive)
{
    EchoServer server;
    CommsClientEndpoint client;

    TEST_SETUP()
    {
        server.start(IPC_ADDRESS_COMMS_MODULE_TEST);
        client.connect(IPC_ADDRESS_COMMS_MODULE_TEST);
    }

    TEST_TEARDOWN()
    {
        client.disconnect();
        server.stop();
    }
};

/**
 * Test message sending
 * 1. Sending empty message
 * 2. Check contents of message (empty message)
 * 3. Message payload check
 */

TEST(SendReceive, normal)
{
#ifdef __SYMBIAN32__
    EXPECT_N_LEAKS(1);
#else
    EXPECT_N_LEAKS(2);
#endif
    CommsMessage msg;
    msg.setModuleId(MODULE_ID_NO_REPLY);

    // 1. Sending empty message
    CHECK(client.send(msg) == 0);

    // 2. Check contents of message (empty message)
    CommsMessage msg2;
    msg2.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg;

    CHECK(client.sendReceive(msg2, receivedMsg, 1) == 0);
    CHECK(msg2.toString().compare(receivedMsg.toString()) == 0);
    CHECK(msg2.getModuleId() == receivedMsg.getModuleId());

    // 3. Message payload check
    CommsMessage msg3;
    msg3.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg2;

    int in = 0xCAFEBABE;
    int zero = 0;
    int one = 1;
    int max = 0xFFFFFFFF;
    int neg = -12345;
    string ascii = "abcdefghijklmnopqrstyvwxyzABCDEFGHIJKLMNOPQRSTYVWXYZ0123456789";
    msg3 << ascii << in << zero << one << max << neg;

    CHECK(client.sendReceive(msg3, receivedMsg2, 1) == 0);

    string s;
    int i;
    receivedMsg2 >> s;
    CHECK(s.compare(ascii) == 0);
    receivedMsg2 >> i;
    CHECK(i == in);
    receivedMsg2 >> i;
    CHECK(i == zero);
    receivedMsg2 >> i;
    CHECK(i == one);
    receivedMsg2 >> i;
    CHECK(i == max);
    receivedMsg2 >> i;
    CHECK(i == neg);

}

/**
 * Test message sending with wait for ever flag
 * 1. Check contents of message (empty message)
 * 2. Message payload check
 */

TEST(SendReceive, normalwaitforever)
{
#ifdef __SYMBIAN32__
    EXPECT_N_LEAKS(1);
#else
    EXPECT_N_LEAKS(2);
#endif
    // 1. Check contents of message (empty message)
    CommsMessage msg2;
    msg2.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg;

    CHECK(client.sendReceive(msg2, receivedMsg, WAIT_FOR_EVER) == 0);
    CHECK(msg2.toString().compare(receivedMsg.toString()) == 0);
    CHECK(msg2.getModuleId() == receivedMsg.getModuleId());

    // 2. Message payload check
    CommsMessage msg3;
    msg3.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg2;

    int in = 0xCAFEBABE;
    int zero = 0;
    int one = 1;
    int max = 0xFFFFFFFF;
    int neg = -12345;
    string ascii = "abcdefghijklmnopqrstyvwxyzABCDEFGHIJKLMNOPQRSTYVWXYZ0123456789";
    msg3 << ascii << in << zero << one << max << neg;

    CHECK(client.sendReceive(msg3, receivedMsg2, WAIT_FOR_EVER) == 0);

    string s;
    int i;
    receivedMsg2 >> s;
    CHECK(s.compare(ascii) == 0);
    receivedMsg2 >> i;
    CHECK(i == in);
    receivedMsg2 >> i;
    CHECK(i == zero);
    receivedMsg2 >> i;
    CHECK(i == one);
    receivedMsg2 >> i;
    CHECK(i == max);
    receivedMsg2 >> i;
    CHECK(i == neg);

}

/**
 * Test message sending with big timeout values
 * 1. OpenC timeout limit
 * 2. 1h timeout
 * 3. 24h timeout
 */

TEST(SendReceive, bigtimeout)
{
    EXPECT_N_LEAKS(1);
    CommsMessage msg;
    msg.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg;

    // 1. OpenC timeout limit
    CHECK(client.sendReceive(msg, receivedMsg, 2148) == 0);
    CHECK(msg.getModuleId() == receivedMsg.getModuleId());

    // 2. 1h timeout
    CHECK(client.sendReceive(msg, receivedMsg, 3600) == 0);
    CHECK(msg.getModuleId() == receivedMsg.getModuleId());

    // 3. 24h timeout
    CHECK(client.sendReceive(msg, receivedMsg, 86400) == 0);
    CHECK(msg.getModuleId() == receivedMsg.getModuleId());
}

/**
 * Test message sending
 * 1. Sending big message
 */

TEST(SendReceive, bigmessage)
{
#ifdef __SYMBIAN32__
    EXPECT_N_LEAKS(1);
#else
    EXPECT_N_LEAKS(2);
#endif
    // 1. Sending big message
    int SIZE = 60000; // 60k
    char* array = new char[SIZE];
    for (int i = 0; i < SIZE; i++)
    {
        array[i] = i;
    }
    string input(array, SIZE);
    delete[] array;

    CommsMessage msg;
    msg.setModuleId(MODULE_ID_ECHO);
    msg << input;

    CommsMessage receivedMsg;
    CHECK(client.sendReceive(msg, receivedMsg, 1) == 0);

    string s;
    receivedMsg >> s;
    CHECK(s.compare(input) == 0);
}


/**
 * Test reply from listener
 * 1. sendReceive and reply from listener
 */

TEST(SendReceive, listener)
{
    // 1. sendReceive and reply from listener
    int MODULEID = 666;
    CommsEndpoint* comms = server.getComms();
    EchoListener listener(comms);

    CHECK(!comms->registerListener(MODULEID, &listener));

    CommsMessage msg;
    msg.setModuleId(MODULEID);
    msg << "hello world";

    CommsMessage receivedMsg;
    CHECK(client.sendReceive(msg, receivedMsg, 1) == 0);

    string s;
    receivedMsg >> s;
    CHECK(s.compare("hello world") == 0);

    CHECK(!comms->unregisterListener(MODULEID, &listener));
}


/**
 * Test sendReceive timeout
 * 1. sendReceive no timeout
 * 2. sendReceive timeout
 */

TEST(SendReceive, timeouts)
{
    EXPECT_N_LEAKS(1);
    // 1. sendReceive no timeout
    CommsMessage msg;
    msg.setModuleId(MODULE_ID_SLEEP_1S);

    CommsMessage receivedMsg;
    CHECK(client.sendReceive(msg, receivedMsg, 2) == 0);

    // 2. sendReceive timeout
    msg.setModuleId(MODULE_ID_NO_REPLY);
    CHECK(client.sendReceive(msg, receivedMsg, 1) != 0);
}


/**
 * Test send error cases
 * 1. send when not connected
 * 2. sendReceive when not connected
 * 3. send to non-existing endpoint
 * 4. invalid timeout values
 */

TEST(SendReceive, errors)
{
    EXPECT_N_LEAKS(1);
    CommsClientEndpoint con;

    // 1. send when not connected
    CommsMessage msg;
    msg.setModuleId(MODULE_ID_ECHO);
    CHECK(!con.connect(IPC_ADDRESS_COMMS_MODULE_TEST));
    CHECK(!con.send(msg));
    CHECK(!con.disconnect());
    CHECK(con.send(msg)!=0);

    // 2. sendReceive when not connected
    CommsMessage receivedMsg;
    CHECK(!con.connect(IPC_ADDRESS_COMMS_MODULE_TEST));
    CHECK(!con.sendReceive(msg, receivedMsg, 2));
    CHECK(!con.sendReceive(msg, receivedMsg, WAIT_FOR_EVER));
    CHECK(!con.disconnect());
    CHECK(con.sendReceive(msg, receivedMsg, 2)!=0);

    // 3. send to non-existing endpoint
    CommsMessage dummy;
    dummy.setReceiver(666);
    CHECK(server.getComms()->send(msg) != 0);

    // 4. invalid timeout values
    msg.setModuleId(MODULE_ID_ECHO);
    CHECK(!con.connect(IPC_ADDRESS_COMMS_MODULE_TEST));
    CHECK(con.sendReceive(msg, receivedMsg, 0)!=0);
    CHECK(con.sendReceive(msg, receivedMsg, -2)!=0);
    CHECK(!con.disconnect());
}

/**
 * Test multithread send (client)
 * 1. send in multiple threads
 * 2. sendReceive in multiple threads
 */

void doSends(CommsClientEndpoint* aComms)
{
    CommsMessage msg;
    msg.setModuleId(MODULE_ID_ECHO);
    CommsMessage receivedMsg;

    for (int i = 0; i < 5; i++)
    {
        CHECK(aComms->send(msg) == 0);
        CHECK(aComms->sendReceive(msg, receivedMsg, 1) == 0);
        CHECK(aComms->sendReceive(msg, receivedMsg, WAIT_FOR_EVER) == 0);
        usleep(300000); //0.3s
    }
}

void* clientSendThread(void* aComms)
{
    CommsClientEndpoint* comms = reinterpret_cast<CommsClientEndpoint*>(aComms);
    doSends(comms);
    return 0;
}

TEST(SendReceive, multiThreadClient)
{
    EXPECT_N_LEAKS(1);
    CommsClientEndpoint con;
    CHECK(!con.connect(IPC_ADDRESS_COMMS_MODULE_TEST));

    const int NUM_THREADS = 5;
    pthread_t thread[NUM_THREADS];

    for (int i=0; i<NUM_THREADS; i++)
    {
        pthread_create(&thread[i], 0, &clientSendThread, &con);
    }

    for (int i=0; i<NUM_THREADS; i++)
    {
        pthread_join(thread[i], 0);
    }

    CHECK(!con.disconnect());
}


class ReplyListener : public CommsListener
{
public:
    ReplyListener(CommsEndpoint& aComms, Monitor& aMonitor)
            : mComms(aComms), mMonitor(aMonitor), mReplyCount(30) {}
    virtual void processMessage(CommsMessage& aMessage)
    {
        CommsMessage reply;
        reply.replyTo(aMessage);
        //ELOG1(EJavaComms, "ReplyListener count=%d", mReplyCount);
        mComms.send(reply);

        if (--mReplyCount < 0)
        {
            mMonitor.notify();
        }
        //ELOG1(EJavaComms, "ReplyListener after send count=%d", mReplyCount);
    }
private:
    CommsEndpoint& mComms;
    Monitor& mMonitor;
    int mReplyCount;
};

TEST(SendReceive, sendsInProcessMessage)
{
    EXPECT_N_LEAKS(1);
    std::auto_ptr<Monitor> monitor(Monitor::createMonitor());

    CommsClientEndpoint con;
    ReplyListener listener(con, *monitor);

    CHECK(!con.connect(IPC_ADDRESS_COMMS_MODULE_TEST));
    CHECK(!con.registerDefaultListener(&listener));

    CommsMessage msg;
    CHECK(!con.send(msg));

    monitor->wait();
    CHECK(!con.disconnect());
}
