package javafx.scene.input;

import com.sun.javafx.util.WeakReferenceQueue;

import java.util.*;

/**
 * Data format identifier used as means
 * of identifying the data stored on a clipboard/dragboard.
 * @since JavaFX 2.0
 */
public class DataFormat {

    /**
     * A static cache of all DataFormats created and currently in use. This is needed
     * by the underlying implementation, such that, given a mime type, we can determine
     * the associated DataFormat. The OS level is going to supply us with a mime type
     * (or other string based key), and we need to be able to map this back to the FX
     * DataFormat.
     */
    private static final WeakReferenceQueue<DataFormat> DATA_FORMAT_LIST = new WeakReferenceQueue<DataFormat>();

    /**
     * Represents a plain text string.
     */
    public static final DataFormat PLAIN_TEXT = new DataFormat("text/plain");

    /**
     * Represents an HTML formatted string.
     */
    public static final DataFormat HTML = new DataFormat("text/html");

    /**
     * Represents an RTF formatted string
     */
    public static final DataFormat RTF = new DataFormat("text/rtf");

    /**
     * Represents a URL, encoded as a String
     */
    public static final DataFormat URL = new DataFormat("text/uri-list");

    /**
     * A special platform specific image type, such as is commonly used
     * on the clipboard and interoperates widely with other applications.
     * For example, on Mac this might ultimately be a TIFF while on
     * Windows it might be a DIB (device independent bitmap).
     */
    public static final DataFormat IMAGE = new DataFormat("application/x-java-rawimage");

    /**
     * Represents a List of Files.
     */
    public static final DataFormat FILES = new DataFormat("application/x-java-file-list", "java.file-list");

    /**
     * Data format used internally, represents dragged image. Making this
     * a private field prevents user from creating this DataFormat and breaking
     * our drag view implementation.
     */
    private static final DataFormat DRAG_IMAGE = new DataFormat("application/x-java-drag-image");

    /**
     * Data format used internally, represents offset in the dragged image.
     * Making this a private field prevents user from creating this DataFormat
     * and breaking our drag view implementation.
     */
    private static final DataFormat DRAG_IMAGE_OFFSET = new DataFormat("application/x-java-drag-image-offset");

    /**
     * A set of identifiers, typically mime types, for this DataFormat.
     * In most cases this will be a single String.
     */
    private final Set<String> identifier;

    /**
     * Create a new DataFormat, specifying the set of ids that are associated with
     * this data format. Typically the ids are one or more mime types. For each
     * id, any data associated with this DataFormat will be registered on the
     * clipboard. For example, suppose I had the following:
     * <pre><code>
     *     DataFormat fmt = new DataFormat("text/foo", "text/bar");
     *     Clipboard clipboard = Clipboard.getSystemClipboard();
     *     ClipboardContent content = new ClipboardContent();
     *     content.put(fmt, "Hello");
     *     clipboard.setContent(content);
     * </code></pre>
     *
     * With the above code, if I were to look on the clipboard, I'd find the String "Hello"
     * listed both for "text/foo" and "text/bar" on the clipboard.
     *
     * <p>
     * Note that the ids may be subject to platform restrictions in some cases.
     * For instance, Swing requires a mime type so if an {@code id} is not
     * of the "type/subtype" format it won't be possible
     * to drag data of this type from/to {@link javafx.embed.swing.JFXPanel}.
     * </p>
     * @param ids The set of ids used to represent this DataFormat on the clipboard.
     * @throws IllegalArgumentException if one of the given mime types is already
     *         assigned to another DataFormat.
     */
    public DataFormat(String... ids) {
        DATA_FORMAT_LIST.cleanup();
        if (ids != null) {
            for (String id : ids) {
                if (lookupMimeType(id) != null) {
                    throw new IllegalArgumentException("DataFormat '" + id +
                            "' already exists.");
                }
            }
            this.identifier = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(ids)));
        } else {
            this.identifier = Collections.<String>emptySet();
        }

        // Add to the statis data format list.
        DATA_FORMAT_LIST.add(this);
    }

    /**
     * Gets the unmodifiable set of identifiers for this DataFormat.
     * @return an unmodifiable set that is never null.
     */
    public final Set<String> getIdentifiers() {
        return identifier;
    }

    /**
     * Returns a string representation of this {@code DataFormat} object.
     * @return a string representation of this {@code DataFormat} object.
     */
    @Override public String toString() {
        if (identifier.isEmpty()) {
            return "[]";
        } else if (identifier.size() == 1) {
            StringBuilder sb = new StringBuilder("[");
            sb.append(identifier.iterator().next());
            return (sb.append("]").toString());
        } else {
            StringBuilder b = new StringBuilder("[");
            Iterator<String> itr = identifier.iterator();
            while (itr.hasNext()) {
                b = b.append(itr.next());
                if (itr.hasNext()) {
                    b = b.append(", ");
                }
            }
            b = b.append("]");
            return b.toString();
        }
    }

    /**
     * Returns a hash code for this {@code DataFormat} object.
     * @return a hash code for this {@code DataFormat} object.
     */
    @Override public int hashCode() {
        int hash = 7;

        for (String id : identifier) {
            hash = 31 * hash + id.hashCode();
        }

        return hash;
    }

    /**
     * Indicates whether some other object is "equal to" this one.
     * @param obj the reference object with which to compare.
     * @return {@code true} if this object is equal to the {@code obj} argument; {@code false} otherwise.
     */
    @Override public boolean equals(Object obj) {
        if (obj == null || ! (obj instanceof DataFormat)) {
            return false;
        }

        DataFormat otherDataFormat = (DataFormat) obj;

        if (identifier.equals(otherDataFormat.identifier)) {
            return true;
        }

        return false;
    }

    /**
     * Looks for the DataFormat which has been previously created with
     * the given mime type as one of its ids.
     * @param mimeType If null or the empty string, then null is returned.
     * @return The matching DataFormat
     */
    public static DataFormat lookupMimeType(String mimeType) {
        if (mimeType == null || mimeType.length() == 0) {
            return null;
        }

        Iterator itr = DATA_FORMAT_LIST.iterator();
        while (itr.hasNext()) {
            DataFormat dataFormat = (DataFormat) itr.next();
            if (dataFormat.getIdentifiers().contains(mimeType)) {
                return dataFormat;
            }
        }
        return null;
    }
}
