/**
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package org.waveprotocol.wave.client.common.util;

import com.google.gwt.core.client.Duration;
import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.DateTimeFormat;

import java.util.Date;

/**
 * Date formatting utilities.
 *
 */
@SuppressWarnings("deprecation")  // GWT supports Date.getXXX, but java doesn't anymore
public final class DateUtils {

  private final static long SEC_MS = 1000;
  private final static long MIN_MS = 60 * SEC_MS;
  private final static long HOUR_MS = 60 * MIN_MS;

  // Singleton class.
  private DateUtils() { }
  private static final DateUtils INSTANCE = new DateUtils();

  /**
   * Please avoid invoking getInstance() inside methods but rather inject the
   * instance into the class through the constructor. This will
   *   - make it easier to move to GIN in the future, and
   *   - make the class easier to test since DateUtils needs to be mocked out
   *     in non-GWT tests.
   *
   * @return the shared DateUtils instance
   */
  public static DateUtils getInstance() {
    return INSTANCE;
  }

  /**
   * Formats a date in the past, taking into account how long ago it was
   *
   * @param time The date to format, in ms since whenever
   * @return The formatted date
   */
  public String formatPastDate(long time) {
    return formatPastDate(new Date(time), new Date());
  }

  private static DateTimeFormat monthDayFormat;

  private static DateTimeFormat getMonthDayFormat() {
    if (monthDayFormat == null) {
      monthDayFormat = DateTimeFormat.getFormat(DateTimeFormat.PredefinedFormat.MONTH_ABBR_DAY);
    }
    return monthDayFormat;
  }

  /**
   * Package-private version, takes a fixed "now" time - used for testing
   */
  String formatPastDate(Date date, Date now) {

    // NOTE(zdwang): For now skip it for junit code; also see formatDateTime()
    if (!GWT.isClient()) {
      return "formatPastDate is not yet implemented in unit test code";
    }

    if (!isValid(date, now)) {
      GWT.log("formatPastDate can only format time in the past, trying anyway", null);
    }

    if (isRecent(date, now) || onSameDay(date, now)) {
      return DateTimeFormat.getShortTimeFormat().format(date).toLowerCase();  // AM/PM -> am/pm
    } else if (isSameYear(date, now)) {
      return getMonthDayFormat().format(date);
    } else {
      return DateTimeFormat.getMediumDateFormat().format(date);
    }
  }

  /**
   * Formats the specified date and time as a String.
   */
  public String formatDateTime(Date date) {
    // NOTE(zdwang): For now skip it for junit code; also see formatPastDate()
    if (!GWT.isClient()) {
      return "formatDateTime is not yet implemented in unit test code";
    }


    // AM/PM -> am/pm for consistency with formatPastDate()
    return DateTimeFormat.getShortDateTimeFormat().format(date).toLowerCase();
  }

  /**
   * @return true if a date is approximately in the past (i.e., before a
   *         minute in the future).
   */
  private boolean isValid(Date date, Date now) {
    long diff = (now.getTime() + MIN_MS) - date.getTime();
    return diff >= 0;
  }

  /**
   * @return true if a duration is less than six hours.
   */
  private boolean isRecent(Date date, Date now) {
   return (now.getTime() - date.getTime()) < 6 * HOUR_MS;
  }

 /**
  * @return true if a date occurs on the same day as today.
  */
  private boolean onSameDay(Date date, Date now) {
    return (date.getDate() == now.getDate())
        && (date.getMonth() == now.getMonth())
        && (date.getYear() == now.getYear());
  }

  /**
   * @return true if a date occurs in the same year as this year.
   */
  private boolean isSameYear(Date date, Date now) {
    return date.getYear() == now.getYear();
  }

  /**
   * This is used to get a efficient time for JS.
   * Warning! Use TimerService if you want to actually test and control the time.
   */
  public double currentTimeMillis() {
    // Use an optimised time for JS when running in JS.
    if (!GWT.isClient()) {
      return System.currentTimeMillis();
    } else {
      return Duration.currentTimeMillis();
    }
  }
}

@@ -1106,16 +1106,67 @@
  MIN_MS;
+%0A  private final static long DAY_MS = 24 * HOUR_MS;
 %0A%0A  // S
@@ -2728,16 +2728,29 @@
 ate, now
+, 6 * HOUR_MS
 ) %7C%7C onS
@@ -2787,90 +2787,148 @@
 urn 
-D
+form
 at
-e
 Time
-Format.getShortTimeFormat().format(date).toLowerCase();  // AM/PM -%3E am/pm
+(date);%0A    %7D else if (isRecent(date, now, 30 * DAY_MS)) %7B%0A      return getMonthDayFormat().format(date) + %22 %22 + formatTime(date);
 %0A   
@@ -3095,24 +3095,446 @@
 %0A    %7D%0A  %7D%0A%0A
+  /**%0A   * Formats the specified time as a String.%0A   */%0A  public String formatTime(Date date) %7B%0A    // NOTE(zdwang): For now skip it for junit code; also see formatPastDate()%0A    if (!GWT.isClient()) %7B%0A      return %22formatDateTime is not yet implemented in unit test code%22;%0A    %7D%0A%0A    // AM/PM -%3E am/pm for consistency with formatPastDate()%0A    return DateTimeFormat.getShortTimeFormat().format(date).toLowerCase();%0A  %7D%0A%0A
   /**%0A   * F
@@ -3816,17 +3816,16 @@
 %0A    %7D%0A%0A
-%0A
     // A
@@ -4271,16 +4271,11 @@
 han 
-six hour
+x m
 s.%0A 
@@ -4321,24 +4321,33 @@
 te, Date now
+, long ms
 ) %7B%0A   retur
@@ -4383,27 +4383,18 @@
 me()) %3C 
-6 * HOUR_MS
+ms
 ;%0A  %7D%0A%0A 

Add an extra date formatting category between same day and same year, by ali@lown.me.uk https://reviews.apache.org/r/3425