//
// This file is part of the aMule Project.
//
// Copyright (c) 2008-2011 Dévai Tamás ( gonosztopi@amule.org )
// Copyright (c) 2008-2011 aMule Team ( admin@amule.org / http://www.amule.org )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA
//

#include "Print.h"
#include <wx/datetime.h>
#include <tags/FileTags.h>
#include <tags/ServerTags.h>
#include <tags/TagTypes.h>
#include <protocol/ed2k/Client2Server/UDP.h>
#include "../../OtherFunctions.h"
#include "../../Friend.h"			// Needed for FF_NAME
#include "../../KnownFile.h"		// Needed for PR_*
#include "../../NetworkFunctions.h"	// Needed for Uint32toStringIP

#include <cctype>
#include <map>
#include <iostream>

SDMODE g_stringDecodeMode;

wxString MakePrintableString(const wxString& s)
{
	wxString str = s;
	unsigned c = 0;
	for (unsigned i = 0; i < str.length(); i++) {
		c |= (wxChar) str[i];
	}

	if (c <= 0xff && GetStringsMode() != SD_NONE) {
		wxCharBuffer buf = str.To8BitData();
		wxString test = UTF82unicode(buf.data());
		if (!test.empty()) {
			str = test;
			c = 0;
			for (unsigned i = 0; i < str.length(); i++) {
				c |= (wxChar) str[i];
			}
		}
	}

	wxString retval = wxT("\"");

	if (GetStringsMode() == SD_DISPLAY) {
		retval += str;
	} else if (GetStringsMode() == SD_UTF8) {
		str = wxString::From8BitData((const char *)str.utf8_str());
		c = 0xff;
	}

	if (GetStringsMode() != SD_DISPLAY) {
		for (unsigned i = 0; i < str.length(); i++) {
			if (GetStringsMode() == SD_NONE ? ((unsigned)str[i] >= ' ' && (unsigned)str[i] <= 0x7f) : std::isprint(str[i])) {
				retval += str[i];
			} else if (c <= 0xff) {
				retval += wxString::Format(wxT("\\x%02x"), str[i]);
			} else {
				retval += wxString::Format(wxT("\\u%04x"), str[i]);
			}
		}
	}

	retval += wxT("\"");
	return retval;
}

std::ostream& operator<<(std::ostream& x, const CTimeT& y)
{
	if ((time_t)y != 0) {
		wxDateTime dt((time_t)y);
		return x << (time_t)y << " (" << dt.Format(wxDefaultDateTimeFormat, wxDateTime::UTC) << " UTC)";
	} else {
		return x << "0 (Never)";
	}
}

std::ostream& operator<<(std::ostream& x, const CKadIP& ip)
{
	return x << hex(ip) << " (" << Uint32toStringIP(wxUINT32_SWAP_ALWAYS(ip)) << ')';
}

std::ostream& operator<<(std::ostream& x, const CeD2kIP& ip)
{
	return x << hex(ip) << " (" << Uint32toStringIP(ip) << ')';
}

static inline wxString TagNameString(const wxString& name)
{
	if (name.length() == 1) {
		return wxString::Format(wxT("\"\\x%02x\""), name[0]);
	} else if (name.length() > 1) {
		if (name[0] == FT_GAPSTART || name[0] == FT_GAPEND) {
			return wxString::Format(wxT("\"\\x%02x"), name[0]) + name.substr(1) + wxT("\"");
		}
	}
	return wxT("\"") + name + wxT("\"");
}

#define TEST_VALUE(VALUE)	if (value == VALUE) return #VALUE; else

const char* DecodeTagNameID(uint8_t value)
{
	TEST_VALUE(FT_FILENAME)
	TEST_VALUE(FT_FILESIZE)
	TEST_VALUE(FT_FILESIZE_HI)
	TEST_VALUE(FT_FILETYPE)
	TEST_VALUE(FT_FILEFORMAT)
	TEST_VALUE(FT_LASTSEENCOMPLETE)
	TEST_VALUE(FT_TRANSFERRED)
	TEST_VALUE(FT_GAPSTART)
	TEST_VALUE(FT_GAPEND)
	TEST_VALUE(FT_PARTFILENAME)
	TEST_VALUE(FT_OLDDLPRIORITY)
	TEST_VALUE(FT_STATUS)
	TEST_VALUE(FT_SOURCES)
	TEST_VALUE(FT_PERMISSIONS)
	TEST_VALUE(FT_OLDULPRIORITY)
	TEST_VALUE(FT_DLPRIORITY)
	TEST_VALUE(FT_ULPRIORITY)
	TEST_VALUE(FT_KADLASTPUBLISHKEY)
	TEST_VALUE(FT_KADLASTPUBLISHSRC)
	TEST_VALUE(FT_FLAGS)
	TEST_VALUE(FT_DL_ACTIVE_TIME)
	TEST_VALUE(FT_CORRUPTEDPARTS)
	TEST_VALUE(FT_DL_PREVIEW)
	TEST_VALUE(FT_KADLASTPUBLISHNOTES)
	TEST_VALUE(FT_AICH_HASH)
	TEST_VALUE(FT_COMPLETE_SOURCES)
	TEST_VALUE(FT_PUBLISHINFO)
	TEST_VALUE(FT_ATTRANSFERRED)
	TEST_VALUE(FT_ATREQUESTED)
	TEST_VALUE(FT_ATACCEPTED)
	TEST_VALUE(FT_CATEGORY)
	TEST_VALUE(FT_ATTRANSFERREDHI)
	TEST_VALUE(FT_MEDIA_ARTIST)
	TEST_VALUE(FT_MEDIA_ALBUM)
	TEST_VALUE(FT_MEDIA_TITLE)
	TEST_VALUE(FT_MEDIA_LENGTH)
	TEST_VALUE(FT_MEDIA_BITRATE)
	TEST_VALUE(FT_MEDIA_CODEC)
	TEST_VALUE(FT_FILERATING)
	return "??";
}

const char* DecodeTagName(const wxString& value)
{
	TEST_VALUE(TAG_FILENAME)
	TEST_VALUE(TAG_FILESIZE)
	TEST_VALUE(TAG_FILESIZE_HI)
	TEST_VALUE(TAG_FILETYPE)
	TEST_VALUE(TAG_FILEFORMAT)
	TEST_VALUE(TAG_COLLECTION)
	TEST_VALUE(TAG_PART_PATH)
	TEST_VALUE(TAG_PART_HASH)
	TEST_VALUE(TAG_COPIED)
	TEST_VALUE(TAG_GAP_START)
	TEST_VALUE(TAG_GAP_END)
	TEST_VALUE(TAG_DESCRIPTION)
	TEST_VALUE(TAG_PING)
	TEST_VALUE(TAG_FAIL)
	TEST_VALUE(TAG_PREFERENCE)
	TEST_VALUE(TAG_PORT)
	TEST_VALUE(TAG_IP_ADDRESS)
	TEST_VALUE(TAG_VERSION)
	TEST_VALUE(TAG_TEMPFILE)
	TEST_VALUE(TAG_PRIORITY)
	TEST_VALUE(TAG_STATUS)
	TEST_VALUE(TAG_SOURCES)
	TEST_VALUE(TAG_AVAILABILITY)
	TEST_VALUE(TAG_PERMISSIONS)
	TEST_VALUE(TAG_QTIME)
	TEST_VALUE(TAG_PARTS)
	TEST_VALUE(TAG_PUBLISHINFO)
	TEST_VALUE(TAG_MEDIA_ARTIST)
	TEST_VALUE(TAG_MEDIA_ALBUM)
	TEST_VALUE(TAG_MEDIA_TITLE)
	TEST_VALUE(TAG_MEDIA_LENGTH)
	TEST_VALUE(TAG_MEDIA_BITRATE)
	TEST_VALUE(TAG_MEDIA_CODEC)
	TEST_VALUE(TAG_ENCRYPTION)
	TEST_VALUE(TAG_FILERATING)
	TEST_VALUE(TAG_BUDDYHASH)
	TEST_VALUE(TAG_CLIENTLOWID)
	TEST_VALUE(TAG_SERVERPORT)
	TEST_VALUE(TAG_SERVERIP)
	TEST_VALUE(TAG_SOURCEUPORT)
	TEST_VALUE(TAG_SOURCEPORT)
	TEST_VALUE(TAG_SOURCEIP)
	TEST_VALUE(TAG_SOURCETYPE)
	return "??";
}

const char* DecodeTagType(uint8_t value)
{
	TEST_VALUE(TAGTYPE_HASH16)
	TEST_VALUE(TAGTYPE_STRING)
	TEST_VALUE(TAGTYPE_UINT32)
	TEST_VALUE(TAGTYPE_FLOAT32)
	TEST_VALUE(TAGTYPE_BOOL)
	TEST_VALUE(TAGTYPE_BOOLARRAY)
	TEST_VALUE(TAGTYPE_BLOB)
	TEST_VALUE(TAGTYPE_UINT16)
	TEST_VALUE(TAGTYPE_UINT8)
	TEST_VALUE(TAGTYPE_BSOB)
	TEST_VALUE(TAGTYPE_UINT64)
	TEST_VALUE(TAGTYPE_STR1)
	TEST_VALUE(TAGTYPE_STR2)
	TEST_VALUE(TAGTYPE_STR3)
	TEST_VALUE(TAGTYPE_STR4)
	TEST_VALUE(TAGTYPE_STR5)
	TEST_VALUE(TAGTYPE_STR6)
	TEST_VALUE(TAGTYPE_STR7)
	TEST_VALUE(TAGTYPE_STR8)
	TEST_VALUE(TAGTYPE_STR9)
	TEST_VALUE(TAGTYPE_STR10)
	TEST_VALUE(TAGTYPE_STR11)
	TEST_VALUE(TAGTYPE_STR12)
	TEST_VALUE(TAGTYPE_STR13)
	TEST_VALUE(TAGTYPE_STR14)
	TEST_VALUE(TAGTYPE_STR15)
	TEST_VALUE(TAGTYPE_STR16)
	TEST_VALUE(TAGTYPE_STR17)
	TEST_VALUE(TAGTYPE_STR18)
	TEST_VALUE(TAGTYPE_STR19)
	TEST_VALUE(TAGTYPE_STR20)
	TEST_VALUE(TAGTYPE_STR21)
	TEST_VALUE(TAGTYPE_STR22)
	return "??";
}

const char* DecodeServerTagNameID(uint8_t value)
{
	TEST_VALUE(ST_SERVERNAME)
	TEST_VALUE(ST_DESCRIPTION)
	TEST_VALUE(ST_PING)
	TEST_VALUE(ST_FAIL)
	TEST_VALUE(ST_PREFERENCE)
	TEST_VALUE(ST_DYNIP)
	TEST_VALUE(ST_LASTPING_DEPRECATED)
	TEST_VALUE(ST_MAXUSERS)
	TEST_VALUE(ST_SOFTFILES)
	TEST_VALUE(ST_HARDFILES)
	TEST_VALUE(ST_LASTPING)
	TEST_VALUE(ST_VERSION)
	TEST_VALUE(ST_UDPFLAGS)
	TEST_VALUE(ST_AUXPORTSLIST)
	TEST_VALUE(ST_LOWIDUSERS)
	TEST_VALUE(ST_UDPKEY)
	TEST_VALUE(ST_UDPKEYIP)
	TEST_VALUE(ST_TCPPORTOBFUSCATION)
	TEST_VALUE(ST_UDPPORTOBFUSCATION)
	return "??";
}

const char* DecodeFriendTagNameID(uint8_t value)
{
	TEST_VALUE(FF_NAME)
	return "??";
}

typedef std::map<uint32_t, const char*> FlagMap;

class CFlagDecoder
{
      public:
	void AddFlag(uint32_t bit, const char* name)
	{
		m_flags[bit] = name;
	}

	wxString DecodeFlags(uint32_t flags)
	{
		uint32_t cur_flag = 1;
		wxString result;
		while (flags) {
			if (flags & 1) {
				if (!result.empty()) {
					result += wxT(" | ");
				}
				FlagMap::const_iterator it = m_flags.find(cur_flag);
				if (it != m_flags.end()) {
					result += wxString::FromAscii(it->second);
				} else {
					result += wxString::Format(wxT("%#x"), cur_flag);
				}
			}
			cur_flag <<= 1;
			flags >>= 1;
		}
		return result;
	}

      private:
	FlagMap	m_flags;
};

#define ADD_DECODER_FLAG(decoder, FLAG)	decoder.AddFlag(FLAG, #FLAG)

std::ostream& operator<<(std::ostream& out, const CTag& tag)
{
	out << "{ ";
	if (tag.GetName().empty()) {
		out << hex(tag.GetNameID()) << ' ' << DecodeTagNameID(tag.GetNameID());
	} else {
		out << TagNameString(tag.GetName()) << ' ';
		bool name_decoded = false;
		if (tag.IsInt() && tag.GetName().length() > 1) {
			wxCharBuffer ascii_name = tag.GetName().ToAscii();
			char gap_mark = ascii_name ? ascii_name[(size_t)0] : 0;
			if (gap_mark == FT_GAPSTART || gap_mark == FT_GAPEND) {
				unsigned long gapkey;
				if (tag.GetName().Mid(1).ToULong(&gapkey)) {
					out << DecodeTagNameID(gap_mark) << wxString::Format(wxT("[%lu]"), gapkey);
					name_decoded = true;
				}
			}
		}
		if (!name_decoded) {
			out << DecodeTagName(tag.GetName());
		}
	}
	out << ", " << (unsigned)tag.GetType() << ' ' << DecodeTagType(tag.GetType()) << ", ";
	if (tag.IsInt()) {
		if (tag.GetName() == TAG_SOURCEIP) {
			out << CKadIP(tag.GetInt());
		} else if (tag.GetName() == TAG_SERVERIP) {
			out << CeD2kIP(tag.GetInt());
		} else if (tag.GetName() == TAG_ENCRYPTION) {
			uint8_t enc = tag.GetInt();
			out << hex(enc);
			if (enc) {
				CFlagDecoder decoder;
				decoder.AddFlag(0x01, "CryptSupported");
				decoder.AddFlag(0x02, "CryptRequested");
				decoder.AddFlag(0x04, "CryptRequired");
				decoder.AddFlag(0x08, "SupportsDirectCallback");
				out << " (" << decoder.DecodeFlags(enc) << ')';
			}
		} else if (tag.GetNameID() == FT_LASTSEENCOMPLETE
			   || tag.GetNameID() == FT_KADLASTPUBLISHSRC
			   || tag.GetNameID() == FT_KADLASTPUBLISHNOTES
			   || tag.GetNameID() == FT_KADLASTPUBLISHKEY
			   ) {
			out << CTimeT(tag.GetInt());
		} else if (tag.GetName() == TAG_FILESIZE
			   || tag.GetNameID() == FT_FILESIZE
			   || tag.GetNameID() == FT_TRANSFERRED
			   || tag.GetNameID() == FT_ATTRANSFERRED
			   ) {
			out << tag.GetInt() << " (" << CastItoXBytes(tag.GetInt()) << ')';
		} else if (tag.GetNameID() == FT_ULPRIORITY
			   || tag.GetNameID() == FT_DLPRIORITY
			   || tag.GetNameID() == FT_OLDULPRIORITY
			   || tag.GetNameID() == FT_OLDDLPRIORITY
			   ) {
			out << tag.GetInt() << ' ';
			switch (tag.GetInt()) {
				case PR_VERYLOW:	out << "PR_VERYLOW"; break;
				case PR_LOW:		out << "PR_LOW"; break;
				case PR_NORMAL:		out << "PR_NORMAL"; break;
				case PR_HIGH:		out << "PR_HIGH"; break;
				case PR_VERYHIGH:	out << "PR_VERYHIGH"; break;
				case PR_AUTO:		out << "PR_AUTO"; break;
				case PR_POWERSHARE:	out << "PR_POWERSHARE"; break;
			}
		} else {
			out << tag.GetInt();
		}
	} else if (tag.IsStr()) {
		out << MakePrintableString(tag.GetStr());
	} else if (tag.IsFloat()) {
		out << tag.GetFloat();
	} else if (tag.IsHash()) {
		out << tag.GetHash();
	} else if (tag.IsBlob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBlobSize());
	} else if (tag.IsBsob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBsobSize());
	} else {
		out << "(...)";
	}
	out << " }";
	return out;
}

std::ostream& operator<<(std::ostream& out, const CServerTag& tag)
{
	out << "{ ";
	if (tag.GetName().empty()) {
		out << hex(tag.GetNameID()) << ' ' << DecodeServerTagNameID(tag.GetNameID());
	} else {
		out << TagNameString(tag.GetName());
	}
	out << ", " << (unsigned)tag.GetType() << ' ' << DecodeTagType(tag.GetType()) << ", ";
	if (tag.IsInt()) {
		if (tag.GetNameID() == ST_DYNIP || tag.GetNameID() == ST_UDPKEYIP) {
			out << CeD2kIP(tag.GetInt());
		} else if (tag.GetNameID() == ST_UDPKEY) {
			out << hex((uint32_t)tag.GetInt());
		} else if (tag.GetNameID() == ST_UDPFLAGS) {
			uint32_t flags = tag.GetInt();
			out << hex(flags);
			if (flags) {
				CFlagDecoder decoder;
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_EXT_GETSOURCES);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_EXT_GETFILES);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_NEWTAGS);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_UNICODE);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_EXT_GETSOURCES2);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_LARGEFILES);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_UDPOBFUSCATION);
				ADD_DECODER_FLAG(decoder, SRV_UDPFLG_TCPOBFUSCATION);
				out << " (" << decoder.DecodeFlags(flags) << ')';
			}
		} else if (tag.GetNameID() == ST_PREFERENCE) {
			out << tag.GetInt() << ' ';
			switch (tag.GetInt()) {
				case 0: out << "SRV_PR_NORMAL"; break;
				case 1: out << "SRV_PR_HIGH"; break;
				case 2: out << "SRV_PR_LOW"; break;
				default: out << "??";
			}
		} else {
			out << tag.GetInt();
		}
	} else if (tag.IsStr()) {
		out << MakePrintableString(tag.GetStr());
	} else if (tag.IsFloat()) {
		out << tag.GetFloat();
	} else if (tag.IsHash()) {
		out << tag.GetHash();
	} else if (tag.IsBlob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBlobSize());
	} else if (tag.IsBsob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBsobSize());
	} else {
		out << "(...)";
	}
	out << " }";
	return out;
}

std::ostream& operator<<(std::ostream& out, const CFriendTag& tag)
{
	out << "{ ";
	if (tag.GetName().empty()) {
		out << hex(tag.GetNameID()) << ' ' << DecodeFriendTagNameID(tag.GetNameID());
	} else {
		out << TagNameString(tag.GetName());
	}
	out << ", " << (unsigned)tag.GetType() << ' ' << DecodeTagType(tag.GetType()) << ", ";
	if (tag.IsInt()) {
		out << tag.GetInt();
	} else if (tag.IsStr()) {
		out << MakePrintableString(tag.GetStr());
	} else if (tag.IsFloat()) {
		out << tag.GetFloat();
	} else if (tag.IsHash()) {
		out << tag.GetHash();
	} else if (tag.IsBlob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBlobSize());
	} else if (tag.IsBsob()) {
		out << wxString::Format(wxT("(size = %u)"), tag.GetBsobSize());
	} else {
		out << "(...)";
	}
	out << " }";
	return out;
}
