//
// Copyright (C) 2007 Institut fuer Telematik, Universitaet Karlsruhe (TH)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// 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 General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//

/**
 * @file MyDHTTestApp.cc
 * @author Ingmar Baumgart, Dilum Bandara
 * @version 0.1.1
 * @date 07/31/2010
 */

#include <IPAddressResolver.h>
#include <GlobalStatistics.h>
#include <GlobalNodeListAccess.h>
#include <GlobalStatisticsAccess.h>
#include <UnderlayConfiguratorAccess.h>
#include <RpcMacros.h>
#include "CommonMessages_m.h"
#include <MyGlobalKQ.h>
#include "MyDHTTestApp.h"
#include "MyDHT.h"

Define_Module(MyDHTTestApp);

using namespace std;


MyDHTTestApp::MyDHTTestApp()
{
    dhttestput_timer = NULL;
    dhttestget_timer = NULL;
}


MyDHTTestApp::~MyDHTTestApp()
{
	cancelAndDelete(dhttestput_timer);
	cancelAndDelete(dhttestget_timer);
}


void MyDHTTestApp::initializeApp(int stage)
{
    if (stage != MIN_STAGE_APP)	//If this model is not being created
        return;

    // Fetch parameters
    activeNetwInitPhase = par("activeNetwInitPhase");
	delayToStart = par("delayToStart");
    ttl = par("testTtl");
    mean = par("testInterval");
    deviation = mean / 5;

    globalNodeList = GlobalNodeListAccess().get();
    underlayConfigurator = UnderlayConfiguratorAccess().get();
    globalStatistics = GlobalStatisticsAccess().get();

	//Assign group ID. If maxGroupId = -1 no groups in system
	maxGroupId = par("maxGroupId");
	if(maxGroupId == 1)
		groupId = -1;
	else
	{
		int gID = intuniform(1, 25);
		if (gID <= 3)
			groupId = gID;
		else if (gID <= 13)
		{
			if(gID >= 4 && gID <= 5)
				groupId = 4;
			else if(gID >= 6 && gID <= 7)
				groupId = 5;
			else if(gID >= 8 && gID <= 9)
				groupId = 6;
			else if(gID >= 10 && gID <= 11)
				groupId = 7;
			else if(gID >= 12 && gID <= 13)
				groupId = 8;
		}
		else if(gID <= 17)
			groupId = 9;
		else if(gID <=25)
			groupId = 10;
	}

	overlay->setGroupId(groupId);	//Update overlay groupID
	//Update application (tier1) groupID
	cModule* thisOverlayTerminal = check_and_cast<cModule*>(getParentModule()->getParentModule());
	cCompoundModule* appModule = check_and_cast<cCompoundModule*> (thisOverlayTerminal->getSubmodule("tier1"));
	MyDHT* app = check_and_cast<MyDHT*> (appModule->getSubmodule("myDht"));
	app->setGroupId(groupId);

    myGlobalKQ = dynamic_cast<MyGlobalKQ*>(simulation.getModuleByPath(
            "globalObserver.globalFunctions[0].function")); //Defined in omnet.ini
    if (myGlobalKQ == NULL) 
	{
        throw cRuntimeError("MyDHTTestApp::initializeApp(): "
                                "MyGlobalKQ module not found!");
    }

    // statistics
    numGetSent = 0;
    numGetError = 0;
    numGetSuccess = 0;
    numPutSent = 0;
    numPutError = 0;
    numPutSuccess = 0;

    //initRpcs();
    WATCH(numGetSent);
    WATCH(numGetError);
    WATCH(numGetSuccess);
    WATCH(numPutSent);
    WATCH(numPutError);
    WATCH(numPutSuccess);

    nodeIsLeavingSoon = false;

    // initiate put & get message transmission
    dhttestput_timer = new cMessage("dhttest_put_timer");
    dhttestget_timer = new cMessage("dhttest_get_timer");

	// Schedule only a PUT message after delatToStart with slight variation
	// GET is used only when all keys are stored
    if (mean > 0) 
		scheduleAt((delayToStart + truncnormal(mean, deviation)), dhttestput_timer);
		//scheduleAt((delayToStart + exponential(mean)), dhttestput_timer);
}


void MyDHTTestApp::handleTimerEvent(cMessage* msg)
{
    if (msg->isName("dhttest_put_timer")) //PUT timer message
	{
		if(simTime() < delayToStart)		//Not suppose to store keys
		{
        	scheduleAt(simTime() + mean, msg);	// schedule next timer event
			return;
		}
			
        // do nothing if network is still in initialization phase, I'm ready to leave
		// or overlay not ready (READY = 4)
        if (((!activeNetwInitPhase) && (underlayConfigurator->isInInitPhase()))
                || underlayConfigurator->isSimulationEndingSoon()
                || nodeIsLeavingSoon || overlay->getState() != 4) 
		{
        	scheduleAt(simTime() + truncnormal(mean, deviation), msg);	// schedule next timer event
            return;
		}

        const OverlayKey& destKey = myGlobalKQ->getKey();	//Get a key from MyGlobalKQ
		if (destKey.isUnspecified())	//If no more keys
		{
			//Start sending queries. Schedule GET (query) timer
			scheduleAt(simTime() + truncnormal(mean, deviation), dhttestget_timer);
			//scheduleAt(simTime() + exponential(mean), dhttestget_timer);
			//DHTdumpCall* dmpCallMsg = new DHTdumpCall();
	    	//sendInternalRpcCall(TIER1_COMP, dmpCallMsg, NULL);
			return;
		}

	    DHTputCAPICall* dhtPutMsg = new DHTputCAPICall();
    	dhtPutMsg->setKey(destKey);
	    dhtPutMsg->setValue(generateRandomValue());
	    dhtPutMsg->setTtl(ttl);
	    dhtPutMsg->setIsModifiable(true);
	    RECORD_STATS(numPutSent++);
		//Call PUT function in Application layer
	    sendInternalRpcCall(TIER1_COMP, dhtPutMsg,
            new DHTStatsContext(globalStatistics->isMeasuring(),
                                simTime(), destKey, dhtPutMsg->getValue()));
        scheduleAt(simTime() + truncnormal(mean, deviation), msg);	// schedule next timer event
    } 
	else if (msg->isName("dhttest_get_timer")) //GET timer message
	{
        // do nothing if network is still in initialization phase or I'm leaving
		// or overlay not ready (READY = 4)
        if (((!activeNetwInitPhase) && (underlayConfigurator->isInInitPhase()))
                || underlayConfigurator->isSimulationEndingSoon()
                || nodeIsLeavingSoon || overlay->getState() != 4)
		{
			//Use poission distribution for GET messages
        	//scheduleAt(simTime() + truncnormal(mean, deviation), msg);
        	scheduleAt(simTime() + exponential(mean), msg);
			return;
		}

		//Get a key to query from MyGlobalKQ
        const OverlayKey& key = myGlobalKQ->getQuery(groupId);
        if (key.isUnspecified())	// If no more keys
			return;
	        
		DHTgetCAPICall* dhtGetMsg = new DHTgetCAPICall();
	    dhtGetMsg->setKey(key);
    	RECORD_STATS(numGetSent++);
		//Call get function in Application layer
    	sendInternalRpcCall(TIER1_COMP, dhtGetMsg,
        	new DHTStatsContext(globalStatistics->isMeasuring(), simTime(), key));

		//Schedule next timer. If group sizes are differnt update scaling factor
	    //scheduleAt(simTime() + truncnormal(mean, deviation), msg);
		if(groupId == 1)
	        scheduleAt(simTime() + exponential(mean), msg);
		else if(groupId == 2)
	        scheduleAt(simTime() + 1.06 * exponential(mean), msg);
		else if(groupId == 3)
	        scheduleAt(simTime() + 1.14 * exponential(mean), msg);
		else if(groupId == 4)
	        scheduleAt(simTime() + 1.18 * exponential(mean), msg);
		else if(groupId == 5)
	        scheduleAt(simTime() + 1.18 * exponential(mean), msg);
		else if(groupId == 6)
	        scheduleAt(simTime() + 1.18 * exponential(mean), msg);
		else if(groupId == 7)
	        scheduleAt(simTime() + 1.09 * exponential(mean), msg);
		else if(groupId == 8)
	        scheduleAt(simTime() + 1.13 * exponential(mean), msg);
		else if(groupId == 9)
	        scheduleAt(simTime() + 1.24 * exponential(mean), msg);
		else if(groupId == 10)
	        scheduleAt(simTime() + 1.28 * exponential(mean), msg);
    }
}


void MyDHTTestApp::handleRpcResponse(BaseResponseMessage* msg,
                                   const RpcState& state, simtime_t rtt)
{
    RPC_SWITCH_START(msg)
    RPC_ON_RESPONSE( DHTputCAPI ) 	//Response to PUT call
	{
        handlePutResponse(_DHTputCAPIResponse,
                          check_and_cast<DHTStatsContext*>(state.getContext()));
        EV << "[MyDHTTestApp::handleRpcResponse()]\n"
           << "    DHT Put RPC Response received: id=" << state.getId()
           << " msg=" << *_DHTputCAPIResponse << " rtt=" << rtt << endl;
        break;
    }
    RPC_ON_RESPONSE(DHTgetCAPI)		//Responce to GET call
    {
        handleGetResponse(_DHTgetCAPIResponse,
                          check_and_cast<DHTStatsContext*>(state.getContext()));
        EV << "[MyDHTTestApp::handleRpcResponse()]\n"
           << "    DHT Get RPC Response received: id=" << state.getId()
           << " msg=" << *_DHTgetCAPIResponse << " rtt=" << rtt << endl;
        break;
    }
    RPC_SWITCH_END()
}


void MyDHTTestApp::handlePutResponse(DHTputCAPIResponse* msg,
                                   DHTStatsContext* context)
{
    // don't count response, if request was not sent in measurement phase
    if (context->measurementPhase == false) 
	{
        delete context;
        return;
    }

    if (msg->getIsSuccess())	//Success 
	{
        RECORD_STATS(numPutSuccess++);
        RECORD_STATS(globalStatistics->addStdDev("MyDHTTestApp: PUT Latency (s)",
                               SIMTIME_DBL(simTime() - context->requestTime)));
    } 
	else 						//Failed
    {
		cout << "--- Fail To PUT " << context->key << " " << overlay->getThisNode().getKey() << endl;
	    RECORD_STATS(numPutError++);
        EV << "[MyDHTTestApp::handlePutResponse()]\n"
           << "    DHT Put RPC Response FAILED: Key=" << context->key << endl;
		endSimulation();
	}

    delete context;
}


void MyDHTTestApp::handleGetResponse(DHTgetCAPIResponse* msg,
                                   DHTStatsContext* context)
{
    // don't count response, if request was not sent in measurement phase
    if (context->measurementPhase == false) 
	{
        delete context;
        return;
    }

    //RECORD_STATS(globalStatistics->addStdDev("MyDHTTestApp: GET Latency (s)",
    //                           SIMTIME_DBL(simTime() - context->requestTime)));

    if (!(msg->getIsSuccess())) 	//If failed
	{
        EV << "MyDHTTestApp::handleGetResponse() \n"
		   << " DHT Get RPC Responce FAILED: Key = " << context->key << endl;
        RECORD_STATS(numGetError++);
        delete context;
        return;
    }
	else
	{
		delete context;
        RECORD_STATS(numGetSuccess++);
        return;
	}
}


BinaryValue MyDHTTestApp::generateRandomValue()
{
    char value[DHTTESTAPP_VALUE_LEN + 1];

    for (int i = 0; i < DHTTESTAPP_VALUE_LEN; i++)
        value[i] = intuniform(0, 25) + 'a';

    value[DHTTESTAPP_VALUE_LEN] = '\0';
    return BinaryValue(value);
}


void MyDHTTestApp::handleNodeLeaveNotification()
{
    nodeIsLeavingSoon = true;
}


void MyDHTTestApp::finishApp()
{
    simtime_t time = globalStatistics->calcMeasuredLifetime(creationTime);

    if (time >= GlobalStatistics::MIN_MEASURED) 
	{
        // record scalar data
		globalStatistics->recordOutVector("MyDHTTestApp: Group Id", groupId);
        globalStatistics->addStdDev("MyDHTTestApp: No Sent GET Msgs", numGetSent);
        globalStatistics->addStdDev("MyDHTTestApp: No Failed GET Msgs", numGetError);
        globalStatistics->addStdDev("MyDHTTestApp: No Successful GET Msgs", numGetSuccess);
        globalStatistics->addStdDev("MyDHTTestApp: No Sent PUT Msgs", numPutSent);
        globalStatistics->addStdDev("MyDHTTestApp: No Failed PUT Msgs", numPutError);
        globalStatistics->addStdDev("MyDHTTestApp: No Successful PUT Msgs", numPutSuccess);

        if ((numGetSuccess + numGetError) > 0) 
		{
            globalStatistics->addStdDev("MyDHTTestApp: GET Success Ratio",
                                        (double) numGetSuccess / (double) (numGetSuccess + numGetError));
        }
    }
}

