package com.n2bb.util;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.StringTokenizer;

/**
 * Utility methods for formatting.
 *
 * @author kmatsuoka
 */
public class Formats {
    /**
     * Gets a run time in hh:mm:ss format from a run time in seconds.
     *
     * @param runTime run time in seconds
     * @return run time in hh:mm:ss format.
     */
    public static String getRunTime(Long runTime) {
        return runTime == null ? "" : getRunTime(runTime.longValue());
    }

    /**
     * Gets a run time in hh:mm:ss format from a run time in seconds.
     *
     * @param runTime run time in seconds
     * @return run time in hh:mm:ss format.
     */
    public static String getRunTime(long runTime) {
        /* Decimal formats aren't thread-safe, so we either create a new one
           each time, or synchronize this method. */
        DecimalFormat format = new DecimalFormat("00");
        int hours = (int) runTime / 3600;
        int minutes = (int) (runTime - hours * 3600) / 60;
        int seconds = (int) runTime % 60;

        return format.format(hours) + ":" + format.format(minutes) + ":" + format.format(seconds);
    }

    /**
     * Gets a run time in seconds from a string formatted as hh:mm:ss.
     *
     * @param runTimeString run time hh:mm:ss formatted string
     * @return run time in seconds
     */
    public static long getRunTimeSeconds(String runTimeString, boolean hoursMinutesOnly)
            throws RuntimeException {
        StringTokenizer tokenizer = new StringTokenizer(runTimeString, ":");

        if ((tokenizer.countTokens() > 3) || (tokenizer.countTokens() < 1)) {
            throw new RuntimeException("bad runtime format");
        }

        long hours = 0;
        long minutes = 0;
        long seconds = 0;

        if (hoursMinutesOnly) {
            hours = Long.parseLong(tokenizer.nextToken());
            minutes = Long.parseLong(tokenizer.nextToken());
        }
        else {
            if (tokenizer.countTokens() == 3) {
                hours = Long.parseLong(tokenizer.nextToken());
            }

            if (tokenizer.countTokens() == 2) {
                minutes = Long.parseLong(tokenizer.nextToken());
            }

            if (tokenizer.countTokens() == 1) {
                seconds = Long.parseLong(tokenizer.nextToken());
            }
        }

        long runTime = (hours * 3600) + (minutes * 60) + seconds;

        return runTime;
    }

    /**
     * Gets a string, truncated with ellipsis if too long.
     *
     * @param str       string to possibly truncate
     * @param maxLength maximum length not to truncate
     * @return          string, truncated if too long
     */
    public static String getTruncated(String str, int maxLength) {
        if (str != null && str.length() > maxLength) {
            return str.substring(0, maxLength) + "...";
        }
        else {
            return str;
        }
    }

    private static final double kilo = 1024;
    private static final double mega = kilo * kilo;
    private static final double giga = mega * kilo;

    /**
     * Gets a file size in appropriate units (KB, MB, GB).
     *
     * @param bytes     file size in bytes
     * @return          file size in appropriate units
     */
    public static String getFileSize(long bytes) {
        if (bytes < kilo) {
            return bytes + " bytes";
        }
        else if (bytes < mega) {
            return formatFileSize(bytes / kilo) + " KB";
        }
        else if (bytes < giga) {
            return formatFileSize(bytes / mega) + " MB";
        }
        else {
            return formatFileSize(bytes / giga) + " GB";
        }
    }

    private static String formatFileSize(double size) {
        int maxFractionDigits = 0;

        if (size < 10) {
            maxFractionDigits = 2;
        }
        else if (size < 100) {
            maxFractionDigits = 1;
        }

        NumberFormat format = NumberFormat.getInstance();
        format.setMaximumFractionDigits(maxFractionDigits);

        return format.format(size);
    }

    /**
     * Gets price in cents from a string representing dollars and cents,
     * such as $3.21 or 3.21.
     *
     * @param dollarsCents  string representing dollars and cents
     * @return              cents
     * @throws NumberFormatException
     */
    public static int getCents(String dollarsCents) throws NumberFormatException {
        if (dollarsCents.startsWith("$")) { // TODO: localize?
            dollarsCents = dollarsCents.substring(1);
        }
        return Math.round(Float.parseFloat(dollarsCents) * 100);
    }

    /**
     * Gets price in dollars and cents.
     *
     * @param cents     price in cents
     * @return          price in dollars and cents
     */
    public static String getDollarsCents(int cents) {
        NumberFormat numberFormat = getPriceFormat();
        return numberFormat.format((double) cents / 100.0);
    }

    /**
     * Gets a price format that has two fractional digits, e.g. 3.21.
     *
     * @return price format that has two fractional digits
     */
    private static NumberFormat getPriceFormat() {
        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMinimumFractionDigits(2);
        numberFormat.setMaximumFractionDigits(2);
        return numberFormat;
    }

    /**
     * Checks if a price is in a valid format.
     *
     * @param price     price
     * @return          true if format is valid
     */
    public static boolean isValidPrice(String price) {
        try {
            getCents(price);
            return true;
        }
        catch (NumberFormatException e) {
            return false;
        }
    }
}
