/*
 Copyright (c) 2009, The JAP-Team, JonDos GmbH
 All rights reserved.
 Redistribution and use in source and binary forms, with or without modification,
 are permitted provided that the following conditions are met:

  - Redistributions of source code must retain the above copyright notice,
 this list of conditions and the following disclaimer.

  - Redistributions in binary form must reproduce the above copyright notice,
 this list of conditions and the following disclaimer in the documentation and/or
 other materials provided with the distribution.

  - Neither the name of the University of Technology Dresden, Germany, nor the name of
 the JonDos GmbH, nor the names of their contributors may be used to endorse or
 promote products derived from this software without specific prior written permission.


 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS
 BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
 */
package jondo;

import java.util.Hashtable;
import java.util.Vector;

import anon.client.TrustModel;
import anon.infoservice.BlacklistedCascadeIDEntry;
import anon.infoservice.Database;
import anon.infoservice.MixCascade;
import anon.infoservice.MixCascadeExitAddresses;
import anon.infoservice.StatusInfo;
import anon.util.CountryMapper;
import anon.util.JAPMessages;
import anon.util.Util;

public class MixServiceInfo 
{	
	public static final int MAX_SERVICE_NAME_LENGTH = MixCascade.MAX_CASCADE_NAME_LENGTH; 
	
	private static final String MSG_CONTACT_MESSAGE = MixServiceInfo.class.getName() + "_contactMessage";
	private static final String MSG_CONTACT = MixServiceInfo.class.getName() + "_contact";
	private static final String MSG_VISIT_MESSAGE = MixServiceInfo.class.getName() + "_visitMessage";
	private static final String MSG_SECURITY = MixServiceInfo.class.getName() + "_security";
	private static final String MSG_NUMBER_OF_USERS = MixServiceInfo.class.getName() + "_numberOfUsers";
	private static final String MSG_COUNTRY_MIX = MixServiceInfo.class.getName() + "_countryMix";
	private static final String MSG_COUNTRY_OPERATOR = MixServiceInfo.class.getName() + "_countryOperator";
	
	private static boolean ms_bToStringAsHTML = false;
	private static Hashtable<String,String> ms_countryISO2CodePathTable = 
		new Hashtable<String,String>();
	private static String ms_pathToUsersIcon;
	private static String ms_pathToSecurityIcon;
	private static boolean ms_bSupportForUsersAndSecurity = true;
	
	private int m_iOperatorsShown = -1;
	private Object SYNC_OPERATORS_SHOWN = new Object();
	
	private MixCascade m_cascade;
	private Vector<MixInfo> m_cachedMixInfos;
	private Vector<MixInfo> m_verifiedMixInfos;
	
	private MixServicePerformance m_performance;
	private MixServiceSecurity m_security;
	
	
	public MixServiceInfo(MixCascade a_cascade)
	{
		m_cascade = a_cascade;
		
		if (m_cascade == null)
		{
			throw new NullPointerException();
		}
		
		m_performance = new MixServicePerformance(this);
		m_security = new MixServiceSecurity(this);
		
		m_cachedMixInfos = new Vector<MixInfo>();
		for (int i = 0; i < a_cascade.getNumberOfMixes(); i++)
		{
			m_cachedMixInfos.addElement(new MixInfo(m_cascade.getMixInfo(i)));
		}
		m_verifiedMixInfos = createMixInfoShown();
	}
	
	/**
	 * Should be called in a web environment for initializing the toString method.
	 * PLEASE NOTE: the interface of this method may change frequently, depending on what is currently needed 
	 * for displaying the toString method in HTML.
	 * @param a_countryISO2CodePathTable absolute or relative URL image paths to country flags, the the format: 
	 * code[ISO2, lower case], [path to flag]
	 * @param a_pathToUsersIcon absolute or relative URL image path to an icon representing the number of users on a cascade,
	 * e.g. an iconified person
	 * @param a_pathToSecurityIcon an absolute or relative path to an icon representing the security of the cascade
	 */
	public static void initToStringAsHTML(Hashtable<String,String> a_countryISO2CodePathTable)  
			//String a_pathToUsersIcon, String a_pathToSecurityIcon)
	{
		ms_bToStringAsHTML = true;
		if (a_countryISO2CodePathTable != null)
		{
			ms_countryISO2CodePathTable = a_countryISO2CodePathTable;
		}
		ms_bSupportForUsersAndSecurity = false;  // maybe too much for short HTML space of 60 characters...
		//ms_pathToUsersIcon = a_pathToUsersIcon;
		//ms_pathToSecurityIcon = a_pathToSecurityIcon;
	}
	
	protected MixCascade getMixCascade()
	{
		return m_cascade;
	}
	
	public MixServicePerformance getPerformance()
	{
		return m_performance;
	}
	
	public MixServiceSecurity getSecurity()
	{
		return m_security;
	}
	
	/**
	 * The number of users currently on this cascade.
	 * @return the number of users currently on this cascade; if < 0, no information is available
	 */
	public int countUsers()
	{
		StatusInfo status = (StatusInfo)Database.getInstance(StatusInfo.class).getEntryById(m_cascade.getId());
		if (status == null)
		{
			return -1;
		}
		return status.getNrOfActiveUsers();
	}
	
	public String createCurrentServiceExitIP()
	{
		MixCascadeExitAddresses entry = (MixCascadeExitAddresses)
			Database.getInstance(MixCascadeExitAddresses.class).getEntryById(
				 getMixCascade().getId());
		if (entry == null)
		{
			return null;
		}
		return entry.createExitAddressAsString();
	}
	
	/**
	 * Returns information about all mixes in this cascade. Please keep in mind that a new object is
	 * created each time you that call this method. This is, because the number of mixes shown might
	 * change if certificates expire or get unverified.
	 * @return information about all mixes in this cascade
	 */
	@SuppressWarnings("unchecked")
	public Vector<MixInfo> createMixInfos()
	{
		m_verifiedMixInfos = createMixInfoShown();
		return (Vector<MixInfo>)m_verifiedMixInfos.clone();
	}
	
	public String getId()
	{
		return m_cascade.getMixIDsAsString();
	}

	/** Returns the ID of the underlying Mix-Cascade*/
	public String getCascadeId()
	{
		return m_cascade.getId();
	}

	public boolean isFiltered()
	{
		return !TrustModel.getCurrentTrustModel().isTrusted(getMixCascade());
	}
	
	public boolean isBlacklisted()
	{
		if (Database.getInstance(BlacklistedCascadeIDEntry.class).getEntryById(
				getMixCascade().getMixIDsAsString()) != null)
		{
			return true;
		}
		return false;
	}
	
	public boolean isPremium()
	{
		return m_cascade.isPayment();
	}
	
	public boolean isSOCKS5Supported()
	{
		return m_cascade.isSocks5Supported();
	}
	
	/**
	 * The maximum number of users allowed on this cascade.
	 * @return
	 */
	public int getUserLimit()
	{
		return m_cascade.getMaxUsers();
	}
	
	/**
	 * This cascade's name. Please note that the names of the Mixes may differ!
	 * @return
	 */
	public String getName()
	{
		return m_cascade.getName();
	}
	
	public String toStringCountries()
	{
		return toStringMixes("", true, createMixInfos());
	}
	
	@SuppressWarnings("unchecked")
	private Vector<MixInfo> createMixInfoShown()
	{
		synchronized (SYNC_OPERATORS_SHOWN)
		{
			if (m_iOperatorsShown != m_cascade.getNumberOfOperatorsShown())
			{
				m_iOperatorsShown = m_cascade.getNumberOfOperatorsShown();
		
				if (m_iOperatorsShown > 1)
				{
					if (m_iOperatorsShown == m_cascade.getNumberOfMixes())
					{
						return m_cachedMixInfos;
					}
					else
					{
						/* very rare case; for example, three mixes; the first and middle or middle and last
						 * have the same operator.
						 */
						Vector<MixInfo> vecMixInfo = new Vector<MixInfo>();
						
						vecMixInfo.add(m_cachedMixInfos.elementAt(0));
						for (int i = 1; i < m_iOperatorsShown - 1; i++)
						{
							vecMixInfo.add(m_cachedMixInfos.elementAt(i));
						}
						vecMixInfo.add(m_cachedMixInfos.elementAt(m_cachedMixInfos.size() - 1));
						return vecMixInfo;
					}
				}
				else if (m_iOperatorsShown == 1)
				{
					return (Vector<MixInfo>)Util.toVector(m_cachedMixInfos.elementAt(m_cachedMixInfos.size() - 1));
				}
				else
				{
					return new Vector<MixInfo>();
				}
			}
			else
			{
				return m_verifiedMixInfos;
			}
		}
	}
	
	public String toStringOperators()
	{
		MixInfo mixInfo;
		String strOutput = "";
		
		Vector<MixInfo> vecMixinfo = createMixInfos();
		
		for (int i = 0; i < vecMixinfo.size(); i++)
		{
		
			mixInfo = vecMixinfo.elementAt(i);
			
			MixOperator operator = mixInfo.getOperator();
			
			if (operator != null)
			{
			
				if (ms_bToStringAsHTML && operator.getHomepage() != null)
				{
					strOutput += "<a title=\"" + JAPMessages.getString(MSG_VISIT_MESSAGE, "" + (i+1)) + "\" target=\"_blank\" href=\"" + 
					operator.getHomepage() + "\">";
				}
		
				
				strOutput += "Operator" + (i + 1);
				
				if (operator.getCountryCode() != null)
				{
					strOutput += "(";
					
					if (operator.getCountryCode() != null)
					{
						strOutput += getFormattedCountry(operator.getCountryCode(), 
								JAPMessages.getString(MSG_COUNTRY_OPERATOR, "" + (i+1)));
					}
					
					strOutput = strOutput.trim() + ")";
				}
				
				strOutput += ": ";
				
				if (operator.getOrganizationShortName() != null)
				{
					strOutput += operator.getOrganizationShortName();
				}
				else
				{
					strOutput += operator.getOrganization();
				}
				
			
				if (ms_bToStringAsHTML && operator.getHomepage() != null)
				{
					strOutput += "</a>";
				}
			}
			
			
			if (i + 1  < vecMixinfo.size())
			{
				strOutput += ", ";
			}
		}
		
		return strOutput;
	}
	
	private String toStringMixes(String a_outputBefore, boolean bCountriesOnly,
			Vector<MixInfo> vecMixinfo)
	{
		MixInfo mixInfo;
		String strOutput = a_outputBefore;
		
		for (int i = 0; i < vecMixinfo.size(); i++)
		{
			
			mixInfo = vecMixinfo.elementAt(i);
			
			MixOperator operator = mixInfo.getOperator();
			
			if (operator != null)
			{
				if (!bCountriesOnly)
				{
					if (ms_bToStringAsHTML && operator.getHomepage() != null)
					{
						strOutput += "<a title=\"" + JAPMessages.getString(MSG_VISIT_MESSAGE, "" + (i+1)) + "\" target=\"_blank\" href=\"" + 
						operator.getHomepage() + "\">";
					}
			
					strOutput += "Mix" + (i + 1) + ":";
				
					if (ms_bToStringAsHTML && operator.getHomepage() != null)
					{
						strOutput += "</a>";
					}
				}
			}
			
			String strLocationCountryCode = null;
			if (mixInfo.getLocation() != null)
			{
				strLocationCountryCode = mixInfo.getLocation().getCountryCode();
			}
			
			if (strLocationCountryCode != null)
			{
				strOutput += " " + getFormattedCountry(strLocationCountryCode, 
						JAPMessages.getString(MSG_COUNTRY_MIX, "" + (i+1)));
			}
			if (operator != null)
			{
				if (bCountriesOnly && operator.getCountryCode() != null && (strLocationCountryCode == null || 
						!strLocationCountryCode.equals(operator.getCountryCode())))
				{
					strOutput += "->" + getFormattedCountry(operator.getCountryCode(), 
							JAPMessages.getString(MSG_COUNTRY_OPERATOR, "" + (i+1)));
				}
				
				if (ms_bToStringAsHTML && operator.getEMailContact() != null)
				{
					strOutput += "(";
					
					if (ms_bToStringAsHTML && operator.getEMailContact() != null)
					{
						strOutput += "<a title=\"" + JAPMessages.getString(MSG_CONTACT_MESSAGE, "" + (i+1)) + "\" href=\"mailto:" + 
						operator.getEMailContact() + "\">" + "" + JAPMessages.getString(MSG_CONTACT) + "</a> ";
					}
					
					strOutput = strOutput.trim() + ")";
				}
			}
			
			if (i + 1  < vecMixinfo.size())
			{
				strOutput += ", ";
			}
		}
		
		return strOutput;
	}
	
	
	
	public boolean equals(Object a_object)
	{
		return getMixCascade().equals(((MixServiceInfo)a_object).getMixCascade());
	}
	
	public int hashCode()
	{
		return getMixCascade().hashCode();
	}
	
	/**
	 * This string returns some general info about the cascade, excluding its name.
	 */
	public String toString()
	{
		String strOutput = "";
		boolean bShowBracket = false;
				
		int nrUsers = countUsers();
		if (nrUsers >= 0 && ms_bSupportForUsersAndSecurity)
		{
			bShowBracket = true;
			
			if (ms_bToStringAsHTML && ms_pathToUsersIcon != null)
			{
				strOutput += "<a title=\"" + JAPMessages.getString(MSG_NUMBER_OF_USERS) + 
				"\"><img alt=\""+ JAPMessages.getString(MSG_NUMBER_OF_USERS) + "\" src=\"" + ms_pathToUsersIcon + "\"/></a> ";
			}
			else
			{
				strOutput += JAPMessages.getString(MSG_NUMBER_OF_USERS) + ": ";
			}
			strOutput += "" + nrUsers;
			if (getUserLimit() > 0)
			{
				strOutput += "/" + getUserLimit() + " ";
			}
			else
			{
				strOutput += " ";
			}
		}		
		
		String strSecurity = m_security.toString();
		
		if (strSecurity != null && ms_bSupportForUsersAndSecurity)
		{
			bShowBracket = true;
			
			if (ms_bToStringAsHTML && ms_pathToSecurityIcon != null)
			{
				strOutput += "<a title=\"" + JAPMessages.getString(MSG_SECURITY) + 
				"\"><img alt=\"" + JAPMessages.getString(MSG_SECURITY) + "\" src=\"" + ms_pathToSecurityIcon + "\"/></a> ";
			}
			else
			{
				strOutput += JAPMessages.getString(MSG_SECURITY) + ": ";
			}
		
			strOutput += strSecurity;
		}
		
		Vector<MixInfo> vecMixInfos = createMixInfos();
		
		if (bShowBracket && vecMixInfos.size() > 0)
		{
			strOutput += " [";
		}
		
		strOutput = toStringMixes(strOutput, false, vecMixInfos);
		
		if (bShowBracket && vecMixInfos.size() > 0)
		{
			strOutput = strOutput.trim() + "]";
		}
		
		if (ms_bToStringAsHTML)
		{
			return Util.toHTMLEntities(strOutput.trim());
		}
		return strOutput.trim();
	}
	
	private static String getFormattedCountry(String a_countryCode, String a_titleMessage)
	{
		String strImgPath = (String)ms_countryISO2CodePathTable.get(a_countryCode);
		String strAlt = a_titleMessage + " ";
		String strOutput = "";
		try
		{
			strAlt += new CountryMapper(a_countryCode).toString();
		}
		catch (IllegalArgumentException a_e)
		{
			strAlt += a_countryCode;
		}

		if (ms_bToStringAsHTML && strImgPath != null)
		{	
			strOutput += "<a title=\"" + strAlt + "\">";
			strOutput += "<img alt=\"" + a_countryCode + "\" src=\"" + strImgPath + "\"/>";
			strOutput += "</a>";
		}
		else
		{
			strOutput += a_countryCode;
		}
		
		return strOutput;
	}
}
