//this file is part of eMule
//Copyright (C)2002-2006 Merkur ( strEmail.Format("%s@%s", "devteam", "emule-project.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"
#include <zlib/zlib.h>
#include "UpDownClient.h"
//#include "UrlClient.h" // <-- peercache?
#include "Packets.h"
//#include "UploadQueue.h"
#include "Statistics.h"
//#include "ClientList.h"
//#include "ClientUDPSocket.h"
//#include "SharedFileList.h"
//#include "KnownFileList.h"
//#include "PartFile.h"
//#include "ClientCredits.h"
//#include "ListenSocket.h" // <-- peercache?
//#include "PeerCacheSocket.h" // <-- peercache?
//#include "Sockets.h" // <-- peercache?
//#include "OtherFunctions.h"
//#include "SafeFile.h"
//#include "DownloadQueue.h"
//#include "emuledlg.h"
#include "TransferWnd.h"
#include "Log.h"
//#include "Collection.h"
#include "UploadPacketCreaterThread.h"
#include <Mmsystem.h>

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

/**
 * The constructor starts the thread.
 */
UploadPacketCreaterThread::UploadPacketCreaterThread(CUpDownClient* owner) {
    //theApp.QueueDebugLogLine(false, _T("%ui UploadPacketCreaterThread constructor Client: %s"), timeGetTime(), owner->DbgGetClientInfo());
    threadEndedEvent = new CEvent(0, 1);

    m_owner = owner;

	doRun = true;
	AfxBeginThread(RunProc, (LPVOID)this);
}

/**
 * The destructor stops the thread. If the thread has already stoppped, destructor does nothing.
 */
UploadPacketCreaterThread::~UploadPacketCreaterThread(void) {
    //theApp.QueueDebugLogLine(false, _T("%ui UploadPacketCreaterThread destructor starting. Client: %s"), timeGetTime(), m_owner->DbgGetClientInfo());

    // stop thread and wait for it to stop
    EndThread();

    // delete objects
    while(!m_PacketCreation_queue.IsEmpty()) {
        delete m_PacketCreation_queue.RemoveHead();
    }

    delete threadEndedEvent;

    //theApp.QueueDebugLogLine(false, _T("%ui UploadPacketCreaterThread destructor completed. Client: %s"), timeGetTime(), m_owner->DbgGetClientInfo());
}

/**
 * Make the thread exit. This method will not return until the thread has stopped
 * looping.
 */
void UploadPacketCreaterThread::EndThread() {
    //theApp.QueueDebugLogLine(false, _T("%ui UploadPacketCreaterThread EndThread() starting. Client: %s"), timeGetTime(), m_owner->DbgGetClientInfo());

    // signal the thread to stop looping and exit.
	doRun = false;
    pauseEvent.SetEvent();

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

    //theApp.QueueDebugLogLine(false, _T("%ui UploadPacketCreaterThread EndThread() completed. Client: %s"), timeGetTime(), m_owner->DbgGetClientInfo());
}

UINT AFX_CDECL UploadPacketCreaterThread::RunProc(LPVOID pParam) {
	DbgSetThreadName("UploadPacketCreaterThread");
	InitThreadLocale();
	UploadPacketCreaterThread* uploadPacketCreaterThread = (UploadPacketCreaterThread*)pParam;

	return uploadPacketCreaterThread->RunInternal();
}

UINT UploadPacketCreaterThread::RunInternal() {
	while(doRun) {
        PacketParameters* params = GetNextPacketParam();

        while(doRun && params) {
            params->CreateAndSend();

            delete params;

            params = GetNextPacketParam();
        }

        if(params) {
            delete params;
        }

        pauseEvent.Lock();
	}

	threadEndedEvent->SetEvent();

	return 0;
}

PacketParameters* UploadPacketCreaterThread::GetNextPacketParam() {
    PacketParameters* params;

    paramQueueLocker.Lock();
    if(!m_PacketCreation_queue.IsEmpty()) {
        params = m_PacketCreation_queue.RemoveHead();
    } else {
        params = NULL;
    }
    paramQueueLocker.Unlock();

    return params;
}

void UploadPacketCreaterThread::CreateStandartPackets(byte* data, UINT togo, const uchar* FileID, uint64 StartOffset, uint64 EndOffset, uint64 currentBlockDone, bool bFromPF, UINT packetSplitSize) {
    PacketParameters* params = new StandardPacketParameters(m_owner, data, togo, FileID, StartOffset, EndOffset, currentBlockDone, bFromPF, packetSplitSize);

    paramQueueLocker.Lock();
    m_PacketCreation_queue.AddTail(params);
    paramQueueLocker.Unlock();

    pauseEvent.SetEvent();
}

void UploadPacketCreaterThread::CreatePackedPackets(byte* data, UINT togo, const uchar* FileID, uint64 StartOffset, uint64 EndOffset, bool bFromPF, UINT packetSplitSize) {
    PacketParameters* params = new PackedPacketParameters(m_owner, data, togo, FileID, StartOffset, EndOffset, bFromPF, packetSplitSize);

    paramQueueLocker.Lock();
    m_PacketCreation_queue.AddTail(params);
    paramQueueLocker.Unlock();

    pauseEvent.SetEvent();
}


// --

PacketParameters::~PacketParameters() {
    //theApp.QueueDebugLogLine(false, _T("%i PacketParameters destructor"), timeGetTime());
    delete data;
}

void PacketParameters::Init(CUpDownClient* owner, byte* par_data, UINT par_togo, const uchar* par_FileID, uint64 par_StartOffset, uint64 par_EndOffset, uint64 par_currentBlockDone, bool par_bFromPF, UINT par_packetSplitSize) {
    m_owner = owner;

    data = par_data;  // DELETE THIS IN CONSTRUCTOR

    togo = par_togo;

    md4cpy(&FileID[0], par_FileID);
    StartOffset = par_StartOffset;
    EndOffset = par_EndOffset;
    currentBlockDone = par_currentBlockDone;
    bFromPF = par_bFromPF;
    packetSplitSize = par_packetSplitSize;
}

void PacketParameters::SendPacket(Packet* packet, uint32 actualPayloadSize) {
    m_owner->SendFileDataPacket(packet, actualPayloadSize);
}


// --

StandardPacketParameters::StandardPacketParameters() {

}


StandardPacketParameters::StandardPacketParameters(CUpDownClient* owner, byte* data, UINT togo, const uchar* FileID, uint64 StartOffset, uint64 EndOffset, uint64 currentBlockDone, bool bFromPF, UINT packetSplitSize) {
    Init(owner, data, togo, FileID, StartOffset, EndOffset, currentBlockDone, bFromPF, packetSplitSize);
}

void StandardPacketParameters::CreateAndSend() {
//void StandardPacketParameters::CreateStandartPackets(byte* data,uint32 togo, uchar* FileID, uint64 StartOffset, uint64 EndOffset, uint64 currentBlockDone, bool bFromPF, UINT packetSplitSize){
	uint32 nPacketSize;
	CMemFile memfile((BYTE*)data,togo);
	if (togo > packetSplitSize)
		nPacketSize = togo/(uint32)(togo/packetSplitSize);
	else
		nPacketSize = togo;

    uint32 done = 0;

	while (togo){
		if (togo < nPacketSize*2)
			nPacketSize = togo;
		ASSERT( nPacketSize );
		togo -= nPacketSize;
        done += nPacketSize;

        //uint64 statpos = (EndOffset - togo) - nPacketSize;
        //uint64 endpos = (EndOffset - togo);
        //if (IsUploadingToPeerCache())
        //{
        //    uint64 statpos = (EndOffset - togo) - nPacketSize;
        //    uint64 endpos = (EndOffset - togo);

        //    if (m_pPCUpSocket == NULL){
        //        ASSERT(0);
        //        CString strError;
        //        strError.Format(_T("Failed to upload to PeerCache - missing socket; %s"), DbgGetClientInfo());
        //        throw strError;
        //    }
        //    CSafeMemFile dataHttp(packetSplitSize);
        //    if (m_iHttpSendState == 0)
        //    {
        //        CKnownFile* srcfile = theApp.sharedfiles->GetFileByID(FileID);
        //        CStringA str;
        //        str.AppendFormat("HTTP/1.0 206\r\n");
        //        str.AppendFormat("Content-Range: bytes %I64u-%I64u/%I64u\r\n", StartOffset, EndOffset - 1, srcfile->GetFileSize());
        //        str.AppendFormat("Content-Type: application/octet-stream\r\n");
        //        str.AppendFormat("Content-Length: %u\r\n", (uint32)(EndOffset - StartOffset));
        //        str.AppendFormat("Server: eMule/%s\r\n", CStringA(theApp.m_strCurVersionLong));
        //        str.AppendFormat("\r\n");
        //        dataHttp.Write((LPCSTR)str, str.GetLength());
        //        theStats.AddUpDataOverheadFileRequest((UINT)dataHttp.GetLength());

        //        m_iHttpSendState = 1;
        //        if (thePrefs.GetDebugClientTCPLevel() > 0){
        //            DebugSend("PeerCache-HTTP", this, FileID);
        //            Debug(_T("  %hs\n"), str);
        //        }
        //    }
        //    dataHttp.Write(data, nPacketSize);
        //    data += nPacketSize;

        //    if (thePrefs.GetDebugClientTCPLevel() > 1){
        //        DebugSend("PeerCache-HTTP data", this, FileID);
        //        Debug(_T("  Start=%I64u  End=%I64u  Size=%u\n"), statpos, endpos, nPacketSize);
        //    }

        //    UINT uRawPacketSize = (UINT)dataHttp.GetLength();
        //    LPBYTE pRawPacketData = dataHttp.Detach();
        //    CRawPacket* packet = new CRawPacket((char*)pRawPacketData, uRawPacketSize, bFromPF);
        //    m_pPCUpSocket->SendPacket(packet, true, false, nPacketSize);
        //    free(pRawPacketData);
        //}
        //else
        //{
			Packet* packet;
            if (StartOffset > 0xFFFFFFFF || EndOffset > 0xFFFFFFFF){
		        uint64 statpos = (StartOffset + currentBlockDone) + done -nPacketSize;
		        uint64 endpos = (StartOffset + currentBlockDone) + done;

                packet = new Packet(OP_SENDINGPART_I64,nPacketSize+32, OP_EMULEPROT, bFromPF);
				md4cpy(&packet->pBuffer[0],FileID);
				PokeUInt64(&packet->pBuffer[16], statpos);
				PokeUInt64(&packet->pBuffer[24], endpos);
				memfile.Read(&packet->pBuffer[32],nPacketSize);
				theStats.AddUpDataOverheadFileRequest(32);

                if (thePrefs.GetDebugClientTCPLevel() > 0){
				    DebugSend("OP__SendingPart", m_owner, FileID);
				    Debug(_T("  Start=%I64u  End=%I64u  Size=%u\n"), statpos, endpos, nPacketSize);
			    }
			}
			else{
		        uint32 statpos = ((uint32)StartOffset + (uint32)currentBlockDone) + done -nPacketSize;
		        uint32 endpos = ((uint32)StartOffset + (uint32)currentBlockDone) + done;

                packet = new Packet(OP_SENDINGPART,nPacketSize+24, OP_EDONKEYPROT, bFromPF);
				md4cpy(&packet->pBuffer[0],FileID);
				PokeUInt32(&packet->pBuffer[16], statpos);
				PokeUInt32(&packet->pBuffer[20], endpos);
				memfile.Read(&packet->pBuffer[24],nPacketSize);
				theStats.AddUpDataOverheadFileRequest(24);

                if (thePrefs.GetDebugClientTCPLevel() > 0){
				    DebugSend("OP__SendingPart", m_owner, FileID);
				    Debug(_T("  Start=%u  End=u  Size=%u\n"), statpos, endpos, nPacketSize);
			    }
			}

			// put packet directly on socket

			SendPacket(packet, nPacketSize);
		//}
	}
}

//--

PackedPacketParameters::PackedPacketParameters(CUpDownClient* owner, byte* data, UINT togo, const uchar* FileID, uint64 StartOffset, uint64 EndOffset, bool bFromPF, UINT packetSplitSize) {
    Init(owner, data, togo, FileID, StartOffset, EndOffset, 0, bFromPF, packetSplitSize);
}

PackedPacketParameters::~PackedPacketParameters() {

}

void PackedPacketParameters::CreateAndSend() {
//void PackedPacketParameters::CreatePackedPackets(byte* data, uint32 togo, uchar* FileID, uint64 StartOffset, uint64 EndOffset, bool bFromPF, UINT packetSplitSize){
	//ASSERT(m_currentBlockDone==0);
	BYTE* output = new BYTE[togo+300];
	uLongf newsize = togo+300;
	UINT result = compress2(output, &newsize, data, togo, 9);
	if (result != Z_OK || togo <= newsize){
		delete[] output;
        StandardPacketParameters::CreateAndSend();
		return;
	}
	CMemFile memfile(output,newsize);
    uint32 oldSize = togo;
	togo = newsize;
	uint32 nPacketSize;
    if (togo > packetSplitSize)
        nPacketSize = togo/(uint32)(togo/packetSplitSize);
    else
        nPacketSize = togo;

    uint32 totalPayloadSize = 0;

    while (togo){
		if (togo < nPacketSize*2)
			nPacketSize = togo;
		ASSERT( nPacketSize );
		togo -= nPacketSize;
		uint64 statpos = StartOffset;
		Packet* packet;
		if (StartOffset > 0xFFFFFFFF || EndOffset > 0xFFFFFFFF){
			packet = new Packet(OP_COMPRESSEDPART_I64,nPacketSize+28,OP_EMULEPROT,bFromPF);
			md4cpy(&packet->pBuffer[0],FileID);
			PokeUInt64(&packet->pBuffer[16], statpos);
			PokeUInt32(&packet->pBuffer[24], newsize);
			memfile.Read(&packet->pBuffer[28],nPacketSize);
		}
		else{
			packet = new Packet(OP_COMPRESSEDPART,nPacketSize+24,OP_EMULEPROT,bFromPF);
			md4cpy(&packet->pBuffer[0],FileID);
			PokeUInt32(&packet->pBuffer[16], (uint32)statpos);
			PokeUInt32(&packet->pBuffer[20], newsize);
			memfile.Read(&packet->pBuffer[24],nPacketSize);
		}

		if (thePrefs.GetDebugClientTCPLevel() > 0){
			DebugSend("OP__CompressedPart", m_owner, FileID);
			Debug(_T("  Start=%I64u  BlockSize=%u  Size=%u\n"), statpos, newsize, nPacketSize);
		}
        // approximate payload size
        uint32 payloadSize = nPacketSize*oldSize/newsize;

        if(togo == 0 && totalPayloadSize+payloadSize < oldSize) {
            payloadSize = oldSize-totalPayloadSize;
        }
        totalPayloadSize += payloadSize;

        // put packet directly on socket
		theStats.AddUpDataOverheadFileRequest(24);
        SendPacket(packet, payloadSize);
	}
	delete[] output;
}
