package com.spun.util;

import com.spun.util.logger.SimpleLogger;

import java.util.Calendar;
import java.util.Date;

public class DateDifference
{
  public static final String[] STANDARD_TIME_TEXT = {"Year",
                                                     "Years",
                                                     "Month",
                                                     "Months",
                                                     "Week",
                                                     "Weeks",
                                                     "Day",
                                                     "Days",
                                                     "Hour",
                                                     "Hours",
                                                     "Min",
                                                     "Mins",
                                                     "Sec",
                                                     "Secs",
                                                     "Milli",
                                                     "Millis"};
  public static final String   MILLISECONDS       = "milliseconds";
  public static final String   SECONDS            = "seconds";
  public static final String   MINUTES            = "minutes";
  public static final String   HOURS              = "hours";
  public static final String   DAYS               = "days";
  public static final String   WEEKS              = "weeks";
  public static final String   MONTHS             = "months";
  public static final String   YEARS              = "years";
  public static String[]       TIME_UNITS         = {YEARS,
                                                     MONTHS,
                                                     WEEKS,
                                                     DAYS,
                                                     HOURS,
                                                     MINUTES,
                                                     SECONDS,
                                                     MILLISECONDS};
  // assumes a 30 day month
  private static int[]         TIME_SCALE         = {Calendar.YEAR,
                                                     Calendar.MONTH,
                                                     Calendar.WEEK_OF_YEAR,
                                                     Calendar.DATE,
                                                     Calendar.HOUR,
                                                     Calendar.MINUTE,
                                                     Calendar.SECOND,
                                                     Calendar.MILLISECOND};
  private static long[]        DIVIDERS           = {1000 * 60 * 60 * 24 * 365L,
                                                     1000 * 60 * 60 * 24 * 30L,
                                                     1000 * 60 * 60 * 24 * 7L,
                                                     1000 * 60 * 60 * 24L,
                                                     1000 * 60 * 60L,
                                                     1000 * 60L,
                                                     1000L,
                                                     1L};
  private long                 milli;
  public DateDifference(long timeDifference)
  {
    this.milli = timeDifference;
  }
  public DateDifference(Date date1, Date date2)
  {
    milli = date1.getTime() - date2.getTime();
    if (milli < 0)
    {
      milli = milli * -1;
    }
    //    My_System.variable("dateDifference = " + milli);
  }
  /**
   * Debugging tool.
   **/
  public static void printDividers()
  {
    SimpleLogger.variable("max long = " + Long.MAX_VALUE);
    for (int i = 0; i < DIVIDERS.length; i++)
    {
      SimpleLogger.variable("" + DIVIDERS[i]);
    }
  }
  /**
   * Gets the amount of [Units]. <BR>
   * i.e. <BR>
   * Given a  DateDifference of 2 Days 3 Hours 4 Minutes <BR>
   * getAbsoluteDifference(Calendar.DATE) = 2
   * getAbsoluteDifference(Calendar.HOUR) = 51
   **/
  public long getAbsoluteDifference(int unit)
  {
    return getAbsoluteDifference(unit, this.milli);
  }
  public long getAbsoluteDifference(String unit)
  {
    return getAbsoluteDifference(convertUnitString(unit));
  }
  public long getRoundedDifference(String unit)
  {
    return getRoundedDifference(convertUnitString(unit), this.milli);
  }
  public long getRoundedDifference(int unit)
  {
    return getRoundedDifference(unit, this.milli);
  }
  public static long getAbsoluteDifference(int unit, long time)
  {
    //    My_System.variable("divider = " + DIVIDERS[getIndex(unit)]);
    return time / DIVIDERS[getTimeScaleIndex(unit)];
  }
  public static long getRoundedDifference(int unit, long time)
  {
    return Math.round(((double) time) / DIVIDERS[getTimeScaleIndex(unit)]);
  }
  /**
   * Gets the remaining amount of [Units]. <BR>
   * i.e. <BR>
   * Given a  DateDifference of 1 Month 2 Weeks 3 Days,
   * getRemainingDifference(days, months) = 17 days <BR>
   * getRemainingDifference(days, weeks) = 3 days <BR>
   **/
  public long getRemainingDifference(int wantedUnit, int roundTo)
  {
    return getRemainingDifference(wantedUnit, roundTo, this.milli);
  }
  public static long getRemainingDifference(int wantedUnit, int roundTo, long time)
  {
    int wantedIndex = getTimeScaleIndex(wantedUnit);
    int roundToIndex = getTimeScaleIndex(roundTo);
    if (wantedIndex < roundToIndex)
    {
      throw new Error(
          "Can't round with a smaller Unit.[" + TIME_UNITS[wantedIndex] + ", " + TIME_UNITS[roundToIndex] + "]");
    }
    //roundedDifference = (millis % (roundTo in millis)) / (unit in millis)
    return (time % DIVIDERS[roundToIndex]) / DIVIDERS[wantedIndex];
  }
  /**
   * @return the index in TIME_SCALE[] of the largest Unit to be > 0
   **/
  private int getMaximumTimeUnit()
  {
    int i = 0;
    while ((i < DIVIDERS.length) && (milli < DIVIDERS[i]))
    {
      i++;
    }
    return i;
  }
  /**
   * Finds the index for a Calendar.DATE ect.
   * @return the index in TIME_SCALE[]
   **/
  public static int getTimeScaleIndex(int calendarTime)
  {
    int i = 0;
    while (calendarTime != TIME_SCALE[i])
    {
      i++;
    }
    return i;
  }
  public static int convertUnitString(String unit)
  {
    int result = 0;
    if (MILLISECONDS.equalsIgnoreCase(unit))
    {
      result = Calendar.MILLISECOND;
    }
    else if (SECONDS.equalsIgnoreCase(unit))
    {
      result = Calendar.SECOND;
    }
    else if (MINUTES.equalsIgnoreCase(unit))
    {
      result = Calendar.MINUTE;
    }
    else if (HOURS.equalsIgnoreCase(unit))
    {
      result = Calendar.HOUR;
    }
    else if (DAYS.equalsIgnoreCase(unit))
    {
      result = Calendar.DATE;
    }
    else if (WEEKS.equalsIgnoreCase(unit))
    {
      result = Calendar.WEEK_OF_YEAR;
    }
    else if (MONTHS.equalsIgnoreCase(unit))
    {
      result = Calendar.MONTH;
    }
    else if (YEARS.equalsIgnoreCase(unit))
    {
      result = Calendar.YEAR;
    }
    return result;
  }
  public static long convertUnitStringToMilli(String unit)
  {
    int cal = convertUnitString(unit);
    return DIVIDERS[getTimeScaleIndex(cal)];
  }
  public boolean isMoreThan(int amount, int unit)
  {
    return (getAbsoluteDifference(unit) >= amount);
  }
  public boolean isMoreThan(int amount, String unitString)
  {
    return (getAbsoluteDifference(convertUnitString(unitString)) >= amount);
  }
  public long getStandardRoundedTime(int unitIndex, boolean forceAbsolute)
  {
    return getStandardRoundedTime(unitIndex, forceAbsolute, this.milli);
  }
  private static long getStandardRoundedTime(int unitIndex, boolean forceAbsolute, long time)
  {
    if (unitIndex == 0 || forceAbsolute)
    {
      return getAbsoluteDifference(TIME_SCALE[unitIndex], time);
    }
    else
    {
      return getRemainingDifference(TIME_SCALE[unitIndex], TIME_SCALE[unitIndex - 1], time);
    }
  }
  public String getStandardTimeText(int amount, String maxUnit, String minUnit, String nowText, String agoText)
  {
    return getTimeText(amount, convertUnitString(maxUnit), convertUnitString(minUnit), nowText, agoText,
        STANDARD_TIME_TEXT);
  }
  public String getTimeText(int amount, int maxUnit, int minUnit, String nowText, String agoText, String[] units)
  {
    //		My_System.variable("amount = " + amount + ", maxUnit = " + maxUnit +  ", minUnit = " + minUnit + ", nowText = " + nowText);
    if (amount == 0)
    { throw new Error("getTimeText() requires amount > 0"); }
    maxUnit = getTimeScaleIndex(maxUnit);
    minUnit = getTimeScaleIndex(minUnit);
    int realMax = getMaximumTimeUnit();
    String timeText = nowText;
    if (realMax < maxUnit)
    {
      realMax = maxUnit;
    }
    if (realMax <= minUnit)
    {
      timeText = "";
      long remainingTime = this.milli;
      for (int i = realMax; i < (realMax + amount) && (i <= minUnit); i++)
      {
        long time = getStandardRoundedTime(i, i == realMax, remainingTime);
        remainingTime -= time * DIVIDERS[i];
        timeText += time + " " + units[(time == 1) ? i * 2 : i * 2 + 1] + ", ";
        //My_System.variable("timeText = " + timeText);
      }
      timeText = timeText.substring(0, timeText.length() - 2);
      if ((agoText != null) && (agoText.length() > 0))
      {
        timeText += " " + agoText;
      }
    }
    return timeText;
  }
  public String getClockTimeText()
  {
    int maxUnit = getTimeScaleIndex(Calendar.HOUR);
    int minUnit = getTimeScaleIndex(Calendar.MILLISECOND);
    int realMax = getMaximumTimeUnit();
    realMax = (realMax < maxUnit) ? maxUnit : realMax;
    String timeText = "";
    if (realMax <= minUnit)
    {
      for (int i = realMax; i <= minUnit; i++)
      {
        int padding = (i == getTimeScaleIndex(Calendar.MILLISECOND)) ? 3 : 2;
        long time = getStandardRoundedTime(i, i == realMax);
        if (i == realMax)
        {
          timeText += time + ":";
        }
        else
        {
          timeText += StringUtils.padNumber(time, padding) + ":";
        }
      }
      timeText = timeText.substring(0, timeText.length() - 1);
    }
    return timeText;
  }
  public String getStandardTimeText(int amountShown)
  {
    return getTimeText(amountShown, Calendar.YEAR, Calendar.MILLISECOND, "now", "", STANDARD_TIME_TEXT);
  }
}
