/*
 * Created on 7 Jun 2007
 * Created by Allan Crooks
 * Copyright (C) Azureus Software, Inc, All Rights Reserved.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.aelitis.azureus.core.peermanager.utils;

import java.util.ArrayList;
import java.util.HashMap;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.Constants;

public class BTPeerIDByteDecoderDefinitions {

	// Az style two byte code identifiers to real client name. 
	private static HashMap az_style_code_map = new HashMap();
	private static HashMap az_client_version_map = new HashMap();
	
	// Shadow's style one byte code identifiers to real client name.
	private static HashMap shadow_style_code_map = new HashMap();
	private static HashMap shadow_client_version_map = new HashMap();
	
	// Mainline's new style uses one byte code identifiers too.
	private static HashMap mainline_style_code_map = new HashMap();
	
	/**
	 * List of objects which describes clients with their own custom
	 * naming scheme.
	 */ 
	private static ArrayList custom_style_client_list = new ArrayList();
	
	// Version number formats.
	static String VER_AZ_THREE_DIGITS = "1.2.3";
	static String VER_AZ_THREE_DIGITS_PLUS_MNEMONIC = "1.2.3 [4]";
	static String VER_AZ_ONE_MAJ_TWO_MIN_PLUS_MNEMONIC = "1.23 [4]";
	static String VER_AZ_FOUR_DIGITS = "1.2.3.4";
	static String VER_AZ_TWO_MAJ_TWO_MIN = "12.34";
	static String VER_AZ_SKIP_FIRST_ONE_MAJ_TWO_MIN = "2.34";
	static String VER_AZ_KTORRENT_STYLE = "1.2.3=[RD].4";
	static String VER_AZ_TRANSMISSION_STYLE = "transmission";
	static String VER_AZ_LAST_THREE_DIGITS = "2.3.4";
	static String VER_AZ_THREE_ALPHANUMERIC_DIGITS = "2.33.4"; // So 'B' = 11, for example, like Shadow's style of numbering.
	
	// These are used to describe how the version number is extracted from the
	// peer ID as well as how that version number is formatted. 
	static String VER_BLOCK = "abcde"; // Is given as a block in the peer ID, we show the same block
	static String VER_DOTTED_BLOCK = "a.b.c.d.e"; // Is given as a dotted block in the peer ID, we show the block in the same dotted format.
	static String VER_BYTE_BLOCK_DOTTED_CHAR = "abcde -> a.b.c.d.e"; // Is given a block in the peer ID, but should be displayed in a dotted format.
	static String VER_TWOBYTE_BLOCK_DOTTED_CHAR = "abcde -> ab.cd"; // Is given a block in the peer ID, but should be displayed in a dotted format.
	static String VER_BITS_ON_WHEELS = "BOW-STYLE";
	static String VER_TWO_BYTE_THREE_PART = "ab -> a . b/10 . b%10";
	static String NO_VERSION = "NO_VERSION";
	static String VER_BYTE_UM_STYLE = "abcd -> a.b.cd"; 
	
	// Used to register client information.
	private static void addAzStyle(String id, String client) {
		addAzStyle(id, client, VER_AZ_FOUR_DIGITS);
	}
	
	private static void addAzStyle(String id, String client, String version_style) {
		if (id.length() != 2) {throw new RuntimeException("not two chars long - " + id);}
		az_style_code_map.put(id, client);
		az_client_version_map.put(client, version_style);
	}
	
	private static void addShadowStyle(char id, String client) {
		addShadowStyle(id, client, VER_AZ_THREE_DIGITS);
	}
	
	private static void addShadowStyle(char id, String client, String version_style) {
		shadow_style_code_map.put("" + id, client);
		shadow_client_version_map.put(client, version_style);
	}
	
	private static void addMainlineStyle(char id, String client) {
		mainline_style_code_map.put("" + id, client);
	}
	
	private static ClientData addSimpleClient(String client_name, String identifier) {
		return addSimpleClient(client_name, identifier, 0);
	}
	
	private static ClientData addSimpleClient(String client_name, String identifier, int position) {
		ClientData result = new ClientData(client_name, identifier, position);
		custom_style_client_list.add(result);
		return result;
	}
	
	private static void addVersionedClient(ClientData client, String version_type, int length) {
		addVersionedClient(client, version_type, length, null);
	}
	
	private static void addVersionedClient(ClientData client, String version_type, int length, String format) {
		addVersionedClient(client, version_type, length, format, client.simple_string.length() + client.simple_string_pos);
	}

	private static void addVersionedClient(ClientData client, String version_type, int length, int version_pos) {
		addVersionedClient(client, version_type, length, null, version_pos);
	}
	
	private static void addVersionedClient(ClientData client, String version_type, int length, String format, int version_pos) {
		client.version_data = new VersionNumberData(version_type, length, format, version_pos);
	}
	
	public static String getAzStyleClientName(String peer_id) {
		return (String)az_style_code_map.get(peer_id.substring(1, 3));
	}
	
	public static String getShadowStyleClientName(String peer_id) {
		return (String)shadow_style_code_map.get(peer_id.substring(0, 1));
	}

	public static String getMainlineStyleClientName(String peer_id) {
		return (String)mainline_style_code_map.get(peer_id.substring(0, 1));
	}
	
	public static String getAzStyleClientVersion(String client_name, String peer_id) {
		String version_scheme = (String)az_client_version_map.get(client_name);
		if (version_scheme == NO_VERSION) {return null;}
		try {
			return client_name + " " + BTPeerIDByteDecoderUtils.decodeAzStyleVersionNumber(
				peer_id.substring(3, 7), version_scheme
			);
		}
		catch (Exception e) {
			BTPeerIDByteDecoder.logUnknownClient(peer_id);
			return null;
		}
	}

	public static ClientData getSubstringStyleClient(String peer_id) {
		ClientData cd = null;
		for (int i=0; i<custom_style_client_list.size(); i++) {
			cd = (ClientData)custom_style_client_list.get(i);
			if (peer_id.startsWith(cd.simple_string, cd.simple_string_pos)) {
				return cd;
			}
		}
		return null;
	}
	
	public static String getSubstringStyleClientVersion(ClientData client_data, String peer_id, byte[] peer_id_bytes) {
		VersionNumberData verdata = client_data.version_data;
		if (verdata == null) {return null;}
		
		String version_scheme = verdata.type;
		String version_string = null;
		try {
			if (version_scheme == VER_TWO_BYTE_THREE_PART) {
				int start_byte_index = verdata.pos;
				version_string = BTPeerIDByteDecoderUtils.getTwoByteThreePartVersion(peer_id_bytes[start_byte_index], peer_id_bytes[start_byte_index+1]);
			}
			else if (version_scheme == VER_BLOCK && verdata.length == -1) {
				version_string = BTPeerIDByteDecoderUtils.extractReadableVersionSubstringFromPeerID(peer_id.substring(verdata.pos, peer_id.length()));
			}
			else {
				version_string = BTPeerIDByteDecoderUtils.decodeCustomVersionNumber(
					peer_id.substring(verdata.pos, verdata.pos + verdata.length), version_scheme
				);
			}
			
			if (verdata.fmt == null) {return client_data.client_name + " " + version_string;}
			else {return client_data.client_name + verdata.fmt.replaceFirst("%s", version_string);}
		}
		catch (Exception e) {
			BTPeerIDByteDecoder.logUnknownClient(peer_id);
			return null;
		}
	}
	
	public static String formatUnknownAzStyleClient(String peer_id) {
		String version_string = peer_id.substring(3, 7);
		try {
		    version_string = BTPeerIDByteDecoderUtils.decodeAzStyleVersionNumber(
		    		version_string, VER_AZ_FOUR_DIGITS);
		}
		catch (Exception e) {/* Just use the unformatted string */}

		return MessageText.getString("PeerSocket.unknown_az_style", new String[] {
		    peer_id.substring(1, 3), version_string
		});
	}
	
	public static String formatUnknownShadowStyleClient(String peer_id) {
		String version_string = BTPeerIDByteDecoderUtils.getShadowStyleVersionNumber(peer_id);
		return MessageText.getString("PeerSocket.unknown_shadow_style", new String[] {
			peer_id.substring(0, 1), version_string
		});
	}
	
	
	/**
     * OK - here's where we store the definitions.
	 *
	 * Good place for information about BT peer ID conventions:
	 *  http://wiki.theory.org/BitTorrentSpecification
	 *  http://transmission.m0k.org/trac/browser/trunk/libtransmission/clients.c (hello Transmission authors!) :)
	 *  http://rufus.cvs.sourceforge.net/rufus/Rufus/g3peerid.py?view=log (for older clients)
	 *  http://shareaza.svn.sourceforge.net/viewvc/shareaza/trunk/shareaza/BTClient.cpp?view=markup
	 *  http://libtorrent.rakshasa.no/browser/trunk/libtorrent/src/torrent/peer/client_list.cc
	 *  
	 * By default - if you are unsure about a client's versioning scheme, you should
	 * register it without passing an explicit value.
	 * 
	 * There are some clients which generate peer ID's in Azureus style, but some don't
	 * provide accurate or maybe even any information about what version they are.
	 * 
	 * I've decided that we should follow this criteria - if a client generates a peer
	 * ID which strictly matches the Az-style specification (0'th char is '-', 7'th char
	 * is '-'), then we should include it in this list - if there's no derivable version
	 * information, then we flag it as NO_VERSION.
	 * 
	 * Oh, and hello any closed source BitTorrent client authors who may be reading! ;)
	 */
	static {
		// We define ourselves first... :)
		addAzStyle("AZ", Constants.AZUREUS_PROTOCOL_NAME, VER_AZ_FOUR_DIGITS);
		
		// ... and then do everything else alphabetically.
		addAzStyle("A~", "Ares", VER_AZ_THREE_DIGITS);
		addAzStyle("AG", "Ares", VER_AZ_THREE_DIGITS);
		addAzStyle("AN", "Ares", VER_AZ_FOUR_DIGITS);
		//addAzStyle("AR", "ArcticTorrent", NO_VERSION); //based on libtorrent but same peerid for different versions
		addAzStyle("AR", "Ares"); // Ares is more likely than ArcticTorrent
		addAzStyle("AV", "Avicora");
		addAzStyle("AX", "BitPump", VER_AZ_TWO_MAJ_TWO_MIN);
		addAzStyle("AT", "Artemis");
		addAzStyle("BB", "BitBuddy", "1.234");
		addAzStyle("BC", "BitComet", VER_AZ_SKIP_FIRST_ONE_MAJ_TWO_MIN);
		addAzStyle("BE", "BitTorrent SDK");
		addAzStyle("BF", "BitFlu", NO_VERSION);
		addAzStyle("BG", "BTG", VER_AZ_FOUR_DIGITS);
		addAzStyle("bk", "BitKitten (libtorrent)");
		addAzStyle("BR", "BitRocket", "1.2(34)");
		addAzStyle("BS", "BTSlave");
		addAzStyle("BW", "BitWombat");
		addAzStyle("BX", "BittorrentX");
		addAzStyle("CB", "Shareaza Plus");
		addAzStyle("CD", "Enhanced CTorrent", VER_AZ_TWO_MAJ_TWO_MIN);      
		addAzStyle("CT", "CTorrent", "1.2.34");
		addAzStyle("DP", "Propogate Data Client");
		addAzStyle("DE", "Deluge", VER_AZ_FOUR_DIGITS);
		addAzStyle("EB", "EBit");
		addAzStyle("ES", "Electric Sheep", VER_AZ_THREE_DIGITS);
		addAzStyle("FC", "FileCroc");
		addAzStyle("FG", "FlashGet", VER_AZ_SKIP_FIRST_ONE_MAJ_TWO_MIN);
		addAzStyle("FT", "FoxTorrent/RedSwoosh");
		addAzStyle("GR", "GetRight", "1.2");
		addAzStyle("GS", "GSTorrent"); // TODO: Format is v"abcd"
		addAzStyle("HL", "Halite", VER_AZ_THREE_DIGITS);
		addAzStyle("HN", "Hydranode");
		addAzStyle("KG", "KGet");
		addAzStyle("KT", "KTorrent", VER_AZ_KTORRENT_STYLE);
		addAzStyle("LC", "LeechCraft");
		addAzStyle("LH", "LH-ABC");
		addAzStyle("LK", "linkage", VER_AZ_THREE_DIGITS);
		addAzStyle("LP", "Lphant", VER_AZ_TWO_MAJ_TWO_MIN);
		addAzStyle("LT", "libtorrent (Rasterbar)", VER_AZ_THREE_ALPHANUMERIC_DIGITS);
		addAzStyle("lt", "libTorrent (Rakshasa)", VER_AZ_THREE_ALPHANUMERIC_DIGITS);
		addAzStyle("LW", "LimeWire", NO_VERSION); // The "0001" bytes found after the LW commonly refers to the version of the BT protocol implemented. Documented here: http://www.limewire.org/wiki/index.php?title=BitTorrentRevision
		addAzStyle("MO", "MonoTorrent");
		addAzStyle("MP", "MooPolice", VER_AZ_THREE_DIGITS);
		addAzStyle("MR", "Miro");
		addAzStyle("MT", "MoonlightTorrent");
		addAzStyle("NE", "BT Next Evolution", VER_AZ_THREE_DIGITS);
		addAzStyle("NX", "Net Transport");
		addAzStyle("OS", "OneSwarm", VER_AZ_FOUR_DIGITS);
		addAzStyle("OT", "OmegaTorrent");
		addAzStyle("PC", PeerClassifier.CACHE_LOGIC, "12.3-4" );
		addAzStyle("PD", "Pando");
		addAzStyle("PE", "PeerProject");
		addAzStyle("pX", "pHoeniX");
		addAzStyle("qB", "qBittorrent", VER_AZ_THREE_ALPHANUMERIC_DIGITS);
		addAzStyle("QD", "qqdownload");
		addAzStyle("RT", "Retriever");
		addAzStyle("RZ", "RezTorrent");
		addAzStyle("S~", "Shareaza alpha/beta");
		addAzStyle("SB", "SwiftBit");
		addAzStyle("SD", "\u8FC5\u96F7\u5728\u7EBF (Xunlei)"); // Apparently, the English name of the client is "Thunderbolt".
		addAzStyle("SG", "GS Torrent", VER_AZ_FOUR_DIGITS);
		addAzStyle("SN", "ShareNET");
		addAzStyle("SP", "BitSpirit"); // >= 3.6
		addAzStyle("SS", "SwarmScope");
		addAzStyle("ST", "SymTorrent", "2.34");
		addAzStyle("st", "SharkTorrent");
		addAzStyle("SZ", "Shareaza");
		addAzStyle("TB", "Torch");
		addAzStyle("TN", "Torrent.NET");
		addAzStyle("TR", "Transmission", VER_AZ_TRANSMISSION_STYLE);
		addAzStyle("TS", "TorrentStorm");
		addAzStyle("TT", "TuoTu", VER_AZ_THREE_DIGITS);
		addAzStyle("TX", "Tixati");
		addAzStyle("UL", "uLeecher!");
		addAzStyle("UT", "\u00B5Torrent", VER_AZ_THREE_DIGITS_PLUS_MNEMONIC);
		addAzStyle("UM", "\u00B5Torrent Mac", VER_AZ_THREE_DIGITS_PLUS_MNEMONIC);
		addAzStyle("WT", "Bitlet");
		addAzStyle("WY", "FireTorrent"); // formerly Wyzo.
		addAzStyle("VG", "\u54c7\u560E (Vagaa)", VER_AZ_FOUR_DIGITS);
		addAzStyle("XL", "\u8FC5\u96F7\u5728\u7EBF (Xunlei)"); // Apparently, the English name of the client is "Thunderbolt".
		addAzStyle("XT", "XanTorrent");
		addAzStyle("XX", "XTorrent", "1.2.34");
		addAzStyle("XC", "XTorrent", "1.2.34");
		addAzStyle("ZT", "ZipTorrent"); 
		addAzStyle("7T", "aTorrent"); 
		addAzStyle("#@", "Invalid PeerID");

		addShadowStyle('A', "ABC");
		addShadowStyle('O', "Osprey Permaseed");
		addShadowStyle('Q', "BTQueue");
		addShadowStyle('R', "Tribler");
		addShadowStyle('S', "Shad0w");
		addShadowStyle('T', "BitTornado");
		addShadowStyle('U', "UPnP NAT");
		
		addMainlineStyle('M', "Mainline");
		addMainlineStyle('Q', "Queen Bee");
		
		// Simple clients with no version number.
		addSimpleClient("\u00B5Torrent 1.7.0 RC", "-UT170-"); // http://forum.utorrent.com/viewtopic.php?pid=260927#p260927
		addSimpleClient("Azureus 1", "Azureus");
		addSimpleClient("Azureus 2.0.3.2", "Azureus", 5);
		addSimpleClient("Aria 2", "-aria2-");
		addSimpleClient("BitTorrent Plus! II", "PRC.P---");
		addSimpleClient("BitTorrent Plus!", "P87.P---");
		addSimpleClient("BitTorrent Plus!", "S587Plus");
		addSimpleClient("BitTyrant (Azureus Mod)", "AZ2500BT");
		addSimpleClient("Blizzard Downloader", "BLZ");
		addSimpleClient("BTGetit", "BG", 10);
		addSimpleClient("BTugaXP", "btuga");
		addSimpleClient("BTugaXP", "BTuga", 5);
		addSimpleClient("BTugaXP", "oernu");
		addSimpleClient("Deadman Walking", "BTDWV-");
		addSimpleClient("Deadman", "Deadman Walking-");
		addSimpleClient("External Webseed", "Ext");
		addSimpleClient("G3 Torrent", "-G3");
		addSimpleClient("GreedBT 2.7.1", "271-");
		addSimpleClient("Hurricane Electric", "arclight");
		addSimpleClient("HTTP Seed", "-WS" );	// used internally to denote incoming webseed connections
		addSimpleClient("JVtorrent", "10-------");
		addSimpleClient("Limewire", "LIME");
		addSimpleClient("Martini Man", "martini");
		addSimpleClient("Pando", "Pando");
		addSimpleClient("PeerApp", "PEERAPP");
		addSimpleClient("SimpleBT", "btfans", 4);
		addSimpleClient("Swarmy", "a00---0");
		addSimpleClient("Swarmy", "a02---0");
		addSimpleClient("Teeweety", "T00---0");
		addSimpleClient("TorrentTopia", "346-");
		addSimpleClient("XanTorrent", "DansClient");
		addSimpleClient("MediaGet",     "-MG1");
		addSimpleClient("MediaGet2 2.1", "-MG21");
		
		/**
		 * This is interesting - it uses Mainline style, except uses two characters instead of one.
		 * And then - the particular numbering style it uses would actually break the way we decode
		 * version numbers (our code is too hardcoded to "-x-y-z--" style version numbers).
		 * 
		 * This should really be declared as a Mainline style peer ID, but I would have to
		 * make my code more generic. Not a bad thing - just something I'm not doing right
		 * now.
		 */
		addSimpleClient("Amazon AWS S3", "S3-");
		
		// Clients with their own custom format and version number style.
		ClientData client = null;
		
		// DNA01000 becomes version 1.0 - we'll ignore the other digits for now.
		client = addSimpleClient("BitTorrent DNA", "DNA");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 2, 4);

		// Pre build 10000 versions.
		client = addSimpleClient("Opera", "OP");
		addVersionedClient(client, VER_BLOCK, 4, " (Build %s)");

		// Post build 10000 versions.
		client = addSimpleClient("Opera", "O");
		addVersionedClient(client, VER_BLOCK, 5, " (Build %s)");

		client = addSimpleClient("Burst!", "Mbrst");
		addVersionedClient(client, VER_DOTTED_BLOCK, 5); // 3 version components, 5 chars which describe it
		
		client = addSimpleClient("TurboBT", "turbobt");
		addVersionedClient(client, VER_BLOCK, 5);
		
		client = addSimpleClient("BT Protocol Daemon", "btpd");
		addVersionedClient(client, VER_BLOCK, 3, 5);
		
		client = addSimpleClient("Plus!", "Plus");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 3);
		
		client = addSimpleClient("XBT", "XBT");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 3);
		
		client = addSimpleClient("BitsOnWheels", "-BOW");
		addVersionedClient(client, VER_BITS_ON_WHEELS, 3);
		
		client = addSimpleClient("eXeem", "eX");
		addVersionedClient(client, VER_BLOCK, 18, " [%s]"); // Refers to user ID.
		
		client = addSimpleClient("MLdonkey", "-ML");
		addVersionedClient(client, VER_DOTTED_BLOCK, 5);
		
		client = addSimpleClient("Bitlet", "BitLet");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 2);
		
		client = addSimpleClient("AllPeers", "AP");
		addVersionedClient(client, VER_BLOCK, -1);
		
		client = addSimpleClient("BTuga Revolution", "BTM");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 2);
		
		client = addSimpleClient("Rufus", "RS", 2);
		addVersionedClient(client, VER_TWO_BYTE_THREE_PART, 2, 0);
		
		// BitMagnet - predecessor to Rufus.
		client = addSimpleClient("BitMagnet", "BM", 2);
		addVersionedClient(client, VER_TWO_BYTE_THREE_PART, 2, 0);

		client = addSimpleClient("QVOD", "QVOD");
		addVersionedClient(client, VER_BLOCK, 4, " (Build %s)");
		
		// Top-BT - based on BitTornado, but doesn't quite stick
		// to Shadow's naming conventions - so we'll use substring
		// matching instead.
		client = addSimpleClient("Top-BT", "TB");
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 3);
		
		client = addSimpleClient("Tixati", "TIX");
		addVersionedClient(client, VER_TWOBYTE_BLOCK_DOTTED_CHAR, 4);
		
		client = addSimpleClient("folx", "-FL");	// seems to have a sub-version encoded in following 3 bytes, not worked out how: "folx/1.0.456.591" : 2D 464C 3130 FF862D 486263574A43585F66314D5A
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 2);

		client = addSimpleClient("\u00B5Torrent Mac", "-UM");	
		addVersionedClient(client, VER_BYTE_UM_STYLE, 4);
		
		client = addSimpleClient("\u00B5Torrent", "-UT");		// UT 3.4+
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 3);

		client = addSimpleClient("BitTorrent", "-BT");		// BitTorrent 7.9.1 appeared with this: -BTnnn-
		addVersionedClient(client, VER_BYTE_BLOCK_DOTTED_CHAR, 3);
	}
	
	static class ClientData {
		
		String client_name;
		private int simple_string_pos;
		private String simple_string;
		private VersionNumberData version_data;
		
		ClientData(String client_name, String simple_string, int simple_string_pos) {
			this.simple_string_pos = simple_string_pos;
			this.simple_string = simple_string;
			this.client_name = client_name;
			this.version_data = null;
		}
		
	}

	static class VersionNumberData {

		private String type; // How to extract and put digit components together
		private int pos; // Where does this appear in a client ID string?
		private String fmt; // How do we display that version number?
		private int length; // How long is the version number?
		
		VersionNumberData(String type, int length, String formatter, int position) {
			this.type = type;
			this.pos = position;
			this.fmt = formatter;
			this.length = length;
		}
		
	}

	
}
