//
// 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 MyMyDHTDataStorage.cc
 * @author Ingmar Baumgart, Dilum Bandara
 * @version 0.11
 * @date 07/18/2010
 */

#include <omnetpp.h>
#include <hashWatch.h>
#include "MyDHTDataStorage.h"

Define_Module(MyDHTDataStorage);	//Register module

using namespace std;


std::ostream& operator<<(std::ostream& os, const DhtDataEntry entry)
{
    os << " Value: " << entry.value
       << " Kind: " << entry.kind
       << " ID: " << entry.id
       << " Endtime: " << entry.ttlMessage->getArrivalTime()
       << " Responsible: " << entry.responsible
       << " SourceNode: " << entry.sourceNode;

	if (entry.siblingVote.size()) 
	{
        os << " siblingVote:";

        for (SiblingVoteMap::const_iterator it = entry.siblingVote.begin();
             it != entry.siblingVote.end(); it++) 
		{
            os << " " << it->first << " (" << it->second.size() << ")";
        }
    }

    return os;
}


void MyDHTDataStorage::initialize(int stage)
{
    if (stage != MIN_STAGE_APP)
        return;

    WATCH_MULTIMAP(dataMap);
}


void MyDHTDataStorage::handleMessage(cMessage* msg)
{
    error("MyDHTDataStorage:: This module doesn't handle messages!");
}


void MyDHTDataStorage::clear()
{
    map<OverlayKey, DhtDataEntry>::iterator iter;

    for( iter = dataMap.begin(); iter != dataMap.end(); iter++ )
	{
		if(iter->second.ttlMessage != NULL)
	        cancelAndDelete(iter->second.ttlMessage);	//Cancel & delete expiration timers
	}
    dataMap.clear();	//Remove entire data store
}


uint32_t MyDHTDataStorage::getSize()
{
    return dataMap.size();
}


DhtDataEntry* MyDHTDataStorage::getDataEntry(const OverlayKey& key,
                                           uint32_t kind, uint32_t id)
{
	//Iterator pair to indicate start & end of enteries matching given key
    pair<DhtDataMap::iterator, DhtDataMap::iterator> pos =
        dataMap.equal_range(key);

    while (pos.first != pos.second) //From bigning to end of range
	{
        if ((pos.first->second.kind == kind) && (pos.first->second.id == id)) 
            return &pos.first->second;	//Return pointer to first matching entry
        ++pos.first;
    }

    return NULL;
}


DhtDataVector* MyDHTDataStorage::getDataVector(const OverlayKey& key,
                                             uint32_t kind, uint32_t id)
{
    DhtDataVector* vect = new DhtDataVector();
    DhtDataEntry entry;

	//Iterator pair to indicate start & end of enteries matching given key
    pair<DhtDataMap::iterator, DhtDataMap::iterator> pos = dataMap.equal_range(key);

    while (pos.first != pos.second) 
	{
        entry = pos.first->second;
        vect->push_back(make_pair(key, entry));	//Append to vector
        ++pos.first;
    }

    return vect;	//Return pointer with all entries that match given key
}


const NodeHandle& MyDHTDataStorage::getSourceNode(const OverlayKey& key,
                                                uint32_t kind, uint32_t id)
{
    DhtDataEntry* entry = getDataEntry(key, kind, id);	//get pointer

    if (entry == NULL)
        return NodeHandle::UNSPECIFIED_NODE;
    else
        return entry->sourceNode;	//Extract node ID
}


const bool MyDHTDataStorage::isModifiable(const OverlayKey& key,
                                        uint32_t kind, uint32_t id)
{
    DhtDataEntry* entry = getDataEntry(key, kind, id);	//get pointer

    if (entry == NULL)
        return true;
    else
        return entry->is_modifiable;	//Extract parameter
}


DhtDataEntry* MyDHTDataStorage::addData(const OverlayKey& key, uint32_t kind,
                                      	uint32_t id, BinaryValue value, 
										cMessage* ttlMessage, bool is_modifiable, 
										NodeHandle sourceNode, bool responsible)
{
    if ((kind == 0) || (id == 0)) 
	{
        throw cRuntimeError("MyDHTDataStorage::addData(): "
                            "Not allowed to add data with kind = 0 or id = 0!");
    }
    DhtDataEntry entry;
    entry.kind = kind;
    entry.id = id;
    entry.value = value;
    entry.ttlMessage = ttlMessage;
    entry.sourceNode = sourceNode;
    entry.is_modifiable = is_modifiable;
    entry.responsible = responsible;

	//Iterator pair to indicate start & end of enteries matching given key
    pair<DhtDataMap::iterator, DhtDataMap::iterator> pos = dataMap.equal_range(key);

    // insert new record in sorted multimap (order: key, kind, id)
    while ((pos.first != pos.second) && (pos.first->second.kind < kind))
        ++pos.first;

	//When kind is same sort based on id
    while ((pos.first != pos.second) && (pos.first->second.kind == kind)
            && (pos.first->second.id < id)) 
        ++pos.first;

    return &(dataMap.insert(pos.first, make_pair(key, entry))->second);
}


void MyDHTDataStorage::removeData(const OverlayKey& key, uint32_t kind,
                                uint32_t id)
{
	//Iterator pair to indicate start & end of enteries matching given key
    pair<DhtDataMap::iterator, DhtDataMap::iterator> pos = dataMap.equal_range(key);

    while (pos.first != pos.second) 
	{
        if (((kind == 0) || (pos.first->second.kind == kind)) &&
                ((id == 0) || (pos.first->second.id == id))) 
		{
			if(pos.first->second.ttlMessage != NULL)
	            cancelAndDelete(pos.first->second.ttlMessage); //Remove associated timer
            dataMap.erase(pos.first++);	//Remove entry then increment it to point to next entry
        } 
		else
            ++pos.first;
    }
}


DhtDumpVector* MyDHTDataStorage::dumpDht(const OverlayKey& key, uint32_t kind,
                                       uint32_t id)
{
    DhtDumpVector* vect = new DhtDumpVector();
    DhtDumpEntry entry;

    DhtDataMap::iterator iter, end;

    if (key.isUnspecified()) //If key not given dump entire table
	{
        iter = dataMap.begin();
        end = dataMap.end();
    } 
	else 					//Else dump only entries for given key, kind & ID
	{
        iter = dataMap.lower_bound(key);
        end = dataMap.upper_bound(key);
    }

    for (; iter != end; iter++) 
	{
        if (((kind == 0) || (iter->second.kind == kind)) &&
                ((id == 0) || (iter->second.id == id))) //Append matching ones
		{
            entry.setKey(iter->first);
            entry.setKind(iter->second.kind);
            entry.setId(iter->second.id);
            entry.setValue(iter->second.value);
            if(iter->second.ttlMessage != NULL)
			{
				entry.setTtl((int)SIMTIME_DBL(
                        iter->second.ttlMessage->getArrivalTime() - simTime()));
			}
            entry.setOwnerNode(iter->second.sourceNode);
            entry.setIs_modifiable(iter->second.is_modifiable);
            entry.setResponsible(iter->second.responsible);
            vect->push_back(entry);
        }
    }

    return vect;
}


int MyDHTDataStorage::deleteCacheWithPriority(ProfitMap* profitMap, int noShort)
{
	int noDeleted = 0;
	//map<int, CacheEntry>::iterator itera;
	map<double, CacheEntry>::iterator itera;
	multimap<OverlayKey, DhtDataEntry>::iterator iterb;

	/*
	std::cout << "Before: << dataMap.size() << endl;
	for (iterb = dataMap.begin(); iterb != dataMap.end(); iterb++) //Check each entry
			std::cout << "Key " << iterb->first << endl;
	*/

	for(itera = profitMap->begin(); itera != profitMap->end(); itera++)
	{
		for (iterb = dataMap.begin(); iterb != dataMap.end(); iterb++) //Check each entry
		{
			//Remove only cached entries that are not in dumpVector
			if((itera->second.key != iterb->first) && !iterb->second.responsible 
					&& !iterb->first.isUnspecified())
			{
				if(iterb->second.ttlMessage != NULL)
					cancelAndDelete(iterb->second.ttlMessage); //Remove associated timer
            	dataMap.erase(iterb--);	//Remove entry then decrement it to point to next entry
				noDeleted++;
				if (noDeleted == noShort)	//Deleted enough
					return noDeleted;
			}
        }
	}

	//Still more keys to be deleted
	for(itera = profitMap->begin(); itera != profitMap->end(); itera++)
	{
		for (iterb = dataMap.begin(); iterb != dataMap.end(); iterb++) //Check each entry
		{
			//Remove only cached entries that are in Lookup vector 
			//First remove keys with low profit which are it the begining of vector
			if((itera->second.key == iterb->first) && !iterb->second.responsible 
									&& !iterb->first.isUnspecified())
			{
				if(iterb->second.ttlMessage != NULL)
					cancelAndDelete(iterb->second.ttlMessage); //Remove associated timer
            	dataMap.erase(iterb--);	//Remove entry then decrement it to point to next entry
				noDeleted++;
				if (noDeleted == noShort)	//Deleted enough
					return noDeleted;
			}
		}
	}

	/*
		std::cout << "After: " << dataMap.size() << endl;
		for (iterb = dataMap.begin(); iterb != dataMap.end(); iterb++) //Check each entry
			std::cout << "Key " << iterb->first << endl;
	*/

	return noDeleted;
}


const DhtDataMap::iterator MyDHTDataStorage::begin()
{
    return dataMap.begin();
}


const DhtDataMap::iterator MyDHTDataStorage::end()
{
    return dataMap.end();
}
