/*
 * Copyright (C) 2009 - 2010 Funambol, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE
 * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 *
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 *
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 *
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */

#include <boost/test/unit_test.hpp>

#include "executionqueue/CommandsQueue.h"
#include "executionqueue/ICommand.h"

#include <Event.h>

#ifdef PLATFORM_WINDOWS
void sleep(int sec)
{
	::Sleep(sec * 1000);
}
#endif

using namespace NS_DM_Client;
using namespace NS_DM_Client::NS_ExecutionQueue;
using namespace NS_DM_Client::NS_Common;

static NS_Logging::Logger& logger = NS_Logging::GetLogger("ExecutionQueue");

class CommandMock : public ICommand
{
public:
        CommandMock()
        : executeResult(false)
        {
			LOG_DEBUG_(logger, "(%p) created. ", this);
		}

        volatile bool executeResult;
		virtual bool Execute()
		{
			LOG_DEBUG_(logger, "CommandMock::Execute (%p) is running. ", this);
		    return executeResult;
		}

        virtual bool Visit(ICommandDispatcher& cmdDispatcher)
        {
            return true;
        }
};


class WaitingCommandMock : public CommandMock
{
public:
    WaitingCommandMock(EventEx& wait, EventEx& started)
    : m_waitEvent(wait),
    m_cmdStartedEvent(started)
    {
		LOG_DEBUG_(logger, "(%p) created. ", this);
	}

    virtual bool Execute()
    {
    	m_cmdStartedEvent.signal();
		LOG_DEBUG_(logger, "WaitingCommandMock::Execute - Stopped. ");
        m_waitEvent.wait();
		LOG_DEBUG_(logger, "WaitingCommandMock::Execute - Resumed. ");
        return executeResult;
    }

private:
    EventEx& m_waitEvent;
    EventEx& m_cmdStartedEvent;
};

class DeleteCommandCheckMock : public CommandMock
{
public:
        volatile bool executeResult;
		bool& m_deleted;
		bool& m_executed;
		EventEx& m_event;

        DeleteCommandCheckMock(bool& deleted, bool& executed, EventEx& e)
        : executeResult(false),
        m_deleted(deleted),
        m_executed(executed),
        m_event(e)
        {
			LOG_DEBUG_(logger, "(%p) created. ", this);
			m_deleted = false;
			m_executed = false;
		}

		~DeleteCommandCheckMock()
		{
			m_deleted = true;
		}

		virtual bool Execute()
		{
			m_executed = true;
			LOG_DEBUG_(logger, "DeleteCommandCheckMock::Execute (%p) is running. ", this);
			m_event.signal();
		    return executeResult;
		}

        virtual bool Visit(ICommandDispatcher& cmdDispatcher)
        {
            return true;
        }
};


BOOST_AUTO_TEST_CASE(AddCmdToEmptyQ)
{
	LOG_DEBUG_(logger, "AddCmdToEmptyQ test is running. ");

    CommandsQueue q(logger);

    BOOST_CHECK(q.Init());

    CommandMock* cmd = new CommandMock;
    cmd->executeResult = true;

    BOOST_CHECK(q.Add(*cmd));

    BOOST_CHECK(q.StopCommandExecution());

}


BOOST_AUTO_TEST_CASE(AddCmdToNonEmptyQ_WhileOtherCommandRunning)
{
	LOG_DEBUG_(logger, "AddCmdToNonEmptyQ_WhileOtherCommandRunning test is running. ");

    CommandsQueue q(logger);

    BOOST_CHECK(q.Init());

    EventEx resumeEvent;
    EventEx cmdStartedEvent;
    WaitingCommandMock* wcmd = new WaitingCommandMock(resumeEvent, cmdStartedEvent);
    wcmd->executeResult = true;

    // command will be stopped on event
    BOOST_CHECK(q.Add(*wcmd));

    // add new command
    CommandMock* cmd = new CommandMock;
    cmd->executeResult = true;
    BOOST_CHECK(q.Add(*cmd));

	cmdStartedEvent.wait();

    // resume 1st command
    resumeEvent.signal();

    BOOST_CHECK(q.StopCommandExecution());

}


BOOST_AUTO_TEST_CASE(Executed_command_with_success_result_is_deleted)
{
	LOG_DEBUG_(logger, "Executed_command_with_success_result_is_deleted test is running. ");

    CommandsQueue q(logger);
    BOOST_CHECK(q.Init());

	bool deleted = false;
	bool executed = true;
    EventEx cmdExecutedEvent;
    DeleteCommandCheckMock* cmd = new DeleteCommandCheckMock(deleted, executed, cmdExecutedEvent);
    cmd->executeResult = true;

    BOOST_CHECK(q.Add(*cmd));

    cmdExecutedEvent.wait();
	sleep(1);

	BOOST_CHECK(executed);
	BOOST_CHECK(deleted);

    BOOST_CHECK(q.StopCommandExecution());

}


BOOST_AUTO_TEST_CASE(Executed_command_with_failure_result_is_deleted)
{
	LOG_DEBUG_(logger, "Executed_command_with_failure_result_is_deleted test is running. ");

    CommandsQueue q(logger);
    BOOST_CHECK(q.Init());

	bool deleted = false;
	bool executed = true;
	EventEx cmdExecutedEvent;
    DeleteCommandCheckMock* cmd = new DeleteCommandCheckMock(deleted, executed, cmdExecutedEvent);
    cmd->executeResult = false;

    BOOST_CHECK(q.Add(*cmd));

    cmdExecutedEvent.wait();
	sleep(1);

	BOOST_CHECK(executed);
	BOOST_CHECK(deleted);

    BOOST_CHECK(q.StopCommandExecution());

}


BOOST_AUTO_TEST_CASE(NotExecuted_command_is_deleted)
{
	LOG_DEBUG_(logger, "NotExecuted_command_with_deleted test is running. ");

    CommandsQueue* q = new CommandsQueue(logger);
    BOOST_CHECK(q);
    BOOST_CHECK(q->Init());

    EventEx resumeEvent;
    EventEx cmdStartedEvent;
    WaitingCommandMock* wcmd = new WaitingCommandMock(resumeEvent, cmdStartedEvent);
    wcmd->executeResult = true;

    BOOST_CHECK(q->Add(*wcmd));// command will be stopped on event

    cmdStartedEvent.wait(); // waiting command started execution and stopped

	bool deleted = false;
	bool executed = true;
    EventEx cmdExecutedEvent;
    DeleteCommandCheckMock* cmd = new DeleteCommandCheckMock(deleted, executed, cmdExecutedEvent);
    cmd->executeResult = true;

    BOOST_CHECK(q->Add(*cmd));

    BOOST_CHECK(!q->StopCommandExecution());

    resumeEvent.signal(); // allow to start waiting command

    sleep(1);

	delete q;

	BOOST_CHECK(!executed);
	BOOST_CHECK(deleted);

}


BOOST_AUTO_TEST_CASE(Multiple_cmds_added_while_other_is_running_Executed)
{
	LOG_DEBUG_(logger, "Multiple_cmds_added_while_other_is_running_Executed test is running. ");

    CommandsQueue* q = new CommandsQueue(logger);
    BOOST_CHECK(q);
    BOOST_CHECK(q->Init());

    EventEx resumeEvent;
    EventEx cmdStartedEvent;
    WaitingCommandMock* wcmd = new WaitingCommandMock(resumeEvent, cmdStartedEvent);
    wcmd->executeResult = true;

    BOOST_CHECK(q->Add(*wcmd));// command will be stopped on event

    cmdStartedEvent.wait(); // waiting command started execution and stopped

	const int maxCommands = 5;
	struct CMD_EXEC_INFO
	{
		bool deleted;
		bool executed;
		DeleteCommandCheckMock*	cmd;

		CMD_EXEC_INFO() :
		deleted(false),
		executed(false),
		cmd(0)
		{}
	} cmds[maxCommands];

    EventEx cmdExecutedEvent;

	for (int i = 0; i < maxCommands; ++i)
	{
		cmds[i].cmd = new DeleteCommandCheckMock(cmds[i].deleted, cmds[i].executed, cmdExecutedEvent);
		BOOST_CHECK(cmds[i].cmd);
		cmds[i].cmd->executeResult = true;

		bool cmdAddResult = q->Add(*cmds[i].cmd);
		BOOST_CHECK(cmdAddResult);
		if (!cmdAddResult)
		{
			LOG_DEBUG_(logger, "failed to add command with index=%d. ", i);
		}
	}

    resumeEvent.signal(); // allow to start waiting command

    sleep(3);

    BOOST_CHECK(q->StopCommandExecution());

	delete q;

	for (int i = 0; i < maxCommands; ++i)
	{
		BOOST_CHECK(cmds[i].executed);
		BOOST_CHECK(cmds[i].deleted);

		if (!cmds[i].executed || !cmds[i].deleted)
		{
			LOG_DEBUG_(logger, "failed on command with index=%d. ", i);
		}
	}

}
