package com.limegroup.gnutella.gui.search;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Locale;
import java.util.MissingResourceException;

import java.awt.Component;
import java.awt.Graphics;

import javax.swing.Icon;
import javax.swing.ImageIcon;

import com.limegroup.gnutella.MediaType;
import com.limegroup.gnutella.gui.GUIMediator;
import com.limegroup.gnutella.gui.tables.IconAndNameHolder;
import com.limegroup.gnutella.gui.xml.XMLUtils;
import com.limegroup.gnutella.xml.LimeXMLProperties;
import com.limegroup.gnutella.xml.LimeXMLSchema;
import com.limegroup.gnutella.xml.LimeXMLSchemaRepository;

/**
 * Associates a MediaType with a LimeXMLSchema.
 *
 * Also contains factory methods for retrieving all media types,
 * and retrieving the media type associated with a specific TableLine.
 */
public class NamedMediaType implements IconAndNameHolder, Comparable {

    /**
     * The cached mapping of description -> media type,
     * for easy looking up from incoming results.
     */
    private static final Map /* String -> NamedMediaType */ CACHED_TYPES =
        new HashMap();
    
    /**
     * The MediaType this is describing.
     */
    private final MediaType _mediaType;
    
    /**
     * The name used to describe this MediaType/LimeXMLSchema.
     */
    private final String _name;
    
    /**
     * The icon used to display this mediaType/LimeXMLSchema.
     */
    private final Icon _icon;
    
    /**
     * The (possibly null) LimeXMLSchema.
     */
    private final LimeXMLSchema _schema;
    
    /**
     * Constructs a new NamedMediaType, associating the MediaType with the
     * LimeXMLSchema.
     */
    public NamedMediaType(MediaType mt, LimeXMLSchema schema) {
        if(mt == null)
            throw new NullPointerException("Null media type.");
        
        this._mediaType = mt;
        this._schema = schema;
        this._name = constructName(_mediaType, _schema);
        this._icon = getIcon(_mediaType, _schema);
    }
    
    /**
     * Compares this NamedMediaType to another.
     */
    public int compareTo(Object o) {
        NamedMediaType other = (NamedMediaType)o;
        return _name.compareTo(other._name);
    }
    
    /**
     * Returns the name of this NamedMediaType.
     */
    public String getName() {
        return _name;
    }
    
    /**
     * Returns the icon representing this NamedMediaType.
     */
    public Icon getIcon() {
        return _icon;
    }
    
    /**
     * Returns the description of this NamedMediaType.
     */
    public String toString() {
        return _name;
    }
    
    /**
     * Returns the media type this is wrapping.
     */
    public MediaType getMediaType() {
        return _mediaType;
    }
    
    /**
     * Returns the schema this is wrapping.
     */
    public LimeXMLSchema getSchema() {
        return _schema;
    }
    
    /**
     * Retrieves the named media type for the specified schema uri.
     *
     * This should only be used if you are positive that the media type
     * is already cached for this description OR it is not a default
     * type.
     */
    public static NamedMediaType getFromDescription(String description) {
        NamedMediaType type = (NamedMediaType)CACHED_TYPES.get(description);
        if(type != null)
            return type;
            
        type = new NamedMediaType(new MediaType(description), null);
        CACHED_TYPES.put(description, type);
        return type;
    }
    
    /**
     * Retrieves the named media type from the specified extension.
     *
     * This should only be used if you are positive that the media type
     * is already cached for this extension.
     */
    public static NamedMediaType getFromExtension(String extension) {
        MediaType mt = MediaType.getMediaTypeForExtension(extension);
        if(mt == null)
            return null;
            
        String description = mt.getMimeType();
        return getFromDescription(description);
    }
    
    /**
     * Retrieves all possible media types, wrapped in a NamedMediaType.
     */
    public static List getAllNamedMediaTypes() {
        List allSchemas = new LinkedList();

        //Add all our schemas to the list.
        for(Iterator i = LimeXMLSchemaRepository.instance().
                            getAvailableSchemas().iterator(); i.hasNext(); ) {
            LimeXMLSchema schema = (LimeXMLSchema)i.next();
            allSchemas.add(getFromSchema(schema));
        }

        //Add any default media types that haven't been added already.
        MediaType allTypes[] = MediaType.getDefaultMediaTypes();
        for(int i = 0; i < allTypes.length; i++) {
            if(!containsMediaType(allSchemas, allTypes[i]))
                allSchemas.add(getFromMediaType(allTypes[i]));
        }
        
        return allSchemas;
    }     
    
    /**
     * Retrieves the named media type for the specified schema.
     */
    private static NamedMediaType getFromSchema(LimeXMLSchema schema) {
        String description = schema.getDescription();
        NamedMediaType type = (NamedMediaType)CACHED_TYPES.get(description);
        if(type != null)
            return type;
        
        MediaType mt;
        // If it's not a default type, the MediaType is constructed.
        if(!MediaType.isDefaultType(description)) {
            mt = new MediaType(description);
        } else {
            // Otherwise, the default MediaType is used.
            mt = MediaType.getMediaTypeForSchema(description);
        }
        
        type = new NamedMediaType(mt, schema);
        CACHED_TYPES.put(description, type);
        return type;
    }
    
    /**
     * Retrieves the named media type for the specified media type.
     */
    public static NamedMediaType getFromMediaType(MediaType media) {
        String description = media.getMimeType();
        NamedMediaType type = (NamedMediaType)CACHED_TYPES.get(description);
        if(type != null)
            return type;
            
        type = new NamedMediaType(media, null);
        CACHED_TYPES.put(description, type);
        return type;
    }
    
    /**
     * Determines whether or not the specified MediaType is in a list of
     * NamedMediaTypes.
     */
    private static boolean containsMediaType(List named, MediaType type) {
        for(Iterator i = named.iterator(); i.hasNext(); )
            if(((NamedMediaType)i.next()).getMediaType().equals(type))
                return true;
        return false;
    }        
    
    /**
     * Retrieves the icon representing the MediaType/Schema.
     */
    private Icon getIcon(MediaType type, LimeXMLSchema schema) {
        final ImageIcon icon;
        
        if(type == MediaType.getAnyTypeMediaType())
            icon = GUIMediator.getThemeImage("lime");
        else {
            String location = LimeXMLProperties.instance().getXMLImagesDir() + type.getMimeType();
            icon = GUIMediator.getImageFromPath(location);
            if(icon == null) {
                return new EmptyIcon(getName(), 16, 16);
            }
        }
        
        icon.setDescription(getName());
        return icon;
    }
    
    /**
     * Returns the human-readable description of this MediaType/Schema.
     */
    private static String constructName(MediaType type, LimeXMLSchema schema) {
        // If we can act off the MediaType.
        String name = null;
        if(type.isDefault()) {
            String key = type.getDescriptionKey();
            try {
                if(key != null)
                    name = GUIMediator.getStringResource(key);
            } catch(MissingResourceException mre) {
                // oh well, will capitalize the mime-type
            }
            
            // If still no name, capitalize the mime-type.
            if(name == null) {
				name = type.getMimeType();
                name = name.substring(0, 1).toUpperCase(Locale.US) + name.substring(1);
            }
        } else {
            name = XMLUtils.getTitleForSchema(schema);
        }
        
        return name;
    }
    
    private static class EmptyIcon implements Icon {
        private final String name;
        private final int width;
        private final int height;
        
        public EmptyIcon(String name, int width, int height) {
            this.name = name;
            this.width = width;
            this.height = height;
        }
        
        public void paintIcon(Component c, Graphics g, int x, int y) {}
        public int getIconWidth() { return width; }
        public int getIconHeight() { return height; }
        public String toString() { return name; }
    }
}
