/**
 * Logback: the reliable, generic, fast and flexible logging framework.
 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
 *
 * This program and the accompanying materials are dual-licensed under
 * either the terms of the Eclipse Public License v1.0 as published by
 * the Eclipse Foundation
 *
 *   or (per the licensee's choosing)
 *
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation.
 */
package ch.qos.logback.core.util;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Duration instances represent a lapse of time. Internally, the duration is
 * stored in milliseconds. However, whenever a parameter of type Duration is expected, Joran
 * (logback's configuration system) will automatically convert strings such as "20 seconds"
 * "3.5 minutes" or "5 hours" into Duration instances.
 *
 * <p>The recognized units of time are the "millisecond", "second", "minute" "hour" and "day".
 * The unit name may be followed by an "s". Thus, "2 day" and "2 days" are equivalent. In the
 * absence of a time unit specification, milliseconds are assumed.
 * 
 * <p>Note: the conversion magic is entirely due to the fact that this class follows the
 * {@link #valueOf} convention.
 *
 * @author Ceki Gulcu
 */
public class Duration {

  private final static String DOUBLE_PART = "([0-9]*(.[0-9]+)?)";
  private final static int DOUBLE_GROUP = 1;

  private final static String UNIT_PART = "(|milli(second)?|second(e)?|minute|hour|day)s?";
  private final static int UNIT_GROUP = 3;

  private static final Pattern DURATION_PATTERN = Pattern.compile(DOUBLE_PART
      + "\\s*" + UNIT_PART, Pattern.CASE_INSENSITIVE);

  static final long SECONDS_COEFFICIENT = 1000;
  static final long MINUTES_COEFFICIENT = 60 * SECONDS_COEFFICIENT;
  static final long HOURS_COEFFICIENT = 60 * MINUTES_COEFFICIENT;
  static final long DAYS_COEFFICIENT = 24 * HOURS_COEFFICIENT;

  final long millis;

  public Duration(long millis) {
    this.millis = millis;
  }

  public static Duration buildByMilliseconds(double value) {
    return new Duration((long) (value));
  }

  public static Duration buildBySeconds(double value) {
    return new Duration((long) (SECONDS_COEFFICIENT * value));
  }

  public static Duration buildByMinutes(double value) {
    return new Duration((long) (MINUTES_COEFFICIENT * value));
  }

  public static Duration buildByHours(double value) {
    return new Duration((long) (HOURS_COEFFICIENT * value));
  }

  public static Duration buildByDays(double value) {
    return new Duration((long) (DAYS_COEFFICIENT * value));
  }

  public static Duration buildUnbounded() {
    return new Duration(Long.MAX_VALUE);
  }

  public long getMilliseconds() {
    return millis;
  }

  public static Duration valueOf(String durationStr) {
    Matcher matcher = DURATION_PATTERN.matcher(durationStr);

    if (matcher.matches()) {
      String doubleStr = matcher.group(DOUBLE_GROUP);
      String unitStr = matcher.group(UNIT_GROUP);

      double doubleValue = Double.valueOf(doubleStr);
      if (unitStr.equalsIgnoreCase("milli")
          || unitStr.equalsIgnoreCase("millisecond") || unitStr.length() == 0) {
        return buildByMilliseconds(doubleValue);
      } else if (unitStr.equalsIgnoreCase("second")
          || unitStr.equalsIgnoreCase("seconde")) {
        return buildBySeconds(doubleValue);
      } else if (unitStr.equalsIgnoreCase("minute")) {
        return buildByMinutes(doubleValue);
      } else if (unitStr.equalsIgnoreCase("hour")) {
        return buildByHours(doubleValue);
      } else if (unitStr.equalsIgnoreCase("day")) {
        return buildByDays(doubleValue);
      } else {
        throw new IllegalStateException("Unexpected " + unitStr);
      }
    } else {
      throw new IllegalArgumentException("String value [" + durationStr
          + "] is not in the expected format.");
    }
  }

  @Override
  public String toString() {
    if (millis < SECONDS_COEFFICIENT) {
      return millis + " milliseconds";
    } else if (millis < MINUTES_COEFFICIENT) {
      return millis / SECONDS_COEFFICIENT + " seconds";
    } else if (millis < HOURS_COEFFICIENT) {
      return millis / MINUTES_COEFFICIENT + " minutes";
    } else {
      return millis / HOURS_COEFFICIENT + " hours";
    }

  }
}
