/*
 * @(#)FileTransferRemoteSort.java
 *
 * Copyright (c) 1999-2007 JSCAPE LLC
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 */

package com.jscape.filetransfer;

import com.jscape.util.SuppressObfuscation;

import java.util.*;

/**
 * Sorts an <code>Enumeration</code> of <code>FileTransferRemoteFile</code>. The user must
 * specify a <code>Comparator</code> instance which will be used by the sort algorithm.
 * <p/>
 * The following is an example usage
 * <p/>
 * <pre>
 * FtpTransfer ftp = new FtpTransfer(host, user, password, port);
 * ftp.connect();
 *
 * // Get the remote files enumeration
 * Enumeration e = ftp.getDirListing();
 *
 * //Creates the sorter with the file date comparator
 * FileTransferRemoteSort sorter = new FileTransferRemoteSort(e,
 *         new FileTransferRemoteSort.FileDateComparator());
 *
 * // sort in ascending order
 * sorter.setAscendent(true);
 * // sorts enumeration
 * e = sorter.sort();
 * </pre>
 *
 * @author Alvaro
 */
@SuppressWarnings("unchecked")
@SuppressObfuscation
public class FileTransferRemoteSort {
    /**
     * Data will be sorted.
     */
    private Enumeration data;

    /**
     * Indicates the way will be sorted.
     */
    private boolean ascendent = true;

    /**
     * comparator.
     */
    private FileTransferRemoteComparator comparator;

    /**
     * Creates a new <code>FileTransferRemoteSort</code> instance used to sort an <code>Enumeration</code> of <code>FileTransferRemoteFile</code>.
     *
     * @param e          an <code>Enumeration</code> containing remote files
     * @param comparator the <code>Comparator</code> to use in sorting
     */
    public FileTransferRemoteSort(Enumeration e, FileTransferRemoteComparator comparator) {
        this.data = e;
        this.comparator = comparator;
    }

    /**
     * Gets <code>FileTransferRemoteComparator</code> instance.
     *
     * @return a <code>FileTransferRemoteComparator</code>
     */
    public FileTransferRemoteComparator getComparator() {
        return this.comparator;
    }

    /**
     * Sets <code>FileTransferRemoteComparator</code>.
     *
     * @param comparator a <code>FileTransferRemoteComparator</code>
     */
    public void setComparator(final FileTransferRemoteComparator comparator) {
        this.comparator = comparator;
    }

    /**
     * Sets whether sort will be performed in ascending or descending order.
     *
     * @param ascendent if <code>true</code> the data will be sorted in ascending order
     */
    public void setAscendent(final boolean ascendent) {
        this.ascendent = ascendent;
        comparator.setAscendent(ascendent);
    }

    /**
     * Gets whether sort will be performed in ascending or descending order.
     *
     * @return <code>true</code> if the data will be sorted in ascending order
     */
    public boolean isAscendent() {
        return this.ascendent;
    }

    /**
     * Sets the data to be sorted.
     *
     * @param e an <code>Enumeration</code> of <code>FileTransferRemoteFile</code>
     */
    public void setData(final Enumeration e) {
        this.data = e;
    }

    /**
     * Gets the data to be sorted.
     *
     * @return an <code>Enumeration</code> of <code>FileTranferRemoteFile</code>
     */
    public Enumeration getData() {
        return this.data;
    }

    /**
     * Sorts the data.
     *
     * @return a sorted <code>Enumeration</code> of <code>FileTransferRemoteFile</code>
     */
    public Enumeration sort() {

        List listData = asArrayList(this.data);

        List directoryData = getDataByType(listData, true, false);
        List linkData = getDataByType(listData, false, true);
        List fileData = getDataByType(listData, false, false);

        Collections.sort(directoryData, this.comparator);
        Collections.sort(linkData, this.comparator);
        Collections.sort(fileData, this.comparator);
        listData = new ArrayList();
        if (this.ascendent) {
            listData.addAll(directoryData);
            listData.addAll(linkData);
            listData.addAll(fileData);
        } else {
            listData.addAll(fileData);
            listData.addAll(linkData);
            listData.addAll(directoryData);
        }

        this.data = Collections.enumeration(listData);
        return this.data;
    }

    /**
     * Converts an <code>Enumeration</code> to a <code>List</code>.
     *
     * @param e an Enumeration
     *
     * @return a List
     */
    private List asArrayList(final Enumeration e) {
        List list = new ArrayList();
        while (e.hasMoreElements()) {
            list.add(e.nextElement());
        }
        return list;
    }

    /**
     * Gets a list of files or directories or links. The type of that list will
     * depend of the <code>directory</code> indicator and <code>link</code>
     * indicator
     *
     * @param data      The source where the data will be taken
     * @param directory if <code>true</code> the returned data will contain only
     *                  directories
     * @param link      if <code>true</code> the returned data will contain only
     *                  links
     *
     * @return A list which contain only files or only directories or only links
     */
    private List getDataByType(final List data, final boolean directory, final boolean link) {
        Iterator i = data.iterator();
        List newData = new ArrayList();
        while (i.hasNext()) {
            FileTransferRemoteFile file = (FileTransferRemoteFile) i.next();
            if (file.isDirectory() && directory) {
                newData.add(file);
            } else if (file.isLink() && link) {
                newData.add(file);
            } else if (!file.isDirectory() && !file.isLink() && !directory
                    && !link) {
                newData.add(file);
            }
        }
        return newData;
    }

    /**
     * This the base comparator class. Implements the ascendet getter and setter
     * methos and equals methods too
     *
     * @author Alvaro
     */
    @SuppressObfuscation
    public abstract static class FileTransferRemoteComparator
            implements Comparator {
        /**
         * is ascending.
         */
        protected boolean isAscendent = true;

        /**
         * Sets the ascendet indicator.
         *
         * @param ascendent The new indicator value
         */
        public void setAscendent(boolean ascendent) {
            this.isAscendent = ascendent;
        }

        /**
         * Gets the ascendet value.
         *
         * @return The ascendent indicator
         */
        public boolean getAscendent() {
            return this.isAscendent;
        }

        /**
         * compare.
         *
         * @param o1 Object
         * @param o2 Object
         *
         * @return integer
         */
        public abstract int compare(Object o1, Object o2);

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }

            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            FileTransferRemoteComparator that = (FileTransferRemoteComparator) o;

            return this.isAscendent == that.isAscendent;

        }

        @Override
        public int hashCode() {
            return this.isAscendent ? 1 : 0;
        }
    }

    /**
     * Sorts files by name.
     */
    @SuppressObfuscation
    public static class FileNameComparator
            extends FileTransferRemoteComparator {

        /**
         * Compares the file name of both FileTransferRemoteFile arguments.
         * Compares to string using the compareToIgnoreCase function
         *
         * @param o1 The first file will be compared
         * @param o2 The second file will be compared
         *
         * @return a negative integer, zero, or a positive integer as the first
         *         argument is less than, equal to, or greater than the second.
         */
        public int compare(Object o1, Object o2) {
            FileTransferRemoteFile file1 = (FileTransferRemoteFile) o1;
            FileTransferRemoteFile file2 = (FileTransferRemoteFile) o2;
            int result = 0;
            String fileName1 = file1.getFilename();
            String fileName2 = file2.getFilename();
            result = fileName1 == null || fileName2 == null ? 0 : fileName1.compareToIgnoreCase(fileName2);
            if (!this.isAscendent) {
                result = -result;
            }
            return result;
        }
    }

    /**
     * Sorts files by date.
     *
     * @author Alvaro
     */
    @SuppressObfuscation
    public static class FileDateComparator
            extends FileTransferRemoteComparator {
        /**
         * Compares files for sorting.
         *
         * @param o1 The first file will be compared
         * @param o2 The second file will be compared
         *
         * @return a negative integer, zero or a positive integer as first
         *         argument is less than, equal to or greater than the second
         */
        public int compare(Object o1, Object o2) {
            int result = 0;
            FileTransferRemoteFile file1 = (FileTransferRemoteFile) o1;
            FileTransferRemoteFile file2 = (FileTransferRemoteFile) o2;
            Date fileDate1 = file1.getFileDate();
            Date fileDate2 = file2.getFileDate();
            result = fileDate1 == null || fileDate2 == null ? 0 : fileDate1.compareTo(fileDate2);
            if (!this.isAscendent) {
                result = -result;
            }
            return result;
        }
    }

    /**
     * Sorts files by filesize.
     */
    @SuppressObfuscation
    public static class FileSizeComparator
            extends FileTransferRemoteComparator {

        /**
         * Compares the file size using the compareTo of Long class.
         *
         * @param o1 The first file will be compared
         * @param o2 The second file will be compared
         *
         * @return a negative integer, zero, or a positive integer as the first
         *         argument is less than, equal to, or greater than the second.
         */
        public int compare(Object o1, Object o2) {
            int result = 0;
            FileTransferRemoteFile file1 = (FileTransferRemoteFile) o1;
            FileTransferRemoteFile file2 = (FileTransferRemoteFile) o2;
            Long fileSize1 = new Long(file1.getFilesize());
            Long fileSize2 = new Long(file2.getFilesize());
            result = fileSize1 == null || fileSize2 == null ? 0 : fileSize1.compareTo(fileSize2);

            if (!this.isAscendent) {
                result = -result;
            }
            return result;
        }
    }
}