package com.limegroup.gnutella.gui.search;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.StringTokenizer;
import java.util.Map;
import java.util.HashMap;

import com.limegroup.gnutella.MediaType;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.util.IpPort;

/**
 * Simple struct-like class containing information about a search.
 */
public class SearchInformation {
    /** Constants used to serialize the search information to Map. */
    /** Key in map which holds property {@link #type}. */
    private static final String MAP_TYPE  = "type";
    /** Key in map which holds property {@link #query}. */
    private static final String MAP_QUERY = "query";
    /** Key in map which holds property {@link #xml}. */
    private static final String MAP_XML   = "xml";
    /** Key in map which holds property {@link #media}. */
    private static final String MAP_MEDIA = "media";
    /** Key in map which holds property {@link #title}. */
    private static final String MAP_TITLE = "title";
    
    /**
     * A keyword search.
     */
    public static final int KEYWORD = 0;
    
    /**
     * A what is new search.
     */
    public static final int WHATS_NEW = 1;
    
    /**
     * A browse host search.
     */
    public static final int BROWSE_HOST = 2;
    
    /**
     * The string to use to describe a what's new search.
     */
    public static final String WHATS_NEW_DESC =
        GUIMediator.getStringResource("SEARCH_WHATS_NEW_SMALL");
    
    /**
     * The kind of search this is.
     */
    private final int type;
    
    /**
     * The simple query string.
     */
    private final String query;
    
    /**
     * The XML string.
     */
    private final String xml;
    
    /**
     * The MediaType of the search.
     */
    private final MediaType media;

	/**
	 * The title of this search as it is displayed to the user. 
	 */
	private final String title;
    
    /**
     * Private constructor -- use factory methods instead.
     * @param title can be <code>null</code>, then the query is used.
     */
    private SearchInformation(int type, String query, String xml,
                              MediaType media, String title) {
        if(media == null)
            throw new NullPointerException("null media");
        if(query == null)
            throw new NullPointerException("null query");
        this.type = type;
        this.query = query.trim();
        this.xml = xml;
        this.media = media;
		this.title = title != null ? title : query; 
    }
	
	private SearchInformation(int type, String query, String xml,
			MediaType media) {
		this(type, query, xml, media, null);
	}
    
    /**
     * Creates a new keyword, but state is loaded from Map generated
     * by {@link toMap()}.
     * @param map The map with storred state.
     * @see toMap()
     * @see fromMap(Map)
     */
    private SearchInformation(Map map) {
        Integer type = (Integer) map.get(MAP_TYPE);
        if ( type == null )
            throw new NullPointerException("null type");
        this.type = type.intValue();

        query = (String) map.get(MAP_QUERY);
        xml   = (String) map.get(MAP_XML);
        media = (MediaType) map.get(MAP_MEDIA);
        title = (String) map.get(MAP_TITLE);

        if(media == null)
            throw new NullPointerException("null media");
        if(query == null)
            throw new NullPointerException("null query");
    }
    
    
    /**
     * Creates a keyword search.
     */
    public static SearchInformation createKeywordSearch(String query,
                                                 String xml,
                                                 MediaType media) {
        return new SearchInformation(KEYWORD, query, xml, media);
    }
	
	/**
	 * Creates a keyword search with a title different from the query string.
	 * @param query
	 * @param xml
	 * @param media
	 * @param title
	 * @return
	 */
	public static SearchInformation createTitledKeyWordSearch(String query,
			String xml, MediaType media, String title) {
		return new SearchInformation(KEYWORD, query, xml, media, title);
	}
    
    /**
     * Creates a what's new search.
     */
    public static SearchInformation createWhatsNewSearch(String name, MediaType type){
        return new SearchInformation(WHATS_NEW, 
            WHATS_NEW_DESC + " - " + name, null, type);
    }
    
    /**
     * Create's a browse host search.
     */
    public static SearchInformation createBrowseHostSearch(String desc) {
        return new SearchInformation(BROWSE_HOST, desc, null, 
            MediaType.getAnyTypeMediaType());
    }
    
    /**
     * Retrieves the basic query of the search.
     */
    public String getQuery() {
        return query;
    }
    
    /**
     * Retrieves the XML portion of the search.
     */
    public String getXML() {
        return xml;
    }
    
    /**
     * Retrieves the MediaType of the search.
     */
    public MediaType getMediaType() {
        return media;
    }
    
	public String getTitle() {
		return title;
	}
	
    /**
     * Gets the IP/Port if this is a browse-host.
     */
    IpPort getIpPort() {
        if(!isBrowseHostSearch())
            throw new IllegalStateException();

        StringTokenizer st = new StringTokenizer(getQuery(), ":");
        String host = null;
        int port = 6346;
        if (st.hasMoreTokens())
            host = st.nextToken();
        if (st.hasMoreTokens()) {
            try {
                port = Integer.parseInt(st.nextToken());
            } catch(NumberFormatException ignored) {}
        }
        InetAddress addr = null;
        try {
            addr = InetAddress.getByName(host);
        } catch(UnknownHostException ignored) {}
        
        final String _host = host;
        final int _port = port;
        final InetAddress _addr = addr;
        return new IpPort() {
            public InetAddress getInetAddress() { return _addr; }
            public String getAddress() { return _host; }
            public int getPort() { return _port; }
        };
    }
    
    /**
     * Determines whether or not this is an XML search.
     */
    public boolean isXMLSearch() {
        return xml != null && !xml.equals("");
    }
    
    /**
     * Determines if this is a keyword search.
     */
    public boolean isKeywordSearch() {
        return type == KEYWORD;
    }
    
    /**
     * Determines if this is a what's new search.
     */
    public boolean isWhatsNewSearch() {
        return type == WHATS_NEW;
    }
    
    /**
     * Determines if this is a browse host search.
     */
    public boolean isBrowseHostSearch() {
        return type == BROWSE_HOST;
    }
    
    /**
     * Converts state storred in the object into map. In this way, state
     * can be storred in classes unawre of this class existance.
     * @return A map which holds parameters of the class.
     * @see fromMap()
     * @see SearchInformation(Map)
     */
    public Map toMap() {
        Map map = new HashMap();
        map.put(MAP_TYPE, new Integer(type));
        map.put(MAP_QUERY, query);
        map.put(MAP_XML, xml);
        map.put(MAP_MEDIA, media);
        map.put(MAP_TITLE, title);
        return map;
    }

    /**
     * Creates a new keyword, but state is loaded from map generated
     * by {@link toMap()}.
     * @param map The map with storred state.
     * @see toMap()
     * @see SearchInformation(Map)
     */
    public static SearchInformation createFromMap(Map map) {
        return new SearchInformation(map);
    }
}
