/***************************************************************************
                          cdcproto.cpp  -  description
                             -------------------
    begin                : Sun Dec 15 2002
    copyright            : (C) 2002-2005 by Mathias Küster
    email                : mathen@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "cdcproto.h"

#include <stdio.h>
#include <string.h>

#ifndef WIN32
#include <unistd.h>
#else
#include <wtypes.h>
#include <winbase.h>
#endif

#include <stdlib.h>

#include "dcobject.h"

// needed for remote encoding setting
#include "cconfig.h"

#include <algorithm>

CDCProto::CDCProto( CString remote )
{
	CString from = "UTF-8";
	
	if ( CConfig::Instance() != 0 )
	{
		from = CConfig::Instance()->GetLocalEncoding();
		if ( remote.IsEmpty() )
		{
			remote = CConfig::Instance()->GetRemoteEncoding();
		}
	}
	
	m_pLocalToRemote = new CIconv( from, remote );
	m_pLocalToUTF8 = new CIconv( from, "UTF-8" );
}

CDCProto::~CDCProto()
{
	delete m_pLocalToRemote;
	m_pLocalToRemote = 0;
	delete m_pLocalToUTF8;
	m_pLocalToUTF8 = 0;
}

/** */
int CDCProto::SendChat( CString sNick, CString s )
{
	DCProtoMutex.Lock();

	s = s.Replace( "$", "&#36;");
	s = s.Replace( "|", "&#124;");

	CString t = '<';
	t += m_pLocalToRemote->encode(sNick);
	t += "> ";
	t += m_pLocalToRemote->encode(s);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendVersion()
{
	DCProtoMutex.Lock();

	const CString t = "$Version 1,0091|";
	/* taken out to remove the confusion in some scripts reporting a version # of 0
	Hopefully will prevent users from getting kicked for Hacked Clients.   SMiLeaf
	t += sVersion; */

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendMyInfo( CMessageMyInfo * myinfo )
{
	DCProtoMutex.Lock();

	char c;

	CString t = "$MyINFO $ALL ";
	t += m_pLocalToRemote->encode(myinfo->m_sNick);
	t += ' ';
	t += m_pLocalToRemote->encode(myinfo->m_sComment);
	t += "$ $"; // ???
	t += m_pLocalToRemote->encode(myinfo->m_sUserSpeed);

	if ( myinfo->m_eAwayMode == euamAWAY )
	{
		c = emsfAway;
	}
	else
	{
		c = emsfNormal;
	}
	
	if ( myinfo->m_bServerFlag )
	{
		c = c | emsfServer;
	}
	
	if ( myinfo->m_bFireballFlag )
	{
		c = c | emsfFireball;
	}
	
	if ( myinfo->m_bTLSFlag )
	{
		c = c | emsfTLS;
	}

	t += c;
	t += '$';
	t += m_pLocalToRemote->encode(myinfo->m_sEMail);
	t += '$';
	t += CString::number(myinfo->m_nShared);
	t += '$'; // share
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendKey( CString s )
{
	DCProtoMutex.Lock();

	CString t = "$Key ";
	t += s;
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendValidateNick( CString sNick )
{
	DCProtoMutex.Lock();

	CString t = "$ValidateNick ";
	t += m_pLocalToRemote->encode(sNick);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendMyNick( CString sNick, const CString & ref )
{
	DCProtoMutex.Lock();

	CString t = "$MyNick ";
	t += m_pLocalToRemote->encode(sNick);

	t += "|$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCGUI";
	t += DCLIB_VERSION_STRING;
	t += "ABCABC Ref=";
	t += ref;
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendPrivateMessage( CString sNick, CString sTo, CString sMsg, CString sFromNick )
{
	DCProtoMutex.Lock();

	CString t = "$To: ";
	t += m_pLocalToRemote->encode(sTo);
	t += " From: ";
	t += m_pLocalToRemote->encode(sNick);
	t += " $<";
	if ( sFromNick.IsEmpty() )
		t += m_pLocalToRemote->encode(sNick);
	else
		t += m_pLocalToRemote->encode(sFromNick);

	sMsg = sMsg.Replace( "$", "&#36;");
	sMsg = sMsg.Replace( "|", "&#124;");
	sMsg = m_pLocalToRemote->encode(sMsg);

	t += "> ";
	t += sMsg;
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::RequestNickList()
{
	DCProtoMutex.Lock();

	const CString t = "$GetNickList|";

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendDirection( eDirection Direction, int level )
{
	DCProtoMutex.Lock();

	CString t = "$Direction ";

	if (Direction == edUPLOAD)
		t += "Upload";
	else if (Direction == edDOWNLOAD)
		t += "Download";

	t += ' ';
	t += CString::number(level);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendGet( CString file, ulonglong pos, ulonglong size )
{
	DCProtoMutex.Lock();

	CString t = "$Get ";
	t += m_pLocalToRemote->encode(file);
	t += '$';
	t += CString::number(pos);

	if ( size != 0 )
	{
		t += '$';
		t += CString::number(size);
	}

	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendGetZBlock( CString file, ulonglong pos, ulonglong size )
{
	DCProtoMutex.Lock();

	CString t = "$GetZBlock ";
	t += CString::number(pos);
	t += ' ';
	if (size == (ulonglong)-1)
	{
		t += "-1 ";
	}
	else
	{
		t += CString::number(size);
		t += ' ';
	}
	t += m_pLocalToRemote->encode(file);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** Send "$UGetBlock startPos numBytes fileNameInUTF8|" */
int CDCProto::SendUGetBlock( CString file, ulonglong pos, ulonglong size )
{
	DCProtoMutex.Lock();
	
	CString t = "$UGetBlock ";
	t += CString::number(pos);
	t += ' ';
	if (size == (ulonglong)-1)
	{
		t += "-1 ";
	}
	else
	{
		t += CString::number(size);
		t += ' ';
	}
	t += m_pLocalToUTF8->encode(file);
	t += '|';
	
	const int err = Write((const unsigned char*)t.Data(),t.Length());
	
	DCProtoMutex.UnLock();
	
	return err;
}

/** Send "$UGetZBlock startPos numBytes fileNameInUTF8|" */
int CDCProto::SendUGetZBlock( CString file, ulonglong pos, ulonglong size )
{
	DCProtoMutex.Lock();
	
	CString t = "$UGetZBlock ";
	t += CString::number(pos);
	t += ' ';
	if (size == (ulonglong)-1)
	{
		t += "-1 ";
	}
	else
	{
		t += CString::number(size);
		t += ' ';
	}
	t += m_pLocalToUTF8->encode(file);
	t += '|';
	
	const int err = Write((const unsigned char*)t.Data(),t.Length());
	
	DCProtoMutex.UnLock();
	
	return err;
}

/** */
int CDCProto::SendRevConnectToMe( CString sNick, CString sDstNick )
{
	DCProtoMutex.Lock();

	CString t = "$RevConnectToMe ";
	t += m_pLocalToRemote->encode(sNick);
	t += ' ';
	t += m_pLocalToRemote->encode(sDstNick);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendSend()
{
	DCProtoMutex.Lock();

	const CString t = "$Send|";

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendError( CString message )
{
	DCProtoMutex.Lock();

	CString t = "$Error ";
	t += m_pLocalToRemote->encode(message);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendFileLength( ulonglong len )
{
	DCProtoMutex.Lock();

	CString t = "$FileLength ";
	t += CString::number(len);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendListLen( ulonglong len )
{
	DCProtoMutex.Lock();

	CString t = "$ListLen ";
	t += CString::number(len);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendGetListLen()
{
	DCProtoMutex.Lock();

	const CString t = "$GetListLen|";

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendConnectToMe( CString sDstNick, CString host, bool crypto )
{
	DCProtoMutex.Lock();

	CString t = "$ConnectToMe ";
	t += m_pLocalToRemote->encode(sDstNick);
	t += ' ';
	t += host;
	
	if ( crypto )
	{
		t += 'S';
	}
	
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendCanceled()
{
	DCProtoMutex.Lock();

	const CString t = "$Canceled|";

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** send string */
int CDCProto::SendString( CString message, bool encode )
{
	DCProtoMutex.Lock();

	int err;
	
	if ( encode )
	{
		CString enc = m_pLocalToRemote->encode(message);
		err = Write((const unsigned char*)enc.Data(),enc.Length());
	}
	else
	{
		err = Write((const unsigned char*)message.Data(),message.Length());
	}

	DCProtoMutex.UnLock();

	return err;

}

/** send password */
int CDCProto::SendPass( CString pass )
{
	DCProtoMutex.Lock();

	CString t = "$MyPass ";
	t += pass;
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** operator kick */
int CDCProto::SendKick( CString nick )
{
	DCProtoMutex.Lock();

	CString t = "$Kick ";
	t += m_pLocalToRemote->encode(nick);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** operator force move a user */
int CDCProto::SendOpForceMove( CString nick, CString host, CString message )
{
	DCProtoMutex.Lock();

	CString t = "$OpForceMove $Who:";
	t += m_pLocalToRemote->encode(nick);
	t += "$Where:";
	t += host;
	t += "$Msg:";
	t += m_pLocalToRemote->encode(message);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendMaxedOut()
{
	DCProtoMutex.Lock();

	const CString t = "$MaxedOut|";

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendGetInfo( CString sNick, CString sMyNick )
{
	DCProtoMutex.Lock();

	CString t = "$GetINFO ";
	t += m_pLocalToRemote->encode(sNick);
	t += ' ';
	t += m_pLocalToRemote->encode(sMyNick);
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendSupports( std::vector<CString> & supported, bool shuffle )
{
	DCProtoMutex.Lock();

	CString t = "$Supports ";
	
	if ( shuffle )
	{
		random_shuffle( supported.begin(), supported.end() );
	}
	
	for ( std::vector<CString>::const_iterator it = supported.begin(); it != supported.end(); ++it )
	{
		t += *it;
		t += ' ';
	}
	
	t += '|';

	const int err = Write((const unsigned char*)t.Data(),t.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** */
int CDCProto::SendSearch( CMessageSearchFile * msg )
{
	DCProtoMutex.Lock();

	CString s;

	if ( msg->m_bLocal  )
	{
		s = "$Search Hub:";
	}
	else if ( msg->m_bMulti )
	{
		s = "$MultiSearch ";
	}
	else
	{
		s = "$Search ";
	}
	
	s += m_pLocalToRemote->encode(msg->m_sSource);
	s += " ";

	if ( msg->m_bSizeLimit == false )
		s += "F?";
	else
		s += "T?";

	if ( msg->m_eSizeType == esstATMOST )
		s += "T?";
	else
		s += "F?";

	s += CString::number(msg->m_nSize);
	s += '?';
	
	s += CString::number(msg->m_eFileType);
	s += '?';
	if ( msg->m_eFileType == eftHASH )
	{
		s += "TTH:";
		/* a Base32 TTH does not need encoding or escaping */
		s += msg->m_sString;
	}
	else
	{
		s += m_pLocalToRemote->encode(msg->m_sString.Replace("$","&#36;").Replace("|","&#124;").Replace(' ',"$"));
	}
	s += '|';

	const int err = Write((const unsigned char*)s.Data(),s.Length());

	DCProtoMutex.UnLock();

	return err;
}

/** $Sending numBytes| */
int CDCProto::SendSending( ulonglong length )
{
	DCProtoMutex.Lock();
	
	CString t = "$Sending ";
	t += CString::number(length);
	t += '|';
	
	const int err = Write((const unsigned char*)t.Data(),t.Length());
	
	DCProtoMutex.UnLock();
	
	return err;
}

/** $ADCGET file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 0 1154 ZL1| */
int CDCProto::SendADCGet( eADCType type, CString tth, ulonglong pos, long long size, bool zlib, CString file )
{
	DCProtoMutex.Lock();
	
	CString t = "$ADCGET ";
	
	switch ( type )
	{
		case eAdcFile:
			t += "file ";
			break;
		case eAdcTTHL:
			t += "tthl ";
			break;
		case eAdcList:
			t += "list ";
			break;
		default:
			DCProtoMutex.UnLock();
			return -1;
	}
	
	if ( tth.IsEmpty() )
	{
		// escape spaces
		file = file.Replace( " ", "\\ " );
		t += m_pLocalToUTF8->encode(file);
	}
	else
	{
		t += "TTH/";
		t += tth;
	}
	t += ' ';
	t += CString::number(pos);
	t += ' ';
	t += CString::number(size);
	if (zlib)
	{
		t += " ZL1";
	}
	t += '|';
	
	//printf("Sending ADCGet: %s\n", t.Data());
	
	const int err = Write((const unsigned char*)t.Data(),t.Length());
	
	DCProtoMutex.UnLock();
	
	return err;
}

/** $ADCSND file TTH/PPUROLR2WSYTGPLCM3KV4V6LJC36SCTFQJFDJKA 0 1154 ZL1|*/
int CDCProto::SendADCSnd( eADCType type, CString tth, ulonglong pos, long long size, bool zlib, CString file )
{
	DCProtoMutex.Lock();
	
	CString t = "$ADCSND ";
	
	switch ( type )
	{
		case eAdcFile:
			t += "file ";
			break;
		case eAdcTTHL:
			t += "tthl ";
			break;
		case eAdcList:
			t += "list ";
			break;
		default:
			DCProtoMutex.UnLock();
			return -1;
	}
	
	if ( tth.IsEmpty() )
	{
		// escape spaces
		file = file.Replace( " ", "\\ " );
		t += m_pLocalToUTF8->encode(file);
	}
	else
	{
		t += "TTH/";
		t += tth;
	}
	t += ' ';
	t += CString::number(pos);
	t += ' ';
	t += CString::number(size);
	if (zlib)
	{
		t += " ZL1";
	}
	t += '|';
	
	//printf("Sending ADCSnd: %s\n", t.Data());
	
	const int err = Write((const unsigned char*)t.Data(),t.Length());
	
	DCProtoMutex.UnLock();
	
	return err;
}
