//this file is part of eMule
//Copyright (C)2002 Merkur ( merkur-@users.sourceforge.net / http://www.emule-project.net )
//
//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., 675 Mass Ave, Cambridge, MA 02139, USA.

#include "StdAfx.h"
#include "uploadqueue.h"
#include "packets.h"
#include "emule.h"
#include "knownfile.h"
#include "listensocket.h"
#include "ini2.h"
#include "math.h"
#include "Exceptions.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif


static uint32 counter, sec,statsave;
// -khaos--+++> Added iupdateconnstats...
static uint32 igraph, istats, iupdateconnstats;
// <-----khaos-

//TODO rewrite the whole networkcode, use overlapped sockets

CUploadQueue::CUploadQueue(CPreferences* in_prefs){
	app_prefs = in_prefs;
	h_timer = SetTimer(0,141,100,UploadTimer);
	if (!h_timer)
		theApp.emuledlg->AddDebugLogLine(false,GetResString(IDS_ERR_TIMERCREATEFAILED));
	estadatarate = 2000;
	datarate = 0;
	//dataratems = 0;
	datarateave = 0;
	bannedcount = 0;
	counter=0;
	successfullupcount = 0;
	failedupcount = 0;
	totaluploadtime = 0;
	m_nUpDataRateMSOverhead = 0;
	m_nUpDatarateOverhead = 0;
	m_nUpDataOverheadSourceExchange = 0;
	m_nUpDataOverheadFileRequest = 0;
	m_nUpDataOverheadOther = 0;
	m_nUpDataOverheadServer = 0;
	m_nUpDataOverheadSourceExchangePackets = 0;
	m_nUpDataOverheadFileRequestPackets = 0;
	m_nUpDataOverheadOtherPackets = 0;
	m_nUpDataOverheadServerPackets = 0;
	m_nLastStartUpload = 0;
	statsave=0;
	// -khaos--+++>
	iupdateconnstats=0;
	// <-----khaos-
	// By BadWolf - Accurate Speed Measurement
	sumavgUDRO = 0;
	//sendperclient = 0;
	//uLastAcceptNewClient = ::GetTickCount();
	//sumavgdata = 0;
	//m_delay = ::GetTickCount();
	//m_delaytmp = 0;
	// END By BadWolf - Accurate Speed Measurement

    m_MaxActiveClients = 0;
    m_MaxActiveClientsShortTime = 0;

    m_lastCalculatedDataRateTick = 0;
    m_dwLastCheckedForHighPrioClient = 0;

    m_dwLastCalculatedAverageCombinedFilePrioAndCredit = 0;
    m_fAverageCombinedFilePrioAndCredit = 0;

    m_avarage_dr_sum = 0;
}

/**
 * Remove the client from upload socket if there's another client with same/higher
 * priority that wants to get an upload socket. If there's not another matching client
 * move this client down in the upload list so that it is after all other of it's class.
 *
 * @param client address of the client that should be removed or moved down
 */
void CUploadQueue::RemoveOrMoveDown(CUpDownClient* client) {
    CUpDownClient* newclient = FindBestClientInQueue(true, client);

    if(newclient != NULL &&
        (newclient->HasReleasePrio() == true &&
         (client->IsFriend() == false || client->GetFriendSlot() == false) ||
         newclient->IsFriend() == true && newclient->GetFriendSlot() == true)) {

        // Remove client from ul list to make room for higher/same prio client
        client->SetWaitStartTime();
	    theApp.uploadqueue->RemoveFromUploadQueue(client);
	    theApp.uploadqueue->AddClientToQueue(client,true);
    } else {
        // PENDING: Clear done blocks to prevent chunk check from failing again
        client->ClearUploadDoneBlocks();
        //client->SetWaitStartTime();
        //client->SetUpStartTime();

        // Move down
        // first find the client in the uploadinglist
        uint32 posCounter = 0;
        POSITION foundPos = NULL;
        POSITION pos = uploadinglist.GetHeadPosition();
	    while(pos != NULL && foundPos == NULL) {
		    if (uploadinglist.GetAt(pos) == client){
                foundPos = pos;
            } else {
                uploadinglist.GetNext(pos);
                posCounter++;
            }
	    }

        if(foundPos != NULL) {
            // Remove the found Client
		    uploadinglist.RemoveAt(foundPos);
            theApp.uploadBandwidthThrottler->RemoveFromStandardList(client->socket);

            // then add it last in it's class
            InsertInUploadingList(client);
        }
    }
}

/**
 * Find the highest ranking client in the waiting queue, and return it.
 * Clients are ranked in the following classes:
 *    1: Friends, 2: Clients that wants release files 3: Other clients
 *
 * Clients are then ranked inside their classes by their credits and waiting time.
 *
 * Low id client are ranked as lowest possible, unless they are currently connected.
 * A low id client that is not connected, but would have been ranked highest if it
 * had been connected, gets a flag set. This flag means that the client should be
 * allowed to get an upload slot immediately once it connects.
 *
 * @return address of the highest ranking client.
 */
CUpDownClient* CUploadQueue::FindBestClientInQueue(bool allowLowIdAddNextConnectToBeSet, CUpDownClient* lowIdClientMustBeInSameClassAsThisClient) {
	POSITION toadd = 0;
	POSITION toaddlow = 0;
	uint32	bestscore = 0;
	uint32  bestlowscore = 0;

    CUpDownClient* newclient = NULL;
    CUpDownClient* lowclient = NULL;

    POSITION pos1, pos2;
	for (pos1 = waitinglist.GetHeadPosition();( pos2 = pos1 ) != NULL;){
		waitinglist.GetNext(pos1);
		CUpDownClient* cur_client =	waitinglist.GetAt(pos2);
		// clear dead clients
		ASSERT ( cur_client->GetLastUpRequest() );
		if ((::GetTickCount() - cur_client->GetLastUpRequest() > MAX_PURGEQUEUETIME) || !theApp.sharedfiles->GetFileByID(cur_client->GetUploadFileID()) ){
			RemoveFromWaitingQueue(pos2,true);	
			if (!cur_client->socket)
				cur_client->Disconnected();
        } else {
		// finished clearing
			uint32 cur_score = cur_client->GetScore(false);

		    if (
                (newclient != NULL &&
                 ((cur_client->IsFriend() == true && cur_client->GetFriendSlot() == true) && // cur_client has friend slot, but newclient has not
                  (newclient->IsFriend() == false || newclient->GetFriendSlot() == false) ||
                  (newclient->IsFriend() == false || newclient->GetFriendSlot() == false) &&  // neither has friend slot
                  (cur_client->IsFriend() == false || cur_client->GetFriendSlot() == false) &&
                  (newclient->HasReleasePrio() == false && cur_client->HasReleasePrio() == true || // cur_client wants powershare file, but newclient not
                   newclient->HasReleasePrio() ==  cur_client->HasReleasePrio() && // they both, or neither, want powershare file
                   cur_score > bestscore  // then use score to decide who
                  )
                 ) ||
                 newclient == NULL
                ) &&
                (!cur_client->IsBanned()) && 
                IsDownloading(cur_client) == false
            ) {
                // cur_client is more worthy than current best client that is ready to go (connected).
                if(!cur_client->HasLowID() || (cur_client->socket && cur_client->socket->IsConnected())) {
                    // this client is a HighID or a lowID client that is ready to go (connected)
                    // and it is more worthy
			        bestscore = cur_score;
			        toadd = pos2;
                    newclient = waitinglist.GetAt(toadd);
                } else if(allowLowIdAddNextConnectToBeSet) {
                    // this client is a lowID client that is not ready to go (not connected)

                    // now that we know this client is not ready to go, compare it to the best not ready client
                    // the best not ready client may be better than the best ready client, so we need to check
                    // against that client
                    if (!cur_client->AddNextConnect &&
                        (lowclient != NULL &&
                         ((cur_client->IsFriend() == true && cur_client->GetFriendSlot() == true) && // cur_client has friend slot, but lowclient has not
                          (lowclient->IsFriend() == false || lowclient->GetFriendSlot() == false) ||
                          (lowclient->IsFriend() == false || lowclient->GetFriendSlot() == false) &&  // neither has friend slot
                          (cur_client->IsFriend() == false || cur_client->GetFriendSlot() == false) &&
                          (lowclient->HasReleasePrio() == false && cur_client->HasReleasePrio() == true || // cur_client wants powershare file, but lowclient not
                           lowclient->HasReleasePrio() ==  cur_client->HasReleasePrio() && // they both, or neither, want powershare file
                           cur_score > bestscore // then use score to decide who
                          )
                         ) ||
                         lowclient == NULL
                        )
                    ){
                        // it is more worthy, keep it
					    bestlowscore = cur_score;
					    toaddlow = pos2;
                        lowclient = waitinglist.GetAt(toaddlow);
				    }
                }
            } else {
                // cur_client is more worthy. Save it.
            }
		}
	}

    if (bestlowscore > bestscore && allowLowIdAddNextConnectToBeSet) {
		newclient = waitinglist.GetAt(toaddlow);
        
        // is newclient in same class as lowIdClientMustBeInSameClassAsThisClient?
        if(lowIdClientMustBeInSameClassAsThisClient == NULL ||
           (lowIdClientMustBeInSameClassAsThisClient->IsFriend() && lowIdClientMustBeInSameClassAsThisClient->GetFriendSlot()) ==
           (newclient->IsFriend() && newclient->GetFriendSlot()) &&
           lowIdClientMustBeInSameClassAsThisClient->HasReleasePrio() == newclient->HasReleasePrio()){
		    newclient->AddNextConnect = true;
         }
	}

    if (!toadd) {
		return NULL;
    } else {
	    return waitinglist.GetAt(toadd);
    }
}

/**
 * Insert the client at the correct place in the uploading list.
 * The client should be inserted after all of its class, but before any
 * client of a lower ranking class.
 *
 * Clients are ranked in the following classes:
 *    1: Friends, 2: Clients that wants release files 3: Other clients
 *
 * @param newclient address of the client that should be inserted in the uploading list
 */
void CUploadQueue::InsertInUploadingList(CUpDownClient* newclient) {
    POSITION insertPosition = NULL;
    uint32 posCounter = uploadinglist.GetCount();

    if(newclient->IsFriend() && newclient->GetFriendSlot()) {
        // it's a friend. Add it last of friends.

        bool foundposition = false;
        POSITION pos = uploadinglist.GetTailPosition();
        while(pos != NULL && foundposition == false) {
            CUpDownClient* uploadingClient = uploadinglist.GetAt(pos);
            if(uploadingClient->IsFriend() == true && uploadingClient->GetFriendSlot() == true &&
               (!newclient->HasLowID() || !newclient->AddNextConnect ||
                newclient->HasLowID() && newclient->AddNextConnect && newclient->GetWaitStartTime() >= uploadingClient->GetWaitStartTime())) {
                foundposition = true;
            } else {
                insertPosition = pos;
                uploadinglist.GetPrev(pos);
                posCounter--;
            }
        }
    } else if(newclient->HasReleasePrio() == true) {
        // it's release prio. Add it after all friends and
        // after all release downloaders

        bool foundposition = false;
        POSITION pos = uploadinglist.GetTailPosition();
        while(pos != NULL && foundposition == false) {
            CUpDownClient* uploadingClient = uploadinglist.GetAt(pos);
            if(uploadingClient->IsFriend() == true && uploadingClient->GetFriendSlot() == true ||
               uploadingClient->HasReleasePrio() == true &&
               (!newclient->HasLowID() || !newclient->AddNextConnect ||
               newclient->HasLowID() && newclient->AddNextConnect && newclient->GetWaitStartTime() >= uploadingClient->GetWaitStartTime())) {
                foundposition = true;
            } else {
                insertPosition = pos;
                uploadinglist.GetPrev(pos);
                posCounter--;
            }
        }
	} else {
        // it's normal. Add it last.

        bool foundposition = false;
        POSITION pos = uploadinglist.GetTailPosition();
        while(pos != NULL && foundposition == false) {
            CUpDownClient* uploadingClient = uploadinglist.GetAt(pos);
            if(uploadingClient->IsFriend() == true && uploadingClient->GetFriendSlot() == true ||
               uploadingClient->HasReleasePrio() == true ||
               (!newclient->HasLowID() || !newclient->AddNextConnect ||
                newclient->HasLowID() && newclient->AddNextConnect && newclient->GetWaitStartTime() >= uploadingClient->GetWaitStartTime())) {
                foundposition = true;
            } else {
                insertPosition = pos;
                uploadinglist.GetPrev(pos);
                posCounter--;
            }
        }
	}

    if(insertPosition != NULL) {
        // add it at found pos
		uploadinglist.InsertBefore(insertPosition, newclient);
        theApp.uploadBandwidthThrottler->AddToStandardList(posCounter, newclient->socket);
    } else {
        // Add it first
        theApp.uploadBandwidthThrottler->AddToStandardList(uploadinglist.GetCount(), newclient->socket);
		uploadinglist.AddTail(newclient);
    }
}

bool CUploadQueue::AddUpNextClient(CUpDownClient* directadd, bool highPrioCheck) {
	//POSITION toadd = 0;
	//uint32	bestscore = 0;
	CUpDownClient* newclient = NULL;
	// select next client or use given client
	if (!directadd){
        newclient = FindBestClientInQueue(true);

        if(newclient != NULL) {
            if(highPrioCheck == true) {
		        POSITION lastpos = uploadinglist.GetTailPosition();

                CUpDownClient* lastClient = NULL;
                if(lastpos != NULL) {
                    lastClient = uploadinglist.GetAt(lastpos);
                }

                if(lastClient != NULL) {
                    if (newclient->HasReleasePrio() == true && lastClient->HasReleasePrio() == false &&
                        (lastClient->IsFriend() == false || lastClient->GetFriendSlot() == false) ||
                        newclient->IsFriend() == true && newclient->GetFriendSlot() == true &&
                        (lastClient->IsFriend() == false || lastClient->GetFriendSlot() == false)) {
                        // Remove last client from ul list to make room for higher prio client
		                theApp.uploadqueue->RemoveFromUploadQueue(lastClient, true, true);

		                // add to queue again. This is almost the same as in method
		                //    theApp.uploadqueue->AddClientToQueue(lastClient,true);

                        AddClientToQueue(lastClient,true, true);

                        //waitinglist.AddTail(lastClient);
		                //lastClient->SetUploadState(US_ONUPLOADQUEUE);
		                //lastClient->SendRankingInfo();
		                //theApp.emuledlg->transferwnd.queuelistctrl.AddClient(lastClient, false);
		                //theApp.emuledlg->transferwnd.ShowQueueCount(waitinglist.GetCount());
                    } else {
                        return false;
                    }
                }
            }

		    //RemoveFromWaitingQueue(toadd, true);
            RemoveFromWaitingQueue(newclient, true);
		    theApp.emuledlg->transferwnd.ShowQueueCount(waitinglist.GetCount());
        }
	} else
		newclient = directadd;

    if(newclient == NULL) {
        return false;
    }

	if (IsDownloading(newclient)) {
		return false;
	}

    bool connectSuccess = true;
	// tell the client that we are now ready to upload
	if (!newclient->socket || !newclient->socket->IsConnected()) {
		newclient->SetUploadState(US_CONNECTING);
		connectSuccess = newclient->TryToConnect(true);
	} else {
		Packet* packet = new Packet(OP_ACCEPTUPLOADREQ,0);
		theApp.uploadqueue->AddUpDataOverheadFileRequest(packet->size);
		newclient->socket->SendPacket(packet,true);
		newclient->SetUploadState(US_UPLOADING);
	}

    if(connectSuccess) {
	    newclient->SetUpStartTime();
	    newclient->ResetSessionUp();

        InsertInUploadingList(newclient);

	    // statistic
	    CKnownFile* reqfile = theApp.sharedfiles->GetFileByID((uchar*)newclient->GetUploadFileID());
	    if (reqfile)
		    reqfile->statistic.AddAccepted();
    	
	    theApp.emuledlg->transferwnd.uploadlistctrl.AddClient(newclient);

        return true;
    } else {
        return false;
    }
}

/**
 * Maintenance method for the uploading slots. It adds and removes clients to/from the
 * uploading list. It also makes sure that all the uploading slots' Sockets always have
 * enough packets in their queues, etc.
 *
 * This method is called aproximately once every 100 milliseconds.
 */
void CUploadQueue::Process(){
    DWORD curTick = ::GetTickCount();


    uint32 tempMaxActiveClients = 0;
    uint32 tempMaxActiveClientsShortTime = 0;
    POSITION activeClientsTickPos = activeClients_tick_list.GetHeadPosition();
    POSITION activeClientsListPos = activeClients_list.GetHeadPosition();
    while(activeClientsListPos != NULL) {
        DWORD activeClientsTickSnapshot = activeClients_tick_list.GetAt(activeClientsTickPos);
        uint32 activeClientsSnapshot = activeClients_list.GetAt(activeClientsListPos);

        if(activeClientsSnapshot > tempMaxActiveClients) {
            tempMaxActiveClients = activeClientsSnapshot;
        }

        if(activeClientsSnapshot > tempMaxActiveClientsShortTime && curTick - activeClientsTickSnapshot < 10 * 1000) {
            tempMaxActiveClientsShortTime = activeClientsSnapshot;
        }

        activeClients_tick_list.GetNext(activeClientsTickPos);
        activeClients_list.GetNext(activeClientsListPos);
    }
    m_MaxActiveClients = tempMaxActiveClients;
    m_MaxActiveClientsShortTime = tempMaxActiveClientsShortTime;

    uint32 wantedNumberOfTrickles = GetWantedNumberOfTrickleUploads();

    // How many slots should be open? Trickle slots included (at least 2 trickles, 30% of total, and the slots are expected to use UPLOAD_CLIENT_REALISTIC_AVERAGE_DATARATE in average, whichever number is largest)
    sint32 wantedNumberOfTotalUploads = max((uint32)(estadatarate/UPLOAD_CLIENT_DATARATE), m_MaxActiveClientsShortTime + MINNUMBEROFTRICKLEUPLOADS);
    wantedNumberOfTotalUploads = max(wantedNumberOfTotalUploads, m_MaxActiveClientsShortTime*1.3);

    // PENDING: Each 3 seconds
    if(curTick - m_dwLastCheckedForHighPrioClient >= 3*1000) {
        bool added = AddUpNextClient(NULL, true);
        if(added == false) {
            // set timer so we can wait a while
            m_dwLastCheckedForHighPrioClient = curTick;
        } else {
            // there might be another highprio client
            // don't set timer, so that we check next call as well
        }
    }

    POSITION ulpos = uploadinglist.GetHeadPosition();
    // The loop that feeds the upload slots with data.
    while (ulpos != NULL) {
        // Get the client. Note! Also updates ulpos as a side effect.
		CUpDownClient* cur_client = uploadinglist.GetNext(ulpos);
		cur_client->SendBlockData();
	}

	POSITION lastpos = uploadinglist.GetTailPosition();

    CUpDownClient* lastClient = NULL;
    if(lastpos != NULL) {
        lastClient = uploadinglist.GetAt(lastpos);
    }

    if(uploadinglist.GetCount() > MIN_UP_CLIENTS_ALLOWED &&
       ((uint32)uploadinglist.GetCount() > m_MaxActiveClients+wantedNumberOfTrickles ||
        (uint32)uploadinglist.GetCount() > m_MaxActiveClientsShortTime+wantedNumberOfTrickles && AcceptNewClient(uploadinglist.GetCount()) == false)) {
        // we need to close a trickle slot and put it back first on the queue
        if(lastClient != NULL && lastClient->GetUpStartTimeDelay() > 3*1000) {

            // There's to many open uploads (propably due to the user changing
            // the upload limit to a lower value). Remove the last opened upload and put
            // it back on the waitinglist. When it is put back, it get
            // to keep its waiting time. This means it is likely to soon be
            // choosen for upload again.
            // Remove from upload list.
            RemoveFromUploadQueue(lastClient, true, true);

            // add to queue again. This is almost the same as in method
            AddClientToQueue(lastClient,true, true);
        }
    } else if(m_MaxActiveClientsShortTime+wantedNumberOfTrickles > (uint32)uploadinglist.GetCount()) {
        // we have given all slots bandwidth this round, and couldn't have given them more.
        // open an extra slot so that we always have enough trickle slots

        if(AcceptNewClient(uploadinglist.GetCount()+1) && waitinglist.GetCount() > 0 &&
           (lastClient == NULL || lastClient->GetUpStartTimeDelay() > MINWAITBEFOREOPENANOTHERSLOTMS)
          ) {
            // There's not enough open uploads. Open another one.
            AddUpNextClient();
          }
    }

    // Save used bandwidth for speed calculations
    uint64 sentBytes = theApp.uploadBandwidthThrottler->GetNumberOfSentBytesSinceLastCallAndReset();
	avarage_dr_list.AddTail(sentBytes);
    m_avarage_dr_sum += sentBytes;

    avarage_friend_dr_list.AddTail(theApp.stat_sessionSentBytesToFriend);

    // Save time beetween each speed snapshot
    avarage_tick_list.AddTail(curTick);

    // don't save more than 30 secs of data
    while(avarage_tick_list.GetCount() > 0 && ::GetTickCount()-avarage_tick_list.GetHead() > 30*1000) {
   	    m_avarage_dr_sum -= avarage_dr_list.RemoveHead();
        avarage_friend_dr_list.RemoveHead();
        avarage_tick_list.RemoveHead();
    }

    // Save number of active clients for statistics
	activeClients_list.AddTail(theApp.uploadBandwidthThrottler->GetHighestNumberOfFullyActivatedSlotsSinceLastCallAndReset());
    activeClients_tick_list.AddTail(curTick);

    // Don't save more than three minutes of data about number of fully active clients
    while(curTick-activeClients_tick_list.GetHead() > 3*60*1000) {
        activeClients_tick_list.RemoveHead();
	    activeClients_list.RemoveHead();
    }
};

bool CUploadQueue::AcceptNewClient(uint32 numberOfUploads){
	// check if we can allow a new client to start downloading form us
	if (numberOfUploads < MIN_UP_CLIENTS_ALLOWED)
		return true;
	else if (numberOfUploads >= MAX_UP_CLIENTS_ALLOWED)
		return false;

	//now the final check
    if (numberOfUploads < (GetDatarate()/UPLOAD_CLIENT_DATARATE)+3 ||
        GetDatarate() < UPLOAD_LOW_CLIENT_DR*3 && numberOfUploads < GetDatarate()/UPLOAD_CLIENT_DATARATE)
			return true;
	//nope
	return false;
}

CUploadQueue::~CUploadQueue(){
	KillTimer(0,141);
}

CUpDownClient* CUploadQueue::GetWaitingClientByIP_UDP(uint32 dwIP, uint16 nUDPPort){
	for (POSITION pos = waitinglist.GetHeadPosition();pos != 0;){
		CUpDownClient* cur_client = waitinglist.GetNext(pos);
		if (dwIP == cur_client->GetIP() && nUDPPort == cur_client->GetUDPPort())
			return cur_client;
	}
	return 0;
}

#ifdef _DEBUG
CUpDownClient* CUploadQueue::GetWaitingClientByIP(uint32 dwIP){
	for (POSITION pos = waitinglist.GetHeadPosition();pos != 0;){
		CUpDownClient* cur_client = waitinglist.GetNext(pos);
		if (dwIP == cur_client->GetIP())
			return cur_client;
	}
	return 0;
}
#endif

void CUploadQueue::UpdateBanCount(){
	int count=0;
	for (POSITION pos = waitinglist.GetHeadPosition();pos != 0;waitinglist.GetNext(pos)){
		CUpDownClient* cur_client= waitinglist.GetAt(pos);
		if(cur_client->IsBanned())
			count++;
	}
	SetBanCount(count);
}

/**
 * Add a client to the waiting queue for uploads.
 *
 * @param client address of the client that should be added to the waiting queue
 *
 * @param bIgnoreTimelimit don't check timelimit to possibly ban the client.
 *
 * @param addInFirstPlace the client should be added first in queue, not last
 */
void CUploadQueue::AddClientToQueue(CUpDownClient* client, bool bIgnoreTimelimit, bool addInFirstPlace){
    if(addInFirstPlace == false) {
	    if (theApp.serverconnect->IsConnected() && theApp.serverconnect->IsLowID()
		    && !theApp.serverconnect->IsLocalServer(client->GetServerIP(),client->GetServerPort())
		    && client->GetDownloadState() == DS_NONE && !client->IsFriend()
		    && GetWaitingUserCount() > 50)
		    return;
	    client->AddAskedCount();
	    client->SetLastUpRequest();
	    if (!bIgnoreTimelimit){
		    if (client->IsBanned()){
			    if (::GetTickCount() - client->GetBanTime() > 18000000){
				    client->UnBan();
			    }
			    else
				    return;
		    }
		    client->AddRequestCount(client->GetUploadFileID());
	    }
    }
	// check for double
	for (POSITION pos = waitinglist.GetHeadPosition();pos != 0;waitinglist.GetNext(pos)){
		CUpDownClient* cur_client= waitinglist.GetAt(pos);
		if (cur_client == client){	//already on queue
// VQB LowID Slot Patch -- note:  should add limit so only if #slots < UL -or- UL+1 for Low UL (?)
            // PENDING: Move this to its own method
            uint32 wantedNumberOfTrickles = GetWantedNumberOfTrickleUploads();

            if (client->AddNextConnect && AcceptNewClient(uploadinglist.GetCount()+1) &&
                //(uploadinglist.GetCount() < MIN_UP_CLIENTS_ALLOWED ||
                // (uint32)uploadinglist.GetCount() <= m_MaxActiveClients+wantedNumberOfTrickles ||
                // (uint32)uploadinglist.GetCount() <= m_MaxActiveClientsShortTime+wantedNumberOfTrickles && AcceptNewClient(uploadinglist.GetCount()+1)) &&
                client == FindBestClientInQueue()) {
                    client->AddNextConnect = false;
                    RemoveFromWaitingQueue(client, true);
                    AddUpNextClient(client);
                    // theApp.emuledlg->AddLogLine(true,"Added Low ID User On Reconnect: " + (CString)client->GetUserName()); // VQB:  perhaps only add to debug log?
                    return;
            } else
                ;// theApp.emuledlg->AddLogLine(true, "Skipped LowID User on Reconnect: " + (CString)client->GetUserName());
// VQB end
			client->SendRankingInfo();
			theApp.emuledlg->transferwnd.queuelistctrl.RefreshClient(client);
			return;			
		}
		else if ( client->Compare(cur_client) ) {
			// another client with same ip or hash
			theApp.emuledlg->AddDebugLogLine(false,CString(GetResString(IDS_SAMEUSERHASH)),client->GetUserName(),cur_client->GetUserName(),cur_client->GetUserName() );
			RemoveFromWaitingQueue(pos,true);	
			if (!cur_client->socket)
				cur_client->Disconnected();	
			return;
		}
	}
	// done

	// Add clients server to list.
	if (theApp.glob_prefs->AddServersFromClient()){
		in_addr host;
		host.S_un.S_addr = client->GetServerIP();
		CServer* srv = new CServer(client->GetServerPort(), inet_ntoa(host));
		srv->SetListName(srv->GetAddress());

		if (!theApp.emuledlg->serverwnd.serverlistctrl.AddServer(srv, true))
			delete srv;
		/*else
			theApp.emuledlg->AddLogLine(false,"Added new server: %s:%d", srv->GetFullIP(), srv->GetPort());*/
	}

    if(addInFirstPlace == false) {
	    // statistic values
	    CKnownFile* reqfile = theApp.sharedfiles->GetFileByID((uchar*)client->GetUploadFileID());
		    if (reqfile)
			    reqfile->statistic.AddRequest();
	    // better way to cap the list
        uint32 softQueueLimit;
        uint32 hardQueueLimit;

        //if(theApp.glob_prefs->GetQueueSize()+bannedcount < 500) {
            softQueueLimit = theApp.glob_prefs->GetQueueSize()+bannedcount;
            hardQueueLimit = theApp.glob_prefs->GetQueueSize()+bannedcount + 200;
        //} else {
        //    softQueueLimit = (theApp.glob_prefs->GetQueueSize()+bannedcount)/4*3;
        //    hardQueueLimit = theApp.glob_prefs->GetQueueSize()+bannedcount;
        //}

        if ((uint32)waitinglist.GetCount() > softQueueLimit &&
            (client->IsFriend() == false || client->GetFriendSlot() == false) &&
            client->GetCombinedFilePrioAndCredit() < GetAverageCombinedFilePrioAndCredit()) {
		    return;
        }
    }

	if (client->IsDownloading()){
		// he's already downloading and wants probably only another file
		Packet* packet = new Packet(OP_ACCEPTUPLOADREQ,0);
		theApp.uploadqueue->AddUpDataOverheadFileRequest(packet->size);
		client->socket->SendPacket(packet,true);
		return;
	}

    waitinglist.AddTail(client);
    client->SetUploadState(US_ONUPLOADQUEUE);
    client->SendRankingInfo();
    theApp.emuledlg->transferwnd.queuelistctrl.AddClient(client);
    theApp.emuledlg->transferwnd.ShowQueueCount(waitinglist.GetCount());
}

float CUploadQueue::GetAverageCombinedFilePrioAndCredit() {
    DWORD curTick = ::GetTickCount();

    if (curTick - m_dwLastCalculatedAverageCombinedFilePrioAndCredit > 5*1000) {
        m_dwLastCalculatedAverageCombinedFilePrioAndCredit = curTick;

        POSITION pos1, pos2;

        // TODO: is there a risk of overflow? I don't think so...
        double sum = 0;

	    for (pos1 = waitinglist.GetHeadPosition();( pos2 = pos1 ) != NULL;){
		    waitinglist.GetNext(pos1);
		    CUpDownClient* cur_client =	waitinglist.GetAt(pos2);

            sum += cur_client->GetCombinedFilePrioAndCredit();
        }

        m_fAverageCombinedFilePrioAndCredit = sum/waitinglist.GetSize();
    }

    return m_fAverageCombinedFilePrioAndCredit;
}

bool CUploadQueue::RemoveFromUploadQueue(CUpDownClient* client, bool updatewindow, bool earlyabort){
    uint32 posCounter = 0;
	for (POSITION pos = uploadinglist.GetHeadPosition();pos != 0;uploadinglist.GetNext(pos)){
		if (client == uploadinglist.GetAt(pos)){
			if (updatewindow)
				theApp.emuledlg->transferwnd.uploadlistctrl.RemoveClient(uploadinglist.GetAt(pos));
			uploadinglist.RemoveAt(pos);
            theApp.uploadBandwidthThrottler->RemoveFromStandardList(client->socket);
			if( client->GetTransferedUp() || earlyabort == true ){
				successfullupcount++;
				totaluploadtime += client->GetUpStartTimeDelay()/1000;
			} else if(client->HasBlocks() || client->GetUploadState() != US_UPLOADING) {
				failedupcount++;
			}

            client->TruncateSocketQueues();

            client->SetUploadState(US_NONE);
			client->ClearUploadBlockRequests(/*!earlyabort*/);
			return true;
		}
        posCounter++;
	}
	return false;
}

uint32 CUploadQueue::GetAverageUpTime(){
	if( successfullupcount ){
		return totaluploadtime/successfullupcount;
	}
	return 0;
}

bool CUploadQueue::RemoveFromWaitingQueue(CUpDownClient* client, bool updatewindow){
	POSITION pos = waitinglist.Find(client);
	if (pos){
		RemoveFromWaitingQueue(pos,updatewindow);
		if (updatewindow)
			theApp.emuledlg->transferwnd.ShowQueueCount(waitinglist.GetCount());
		return true;
	}
	else
		return false;
}

void CUploadQueue::RemoveFromWaitingQueue(POSITION pos, bool updatewindow){	
	CUpDownClient* todelete = waitinglist.GetAt(pos);
	waitinglist.RemoveAt(pos);
	if( todelete->IsBanned() )
		todelete->UnBan();
	if (updatewindow)
		theApp.emuledlg->transferwnd.queuelistctrl.RemoveClient(todelete);
	todelete->SetUploadState(US_NONE);
}

void CUploadQueue::DeleteAll(){
	waitinglist.RemoveAll();
	uploadinglist.RemoveAll();
}

uint16 CUploadQueue::GetWaitingPosition(CUpDownClient* client){
	if (!IsOnUploadQueue(client))
		return 0;
	uint16 rank = 1;
	uint32 myscore = client->GetScore(false);
	for (POSITION pos = waitinglist.GetHeadPosition();pos != 0;waitinglist.GetNext(pos)){
		if (waitinglist.GetAt(pos)->GetScore(false) > myscore)
			rank++;
	}
	return rank;
}

void CUploadQueue::CompUpDatarateOverhead(){
	// Patch by BadWolf - Accurate datarate Calculation
	TransferredData newitem = {m_nUpDataRateMSOverhead,::GetTickCount()};
	this->m_AvarageUDRO_list.AddTail(newitem);
	sumavgUDRO += m_nUpDataRateMSOverhead;
	while ((float)(m_AvarageUDRO_list.GetTail().timestamp - m_AvarageUDRO_list.GetHead().timestamp)  > MAXAVERAGETIME)
		sumavgUDRO -= m_AvarageUDRO_list.RemoveHead().datalen;
	m_nUpDataRateMSOverhead = 0;
	if(m_AvarageUDRO_list.GetCount() > 10)
		m_nUpDatarateOverhead = 1000 * sumavgUDRO /(m_AvarageUDRO_list.GetTail().timestamp - m_AvarageUDRO_list.GetHead().timestamp);
	else
		m_nUpDatarateOverhead = 0;
	return;
	// END Patch by BadWolf	
}

VOID CALLBACK CUploadQueue::UploadTimer(HWND hwnd, UINT uMsg,UINT_PTR idEvent,DWORD dwTime)
{
	// NOTE: Always handle all type of MFC exceptions in TimerProcs - otherwise we'll get mem leaks
	try
	{
		// Barry - Don't do anything if the app is shutting down - can cause unhandled exceptions
		if (!theApp.emuledlg->IsRunning())
			return;

		theApp.uploadqueue->Process();
		theApp.downloadqueue->Process();
		theApp.uploadqueue->CompUpDatarateOverhead();
		theApp.downloadqueue->CompDownDatarateOverhead();
		counter++;

		// one second
		if (counter >= 10){
			counter=0;
			theApp.clientcredits->Process();
			theApp.serverlist->Process();
			theApp.friendlist->Process();
			if( theApp.serverconnect->IsConnecting() && !theApp.serverconnect->IsSingleConnect() )
				theApp.serverconnect->TryAnotherConnectionrequest();

			theApp.emuledlg->statisticswnd.UpdateConnectionsStatus();
			if (theApp.glob_prefs->WatchClipboard4ED2KLinks()) theApp.emuledlg->searchwnd.SearchClipBoard();		
			if (theApp.serverconnect->IsConnecting()) theApp.serverconnect->CheckForTimeout();

			// -khaos--+++> Update connection stats...
			iupdateconnstats++;
			// 2 seconds
			if (iupdateconnstats>=2) {
				iupdateconnstats=0;
				theApp.emuledlg->statisticswnd.UpdateConnectionStats((float)theApp.uploadqueue->GetDatarate()/1024, (float)theApp.downloadqueue->GetDatarate()/1024);
			}
			// <-----khaos-

			// display graphs
			if (theApp.glob_prefs->GetTrafficOMeterInterval()>0) {
				igraph++;

				if (igraph >= (uint32)(theApp.glob_prefs->GetTrafficOMeterInterval()) ) {
					igraph=0;
					//theApp.emuledlg->statisticswnd.SetCurrentRate((float)(theApp.uploadqueue->Getavgupload()/theApp.uploadqueue->Getavg())/1024,(float)(theApp.uploadqueue->Getavgdownload()/theApp.uploadqueue->Getavg())/1024);
					theApp.emuledlg->statisticswnd.SetCurrentRate((float)(theApp.uploadqueue->GetDatarate())/1024,(float)(theApp.downloadqueue->GetDatarate())/1024, (float)(theApp.uploadqueue->GetDatarate()-theApp.uploadqueue->GetToNetworkDatarate())/1024);
					//theApp.uploadqueue->Zeroavg();
				}
			}
			if (theApp.emuledlg->activewnd == &theApp.emuledlg->statisticswnd && theApp.emuledlg->IsWindowVisible() )  {
				// display stats
				if (theApp.glob_prefs->GetStatsInterval()>0) {
					istats++;

					if (istats >= (uint32)(theApp.glob_prefs->GetStatsInterval()) ) {
						istats=0;
						theApp.emuledlg->ShowStatistics();
					}
				}
			}
			//save rates every second
			theApp.emuledlg->statisticswnd.RecordRate();

			sec++;
			// 5 seconds
			if (sec>=5) {

				#ifdef _DEBUG
					if (!AfxCheckMemory()) AfxDebugBreak();
				#endif

				sec = 0;
				theApp.listensocket->Process();
				theApp.OnlineSig(); // Added By Bouc7 
				theApp.emuledlg->ShowTransferRate();
				
				// update cat-titles with downloadinfos only when needed
				if (theApp.glob_prefs->ShowCatTabInfos() && 
					theApp.emuledlg->activewnd==&theApp.emuledlg->transferwnd && 
					theApp.emuledlg->IsWindowVisible()) 
						theApp.emuledlg->transferwnd.UpdateCatTabTitles();
				
				if (theApp.glob_prefs->IsSchedulerEnabled()) theApp.scheduler->Check();
			}

			statsave++;
			// -khaos--+++>
			// 60 seconds
			if (statsave>=60) {
				// Time to save our cumulative statistics.
				statsave=0;
				
				// CPreferences::SaveStats() in Preferences.cpp
				// This function does NOT update the tree!
				theApp.glob_prefs->SaveStats();
			// <-----khaos-
			}
		}
	}
	CATCH_DFLT_EXCEPTIONS("CUploadQueue::UploadTimer")
}

CUpDownClient* CUploadQueue::GetNextClient(CUpDownClient* lastclient){
	if (waitinglist.IsEmpty())
		return 0;
	if (!lastclient)
		return waitinglist.GetHead();
	POSITION pos = waitinglist.Find(lastclient);
	if (!pos){
		TRACE("Error: CServerList::GetNextClient");
		return waitinglist.GetHead();
	}
	waitinglist.GetNext(pos);
	if (!pos)
		return NULL;
	else
		return waitinglist.GetAt(pos);
}

void CUploadQueue::FindSourcesForFileById(CTypedPtrList<CPtrList, CUpDownClient*>* srclist, uchar* filehash) {
	POSITION pos;
	
	pos = uploadinglist.GetHeadPosition();
	while(pos) {
		CUpDownClient *potential = uploadinglist.GetNext(pos);
		if(md4cmp(potential->GetUploadFileID(), filehash) == 0)
			srclist->AddTail(potential);
	}

	pos = waitinglist.GetHeadPosition();
	while(pos) {
		CUpDownClient *potential = waitinglist.GetNext(pos);
		if(md4cmp(potential->GetUploadFileID(), filehash) == 0)
			srclist->AddTail(potential);
	}
}

void CUploadQueue::UpdateDatarates() {
    // Calculate average datarate
    if(::GetTickCount()-m_lastCalculatedDataRateTick > 500) {
        m_lastCalculatedDataRateTick = ::GetTickCount();

        if(avarage_dr_list.GetSize() >= 2 && (avarage_tick_list.GetTail() > avarage_tick_list.GetHead())) {
	        datarate = ((m_avarage_dr_sum-avarage_dr_list.GetHead())*1000) / (avarage_tick_list.GetTail()-avarage_tick_list.GetHead());
            friendDatarate = ((avarage_friend_dr_list.GetTail()-avarage_friend_dr_list.GetHead())*1000) / (avarage_tick_list.GetTail()-avarage_tick_list.GetHead());
        } else {
            datarate = 0;
            friendDatarate = 0;
        }
    }
}

uint32 CUploadQueue::GetDatarate() {
    UpdateDatarates();
    return datarate;
}

uint32 CUploadQueue::GetToNetworkDatarate() {
    UpdateDatarates();
    if(datarate > friendDatarate) {
        return datarate - friendDatarate;
    } else {
        return 0;
    }
}

uint32 CUploadQueue::GetWantedNumberOfTrickleUploads() {
    return max(((uint32)uploadinglist.GetCount())*0.3, MINNUMBEROFTRICKLEUPLOADS);
}