package com.jscape.util.file;

import com.jscape.util.Clock;
import com.jscape.util.Time;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * @author Alex Usun
 */
public class LsFileInfoFormat {

    private static final Locale DATE_LOCALE = Locale.US;

    private static final String NEAR_DATE_FORMAT_PATTERN = "MMM dd HH:mm";
    private static final String FAR_DATE_FORMAT_PATTERN = "MMM dd yyyy";
    private static final String FULL_DATE_FORMAT_PATTERN = "MMM dd yyyy";

    private static final Time SIX_MONTHS = Time.days(183);

    public static String format(LsFileInfo value) {
        return String.format("%c%c%c%c%c%c%c%c%c%c % 3d %-8s %-8s % 8d %s %s",
                             value.fileType,
                             readPermissionChar(value.permissions.ownerCanRead),
                             writePermissionChar(value.permissions.ownerCanWrite),
                             executePermissionChar(value.permissions.ownerCanExecute),
                             readPermissionChar(value.permissions.groupCanRead),
                             writePermissionChar(value.permissions.groupCanWrite),
                             executePermissionChar(value.permissions.groupCanExecute),
                             readPermissionChar(value.permissions.otherCanRead),
                             writePermissionChar(value.permissions.otherCanWrite),
                             executePermissionChar(value.permissions.otherCanExecute),
                             value.hardLinksCount,
                             value.owner,
                             value.group,
                             value.size,
                             formatDate(value.lastModifiedDate),
                             value.filename);
    }

    public static LsFileInfo parse(String data)
            throws IOException {
        try {
            Scanner scanner = new Scanner(data).useDelimiter("\\s+");
            String permissionsString = scanner.next();
            char fileType = permissionsString.charAt(0);
            PosixFilePermissions permissions = new PosixFilePermissions(readPermission(permissionsString.charAt(1)),
                                                                        writePermission(permissionsString.charAt(2)),
                                                                        executePermission(permissionsString.charAt(3)),
                                                                        readPermission(permissionsString.charAt(4)),
                                                                        writePermission(permissionsString.charAt(5)),
                                                                        executePermission(permissionsString.charAt(6)),
                                                                        readPermission(permissionsString.charAt(7)),
                                                                        writePermission(permissionsString.charAt(8)),
                                                                        executePermission(permissionsString.charAt(9)),
                                                                        false,
                                                                        false,
                                                                        false);

            int hardLinksCount = scanner.nextInt(10);
            String owner = scanner.next().trim();
            String groupOrSize = scanner.next().trim();
            String sizeOrDate = scanner.next().trim();
            Long possibleSize = parseSize(sizeOrDate);
            String group = possibleSize != null ? groupOrSize : "";
            Long size = possibleSize != null ? possibleSize : parseSize(groupOrSize);
            long lastModifiedDate = parseDate(String.format("%s %s %s", possibleSize != null ? scanner.next() : sizeOrDate, scanner.next(), scanner.next()));
            String filename = scanner.nextLine().trim();

            return new LsFileInfo(fileType,
                                  permissions,
                                  hardLinksCount,
                                  owner,
                                  group,
                                  size != null ? size : 0L,
                                  lastModifiedDate,
                                  filename);
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    private static Long parseSize(String data) {
        try {
            return Long.valueOf(data);
        } catch (NumberFormatException e) {
            return null;
        }
    }

    private static char readPermissionChar(boolean value) {
        return value ? 'r' : '-';
    }

    private static boolean readPermission(char ch) {
        return ch == 'r';
    }

    private static char writePermissionChar(boolean value) {
        return value ? 'w' : '-';
    }

    private static boolean writePermission(char ch) {
        return ch == 'w';
    }

    private static char executePermissionChar(boolean value) {
        return value ? 'x' : '-';
    }

    private static boolean executePermission(char ch) {
        return ch == 'x';
    }

    private static String formatDate(long date) {
        DateFormat format = Clock.elapsedSince(date) < SIX_MONTHS.toMillis()
                            ? new SimpleDateFormat(NEAR_DATE_FORMAT_PATTERN, DATE_LOCALE)
                            : new SimpleDateFormat(FAR_DATE_FORMAT_PATTERN, DATE_LOCALE);

        format.setTimeZone(TimeZone.getTimeZone("GMT"));

        return format.format(new Date(date));
    }

    private static long parseDate(String data) {
        SimpleDateFormat nearDateFormat = new SimpleDateFormat(NEAR_DATE_FORMAT_PATTERN, DATE_LOCALE);
        nearDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));

        Date date = parseDate(nearDateFormat, data);

        if (date != null) {
            Calendar calendar = new GregorianCalendar();
            int currentYear = calendar.get(GregorianCalendar.YEAR);
            calendar.setTime(date);
            calendar.set(GregorianCalendar.YEAR, currentYear);
            date = calendar.getTime();
        } else {
            SimpleDateFormat farDateFormat = new SimpleDateFormat(FAR_DATE_FORMAT_PATTERN, DATE_LOCALE);
            farDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
            date = parseDate(farDateFormat, data);
        }

        return date != null ? date.getTime() : 0;
    }

    private static Date parseDate(DateFormat format, String data) {
        try {
            return format.parse(data);
        } catch (Exception e) {
            return null;
        }
    }

    private LsFileInfoFormat() {
        // empty
    }
}
