package com.jscape.inet.ftp;

import com.jscape.util.Assert;
import com.jscape.util.StringTools;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Entry assembler.
 *
 * @author Alex
 * @version 1.0
 * @see <a href="MLSEntryAssembler.java.html">Source code</a>
 */
public final class MLSEntryAssembler {

    private static final String SIZE_FACT = "size";
    private static final String LAST_MODIFICATION_TIME_FACT = "modify";
    private static final String CREATION_TIME_FACT = "create";
    private static final String TYPE_FACT = "type";
    private static final String UNIQUE_ID_FACT = "unique";
    private static final String PERMISSIONS_FACT = "perm";
    private static final String LANGUAGE_FACT = "language";
    private static final String MEDIA_TYPE_FACT = "media-type";
    private static final String CHARSET_FACT = "charset";

    private static final char FILE_APPENDING = 'a';
    private static final char FILE_CREATION = 'c';
    private static final char DELETION = 'd';
    private static final char DIRECTORY_ENTERING = 'e';
    private static final char RENAMING = 'f';
    private static final char LISTING = 'l';
    private static final char DIRECTORY_CREATION = 'm';
    private static final char DIRECTORY_PURGING = 'p';
    private static final char FILE_READING = 'r';
    private static final char FILE_WRITING = 'w';

    private static final char SP = ' ';
    private static final char EQUAL_SIGN = '=';
    private static final String FACT_SEPARATOR = ";";

    private static final String SHORT_FORMAT = "yyyyMMddHHmmss";
    private static final String PRECISE_FORMAT = "yyyyMMddHHmmss.SSS";

    /**
     * Stores entry to the string representation.
     *
     * @param entry target entry instance
     *
     * @return entry string representation
     */
    public String storeEntry(MLSEntry entry) {
        Assert.notNull(entry);

        StringBuffer buf = new StringBuffer();

        if (entry.getSize() != null) {
            appendFact(SIZE_FACT, entry.getSize().toString(), buf);
        }

        if (entry.getLastModificationDate() != null) {
            appendFact(LAST_MODIFICATION_TIME_FACT, storeDate(entry.getLastModificationDate()), buf);
        }

        if (entry.getCreationDate() != null) {
            appendFact(CREATION_TIME_FACT, storeDate(entry.getCreationDate()), buf);
        }

        if (entry.getType() != null) {
            MLSEntry.Type type = entry.getType();
            appendFact(TYPE_FACT, ((type.getOsName().length() > 0) ? (type.getOsName() + EQUAL_SIGN) : "") + type.getName(), buf);
        }

        if (entry.getUniqueId() != null) {
            appendFact(UNIQUE_ID_FACT, entry.getUniqueId(), buf);
        }

        if (entry.getPermissions() != null) {
            MLSEntry.Permissions permissions = entry.getPermissions();
            appendFact(PERMISSIONS_FACT, storePermissions(permissions), buf);
        }

        if (entry.getLanguage() != null) {
            appendFact(LANGUAGE_FACT, entry.getLanguage(), buf);
        }

        if (entry.getMediaType() != null) {
            appendFact(MEDIA_TYPE_FACT, entry.getMediaType(), buf);
        }

        if (entry.getCharset() != null) {
            appendFact(CHARSET_FACT, entry.getCharset(), buf);
        }

        appendFacts(entry.getAdditionalFacts(), buf);

        buf.append(SP);
        buf.append(entry.getPathname());

        return buf.toString();
    }

    /**
     * Adds a fact to the buffer.
     *
     * @param factname factname
     * @param value    fact value
     * @param buf      buffer
     */
    private void appendFact(String factname, String value, StringBuffer buf) {
        buf.append(factname).append(EQUAL_SIGN).append(value).append(FACT_SEPARATOR);
    }

    /**
     * Adds facts to the buffer.
     *
     * @param facts facts map
     * @param buf   buffer
     */
    private void appendFacts(Map facts, StringBuffer buf) {
        Collection keys = facts.keySet();

        for (Iterator i = keys.iterator(); i.hasNext(); ) {
            String factname = (String) i.next();
            String value = (String) facts.get(factname);

            if (value != null) {
                appendFact(factname, value, buf);
            }
        }
    }

    /**
     * Restores entry from the string.
     *
     * @param data entry data
     *
     * @return restored entry
     */
    public MLSEntry restoreEntry(String data) {
        Assert.isValidString(data);

        int spIndex = data.indexOf(SP);
        Assert.greaterOrEqual(spIndex, 0, "bad data");

        String factsStr = data.substring(0, spIndex);
        String pathname = data.substring(spIndex + 1);

        MLSEntry entry = new MLSEntry(pathname);
        String[] facts = StringTools.split(factsStr, FACT_SEPARATOR, false);

        for (int i = 0; i < facts.length; ++i) {
            String fact = facts[i].trim();
            int equalIndex = fact.indexOf(EQUAL_SIGN);
            Assert.greater(equalIndex, 0, "invalid format");
            String factname = fact.substring(0, equalIndex);
            String value = fact.substring(equalIndex + 1);

            if (SIZE_FACT.equalsIgnoreCase(factname)) {
                entry.setSize(new Long(value));
            } else if (LAST_MODIFICATION_TIME_FACT.equalsIgnoreCase(factname)) {
                entry.setLastModificationDate(restoreDate(value));
            } else if (CREATION_TIME_FACT.equalsIgnoreCase(factname)) {
                entry.setCreationDate(restoreDate(value));
            } else if (TYPE_FACT.equalsIgnoreCase(factname)) {
                entry.setType(restoreType(value));
            } else if (UNIQUE_ID_FACT.equalsIgnoreCase(factname)) {
                entry.setUniqueId(value);
            } else if (PERMISSIONS_FACT.equalsIgnoreCase(factname)) {
                entry.setPermissions(restorePermissions(value.toLowerCase(Locale.ENGLISH)));
            } else if (LANGUAGE_FACT.equalsIgnoreCase(factname)) {
                entry.setLanguage(value);
            } else if (MEDIA_TYPE_FACT.equalsIgnoreCase(factname)) {
                entry.setMediaType(value);
            } else if (CHARSET_FACT.equalsIgnoreCase(factname)) {
                entry.setCharset(value);
            } else {
                entry.setAdditionalFact(factname, value);
            }
        }

        return entry;
    }

    /**
     * Stores date to the string representation.
     *
     * @param date date to store
     *
     * @return date string representation
     */
    private String storeDate(Date date) {
        DateFormat format = new SimpleDateFormat(((date.getTime() % 1000) == 0) ? SHORT_FORMAT : PRECISE_FORMAT);
        format.setTimeZone(TimeZone.getTimeZone("UTC"));

        return format.format(date);
    }

    /**
     * Restores date from the string.
     *
     * @param data date string
     *
     * @return restored date
     */
    private Date restoreDate(String data) {
        try {
            SimpleDateFormat format = new SimpleDateFormat(PRECISE_FORMAT);
            format.setTimeZone(TimeZone.getTimeZone("UTC"));

            return format.parse(data);
        } catch (ParseException e) {
            try {
                SimpleDateFormat format = new SimpleDateFormat(SHORT_FORMAT);
                format.setTimeZone(TimeZone.getTimeZone("UTC"));

                return format.parse(data);
            } catch (ParseException ex) {
                throw new IllegalArgumentException("invalid format");
            }
        }
    }

    /**
     * Restores type fact.
     *
     * @param value type data
     *
     * @return restored type instance
     */
    private MLSEntry.Type restoreType(String value) {
        if (MLSEntry.Type.FILE.getName().equalsIgnoreCase(value)) {
            return MLSEntry.Type.FILE;
        } else if (MLSEntry.Type.CURRENT_DIRECTORY.getName().equalsIgnoreCase(value)) {
            return MLSEntry.Type.CURRENT_DIRECTORY;
        } else if (MLSEntry.Type.PARENT_DIRECTORY.getName().equalsIgnoreCase(value)) {
            return MLSEntry.Type.PARENT_DIRECTORY;
        } else if (MLSEntry.Type.DIRECTORY.getName().equalsIgnoreCase(value)) {
            return MLSEntry.Type.DIRECTORY;
        } else {
            int equalIndex = value.indexOf(EQUAL_SIGN);
            Assert.greater(equalIndex, 0, "invalid format");
            String osName = value.substring(0, equalIndex);
            String name = value.substring(equalIndex + 1);

            return new MLSEntry.Type(osName, name);
        }
    }

    /**
     * Returns permission string.
     *
     * @param permissions permissions instance
     *
     * @return permissions string representation
     */
    private String storePermissions(MLSEntry.Permissions permissions) {
        StringBuffer buf = new StringBuffer();

        if (permissions.isFileAppendingAllowed()) {
            buf.append(FILE_APPENDING);
        }
        if (permissions.isFileCreationAllowed()) {
            buf.append(FILE_CREATION);
        }
        if (permissions.isDeletionAllowed()) {
            buf.append(DELETION);
        }
        if (permissions.isDirectoryEnteringAllowed()) {
            buf.append(DIRECTORY_ENTERING);
        }
        if (permissions.isRenamingAllowed()) {
            buf.append(RENAMING);
        }
        if (permissions.isListingAllowed()) {
            buf.append(LISTING);
        }
        if (permissions.isDirectoryCreationAllowed()) {
            buf.append(DIRECTORY_CREATION);
        }
        if (permissions.isDirectoryPurgingAllowed()) {
            buf.append(DIRECTORY_PURGING);
        }
        if (permissions.isFileReadingAllowed()) {
            buf.append(FILE_READING);
        }
        if (permissions.isFileWritingAllowed()) {
            buf.append(FILE_WRITING);
        }

        return buf.toString();
    }

    /**
     * Restores permissions.
     *
     * @param value permissions data
     *
     * @return restored permissions
     */
    private MLSEntry.Permissions restorePermissions(String value) {
        MLSEntry.Permissions permissions = new MLSEntry.Permissions();

        for (int i = 0, size = value.length(); i < size; ++i) {
            char ch = value.charAt(i);

            if (FILE_APPENDING == ch) {
                permissions.setFileAppendingAllowed(true);
            } else if (FILE_CREATION == ch) {
                permissions.setFileCreationAllowed(true);
            } else if (DELETION == ch) {
                permissions.setDeletionAllowed(true);
            } else if (DIRECTORY_ENTERING == ch) {
                permissions.setDirectoryEnteringAllowed(true);
            } else if (RENAMING == ch) {
                permissions.setRenamingAllowed(true);
            } else if (LISTING == ch) {
                permissions.setListingAllowed(true);
            } else if (DIRECTORY_CREATION == ch) {
                permissions.setDirectoryCreationAllowed(true);
            } else if (DIRECTORY_PURGING == ch) {
                permissions.setDirectoryPurgingAllowed(true);
            } else if (FILE_READING == ch) {
                permissions.setFileReadingAllowed(true);
            } else if (FILE_WRITING == ch) {
                permissions.setFileWritingAllowed(true);
            }
        }

        return permissions;
    }

}
