//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 "emule.h"

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

/**
 * The constructor starts the thread.
 */
UploadBandwidthThrottler::UploadBandwidthThrottler(void) {
    m_SentBytesSinceLastCall = 0;
    m_highestNumberOfFullyActivatedSlots = 0;

    threadEndedEvent = new CEvent(0, 1);
    doRun = true;
    AfxBeginThread(RunProc, (LPVOID)this);
}

/**
 * The destructor stops the thread. If the thread has already stops, destructor does nothing.
 */
UploadBandwidthThrottler::~UploadBandwidthThrottler(void) {
    EndThread();
}

/**
 * Find out how many bytes that has been put on the sockets since the last call to this
 * method.
 *
 * @return the number of bytes that has been put on the sockets since the last call
 */
uint64 UploadBandwidthThrottler::GetNumberOfSentBytesSinceLastCallAndReset() {
    sendLocker.Lock();
    
    uint64 numberOfSentBytesSinceLastCall = m_SentBytesSinceLastCall;
    m_SentBytesSinceLastCall = 0;

    sendLocker.Unlock();

    return numberOfSentBytesSinceLastCall;
}

/**
 * Find out the highest number of slots that has been fed data in the normal standard loop
 * of the thread since the last call of this method. This means all slots that haven't
 * been in the trickle state during the entire time since the last call.
 *
 * @return the highest number of fully activated slots during any loop since last call
 */
uint32 UploadBandwidthThrottler::GetHighestNumberOfFullyActivatedSlotsSinceLastCallAndReset() {
    sendLocker.Lock();
    
    uint64 highestNumberOfFullyActivatedSlots = m_highestNumberOfFullyActivatedSlots;
    m_highestNumberOfFullyActivatedSlots = 0;

    sendLocker.Unlock();

    return highestNumberOfFullyActivatedSlots;
}

/**
 * Add a socket to the list of sockets that have upload slots. The main thread will
 * continously call send on these sockets, to give them chance to work off their queues.
 * The sockets are called in the order they exist in the list, so the top socket (index 0)
 * will be given a chance first to use bandwidth, and then the next socket (index 1) etc.
 *
 * It is possible to add a socket several times to the list without removing it inbetween,
 * but that should be avoided.
 *
 * @param index insert the socket at this place in the list. An index that is higher than the
 *              current number of sockets in the list will mean that the socket should be inserted
 *              last in the list.
 *
 * @param socket the address to the socket that should be added to the list. If the address is NULL,
 *               this method will do nothing.
 */
void UploadBandwidthThrottler::AddToStandardList(uint32 index, CEMSocket* socket) {
    if(socket != NULL) {
        sendLocker.Lock();

        RemoveFromStandardListNoLock(socket);

        if(index > (uint32)m_StandardOrder_list.GetSize()) {
            index = m_StandardOrder_list.GetSize();
        }
        m_StandardOrder_list.InsertAt(index, socket);

        sendLocker.Unlock();
    } else {
        //theApp.emuledlg->AddDebugLogLine(true,"Tried to add NULL socket to UploadBandwidthThrottler Standard list! Prevented.");
    }
}

/**
 * Remove a socket from the list of sockets that have upload slots.
 *
 * If the socket has mistakenly been added several times to the list, this method
 * will return all of the entries for the socket.
 *
 * @param socket the address of the socket that should be removed from the list. If this socket
 *               does not exist in the list, this method will do nothing.
 */
void UploadBandwidthThrottler::RemoveFromStandardList(CEMSocket* socket) {
    sendLocker.Lock();

    RemoveFromStandardListNoLock(socket);

    sendLocker.Unlock();
}

/**
 * Remove a socket from the list of sockets that have upload slots. NOT THREADSAFE!
 * This is an internal method that doesn't take the necessary lock before it removes
 * the socket. This method should only be called when the current thread already owns
 * the sendLocker lock!
 *
 * @param socket address of the socket that should be removed from the list. If this socket
 *               does not exist in the list, this method will do nothing.
 */
void UploadBandwidthThrottler::RemoveFromStandardListNoLock(CEMSocket* socket) {
    // Find the slot
    int slotCounter = 0;
    while(slotCounter < m_StandardOrder_list.GetSize()) {
        if(m_StandardOrder_list.GetAt(slotCounter) == socket) {
            // Remove the slot
            m_StandardOrder_list.RemoveAt(slotCounter);
        } else {
            slotCounter++;
        }
    }
}

/**
* Notifies the send thread that it should try to call controlpacket send
* for the supplied socket. It is allowed to call this method several times
* for the same socket, without having controlpacket send called for the socket
* first. The doublette entries are handled elsewhere.
*
* @param socket address to the socket that requests to have controlpacket send
*               to be called on it
*/
void UploadBandwidthThrottler::QueueForSendingControlPacket(CEMSocket* socket) {
    // Get critical section
    tempQueueLocker.Lock();


    if(doRun) {
        m_TempControlQueue_list.AddTail(socket);
    }

    // End critical section
    tempQueueLocker.Unlock();
}

/**
 * Remove the socket from all lists and queues. This will make it safe to
 * erase/delete the socket. It will also cause the main thread to stop calling
 * send() for the socket.
 *
 * @param socket address to the socket that should be removed
 */
void UploadBandwidthThrottler::RemoveFromAllQueues(CEMSocket* socket) {
    // Get critical section
    sendLocker.Lock();

    if(doRun) {
        // Remove this socket from control packet queue
        POSITION pos1, pos2;
	    for (pos1 = m_ControlQueue_list.GetHeadPosition();( pos2 = pos1 ) != NULL;) {
		    m_ControlQueue_list.GetNext(pos1);
		    CEMSocket* socketinQueue = m_ControlQueue_list.GetAt(pos2);

            if(socketinQueue == socket) {
                m_ControlQueue_list.RemoveAt(pos2);
            }
        }

        // And remove it from upload slots
        RemoveFromStandardListNoLock(socket);
    }

    // End critical section
    sendLocker.Unlock();
}

/**
 * Make the thread exit. This method will not return until the thread has stopped
 * looping. This guarantees that the thread will not access the CEMSockets after this
 * call has exited.
 */
void UploadBandwidthThrottler::EndThread() {
    sendLocker.Lock();

    // signal the thread to stop looping and exit.
    doRun = false;

    sendLocker.Unlock();

    // wait for the thread to signal that it has stopped looping.
    threadEndedEvent->Lock();
}

/**
 * Start the thread. Called from the constructor in this class.
 *
 * @param pParam
 *
 * @return
 */
UINT AFX_CDECL UploadBandwidthThrottler::RunProc(LPVOID pParam) {
    UploadBandwidthThrottler* uploadBandwidthThrottler = (UploadBandwidthThrottler*)pParam;

    return uploadBandwidthThrottler->RunInternal();
}

/**
 * The thread method that handles calling send for the individual sockets.
 *
 * Control packets will always be tried to be sent first. If there is any bandwidth leftover
 * after that, send() for the upload slot sockets will be called in priority order until we have run
 * out of available bandwidth for this loop. Upload slots will not be allowed to go without having sent
 * called for more than a defined amount of time (i.e. two seconds).
 *
 * @return always returns 0.
 */
UINT UploadBandwidthThrottler::RunInternal() {
    DWORD lastLoopTick = ::GetTickCount();

    // For evening out used data
    //DWORD longTime = ::GetTickCount();
    //uint64 longUsedData = 0;

    //DWORD longTime2 = ::GetTickCount();
    //uint64 longUsedData2 = 0;

    sint64 bytesToSpend = 0;

    //uint64 leftoverBandwidth = 0;

    while(doRun) {
        uint32 allowedDataRate = theApp.glob_prefs->GetMaxUpload()*1024;

        DWORD timeSinceLastLoop = ::GetTickCount() - lastLoopTick;

#define TIME_BETWEEN_UPLOAD_LOOPS 1
        if(timeSinceLastLoop < TIME_BETWEEN_UPLOAD_LOOPS) {
            Sleep(TIME_BETWEEN_UPLOAD_LOOPS-timeSinceLastLoop);
        }

        DWORD thisLoopTick = ::GetTickCount();
        timeSinceLastLoop = thisLoopTick - lastLoopTick;

        // Calculate how many bytes we can spend
        if(allowedDataRate != 0) {
            bytesToSpend += allowedDataRate*(thisLoopTick-lastLoopTick)/1000;
        } else {
            bytesToSpend = _I64_MAX;
        }

        lastLoopTick = thisLoopTick;

        sendLocker.Lock();

        uint32 spentBytes = 0;

        tempQueueLocker.Lock();

        // are there any sockets in m_TempControlQueue_list? Move them to normal m_ControlQueue_list;
        while(!m_TempControlQueue_list.IsEmpty()) {
            CEMSocket* moveSocket = m_TempControlQueue_list.RemoveHead();
            m_ControlQueue_list.AddTail(moveSocket);
        }

        tempQueueLocker.Unlock();
        
        // Send any queued up control packets first
        while(spentBytes+MINFRAGSIZE <= bytesToSpend && !m_ControlQueue_list.IsEmpty()) {
            CEMSocket* socket = m_ControlQueue_list.RemoveHead();

            if(socket != NULL) {
                spentBytes += socket->Send(bytesToSpend-spentBytes, true);
            }
        }

        // Any bandwidth that hasn't been used by the control packets are used for the upload slots.
        for(uint32 slotCounter = 0; slotCounter < (uint32)m_StandardOrder_list.GetSize(); slotCounter++) {
            CEMSocket* socket = m_StandardOrder_list.GetAt(slotCounter);

            if(socket != NULL) {
                if(spentBytes+MINFRAGSIZE <= bytesToSpend) {
                    bool firstLoop = true;
                    uint32 lastSpentBytes = 0;
                    while((lastSpentBytes > 0 || firstLoop == true) && spentBytes+MINFRAGSIZE <= bytesToSpend) {
                        lastSpentBytes = socket->Send(bytesToSpend-spentBytes);

                        spentBytes += lastSpentBytes;
                        firstLoop = false;
                    }

                    if(slotCounter > m_highestNumberOfFullyActivatedSlots) {
                        m_highestNumberOfFullyActivatedSlots = slotCounter;
                    }
                } else{
                    if(::GetTickCount()-socket->GetLastCalledSend() > 2*1000) {  // Pending: this seems to give access error exception sometimes?
                        // trickle
                        spentBytes += socket->Send(1024);
                    }
                }
            } else {
                //theApp.emuledlg->AddDebugLogLine(true,"There was a NULL socket in the UploadBandwidthThrottler Standard list! Prevented usage. Index: %i Size: %i", slotCounter, m_StandardOrder_list.GetSize());
            }
        }

        bytesToSpend -= spentBytes;

        uint32 estimatedNumberOfTrickleSlots = ((uint32)m_StandardOrder_list.GetSize())-min(((uint32)m_StandardOrder_list.GetSize()), m_highestNumberOfFullyActivatedSlots);
        if(bytesToSpend < -(sint64)(estimatedNumberOfTrickleSlots*1024)) {
            bytesToSpend = -(sint64)(estimatedNumberOfTrickleSlots*1024);
        } else if(bytesToSpend > MINFRAGSIZE) {
            bytesToSpend = MINFRAGSIZE;
        }

        m_SentBytesSinceLastCall += spentBytes;

        sendLocker.Unlock();
    }

    threadEndedEvent->SetEvent();

    tempQueueLocker.Lock();
    m_TempControlQueue_list.RemoveAll();
    tempQueueLocker.Unlock();

    sendLocker.Lock();

    m_ControlQueue_list.RemoveAll();
    m_StandardOrder_list.RemoveAll();
    sendLocker.Unlock();

    return 0;
}

