package com.jscape.inet.ftp;

import java.io.BufferedReader;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Vector;

/**
 * MLST and MLSD commands output parser.
 *
 * @author Alex
 */
@SuppressWarnings("unchecked")
public final class MLSDParser
        implements FtpFileParser {

    private static final MLSEntryAssembler ASSEMBLER = new MLSEntryAssembler();

    private static final String READING_MASK = "r--r--r--";
    private static final String WRITING_MASK = "-w--w--w-";
    private static final String READ_WRITE_MASK = "rw-rw-rw-";
    private static final String DENIED_MASK = "---------";

    private static final String DATE_FORMAT = "MM/dd/yyyy";
    private static final String TIME_FORMAT = "HH:mm:ss";
    private static final String DATE_TIME_FORMAT = "MM/dd/yyyy HH:mm:ss";

    /**
     * Parses file description line.
     *
     * @param line line to parse
     *
     * @return file description instance
     */
    public FtpFile parse(String line) {
        line = line.trim();
        MLSEntry entry = ASSEMBLER.restoreEntry(line);
        FtpFile file = new FtpFile(entry.getPathname(), line, this);

        if (entry.getSize() != null) {
            file.setFilesize(entry.getSize().toString());
        }

        if (entry.getType() != null) {
            if (entry.getType().getName().contains("dir")) {
                file.setDirectory(true);
            }
        }

        if (entry.getPermissions() != null) {
            file.setPermission(getPermissionsString(entry));
        }

        if (entry.getLastModificationDate() != null) {
            Date date = entry.getLastModificationDate();
            file.setDate(new SimpleDateFormat(DATE_FORMAT).format(date));
            file.setTime(new SimpleDateFormat(TIME_FORMAT).format(date));
        }

        return file;
    }

    /**
     * @see FtpFileParser#parse(java.io.BufferedReader)
     */
    public Enumeration parse(BufferedReader in)
            throws IOException {
        Vector files = new Vector();
        String line;

        while ((line = in.readLine()) != null) {
            files.add(parse(line));
        }

        return files.elements();
    }

    /**
     * Returns permissions string.
     *
     * @param entry entry instance
     *
     * @return Unix permissions string
     */
    private String getPermissionsString(MLSEntry entry) {
        MLSEntry.Permissions permissions = entry.getPermissions();
        StringBuilder buffer = new StringBuilder();
        boolean readingAllowed;
        boolean writingAllowed;

        if (isDirectory(entry)) {
            buffer.append('d');
            readingAllowed = permissions.isDirectoryEnteringAllowed() && permissions.isListingAllowed();
            writingAllowed = permissions.isDirectoryEnteringAllowed() && permissions.isDirectoryCreationAllowed() && permissions.isFileCreationAllowed() && permissions.isDirectoryPurgingAllowed() && permissions.isDeletionAllowed();
        } else {
            buffer.append('c');
            readingAllowed = permissions.isFileReadingAllowed();
            writingAllowed = permissions.isFileWritingAllowed() && permissions.isFileAppendingAllowed() && permissions.isDeletionAllowed() && permissions.isRenamingAllowed();
        }

        if (readingAllowed && writingAllowed) {
            buffer.append(READ_WRITE_MASK);
        } else if (readingAllowed) {
            buffer.append(READING_MASK);
        } else if (writingAllowed) {
            buffer.append(WRITING_MASK);
        } else {
            buffer.append(DENIED_MASK);
        }

        return buffer.toString();
    }

    /**
     * Checks if entry denotes directory.
     *
     * @param entry entry instance
     *
     * @return <code>true</code> if entry denotes directory;
     *         <code>false</code> otherwise
     */
    private boolean isDirectory(MLSEntry entry) {
        MLSEntry.Type type = entry.getType();

        return (MLSEntry.Type.CURRENT_DIRECTORY == type
                || MLSEntry.Type.PARENT_DIRECTORY == type
                || MLSEntry.Type.DIRECTORY == type);
    }

    /**
     * Parses a string which contains a date and returns a date object
     *
     * @param date The string will be parsed
     *
     * @return A object date which corresponds with the input string argument
     *
     * @throws ParseException if an error ocurrs
     */
    public Date getFileDate(String date) {
        try {
            return new SimpleDateFormat(DATE_TIME_FORMAT).parse(date);
        } catch (ParseException e) {
            return null;
        }
    }

    /**
     * Gets the date and time for an FtpFile entry sourced from an MLSD directory listing.
     *
     * @param file the FtpFile
     *
     * @throws ParseException if an error occurs while parsing entry
     * @see FtpFile
     */
    public Date getDateTime(FtpFile file)
            throws ParseException {
        try {
            return new SimpleDateFormat(DATE_TIME_FORMAT).parse(file.getDate() + " " + file.getTime());
        } catch (ParseException e) {
            return null;
        }
    }

}
