// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 1999-2004 Brian Wellington (bwelling@xbill.org)

package org.xbill.DNS;

/**
 * Routines for parsing BIND-style TTL values. These values consist of numbers followed by 1 letter
 * units of time (W - week, D - day, H - hour, M - minute, S - second).
 *
 * @author Brian Wellington
 */
public final class TTL {

  public static final long MAX_VALUE = 0x7FFFFFFFL;

  private TTL() {}

  static void check(long i) {
    if (i < 0 || i > MAX_VALUE) {
      throw new InvalidTTLException(i);
    }
  }

  /**
   * Parses a TTL-like value, which can either be expressed as a number or a BIND-style string with
   * numbers and units.
   *
   * @param s The string representing the numeric value.
   * @param clamp Whether to clamp values in the range [MAX_VALUE + 1, 2^32 -1] to MAX_VALUE. This
   *     should be donw for TTLs, but not other values which can be expressed in this format.
   * @return The value as a number of seconds
   * @throws NumberFormatException The string was not in a valid TTL format.
   */
  @SuppressWarnings("fallthrough")
  public static long parse(String s, boolean clamp) {
    if (s == null || s.isEmpty() || !Character.isDigit(s.charAt(0))) {
      throw new NumberFormatException();
    }
    long value = 0;
    long ttl = 0;
    for (int i = 0; i < s.length(); i++) {
      char c = s.charAt(i);
      long oldvalue = value;
      if (Character.isDigit(c)) {
        value = (value * 10) + Character.getNumericValue(c);
        if (value < oldvalue) {
          throw new NumberFormatException();
        }
      } else {
        switch (Character.toUpperCase(c)) {
          case 'W':
            value *= 7;
          case 'D':
            value *= 24;
          case 'H':
            value *= 60;
          case 'M':
            value *= 60;
          case 'S':
            break;
          default:
            throw new NumberFormatException();
        }
        ttl += value;
        value = 0;
        if (ttl > 0xFFFFFFFFL) {
          throw new NumberFormatException();
        }
      }
    }
    if (ttl == 0) {
      ttl = value;
    }

    if (ttl > 0xFFFFFFFFL) {
      throw new NumberFormatException();
    } else if (ttl > MAX_VALUE && clamp) {
      ttl = MAX_VALUE;
    }
    return ttl;
  }

  /**
   * Parses a TTL, which can either be expressed as a number or a BIND-style string with numbers and
   * units.
   *
   * @param s The string representing the TTL
   * @return The TTL as a number of seconds
   * @throws NumberFormatException The string was not in a valid TTL format.
   */
  public static long parseTTL(String s) {
    return parse(s, true);
  }

  public static String format(long ttl) {
    TTL.check(ttl);
    StringBuilder sb = new StringBuilder();
    long secs = ttl % 60;
    ttl /= 60;
    long mins = ttl % 60;
    ttl /= 60;
    long hours = ttl % 24;
    ttl /= 24;
    long days = ttl % 7;
    ttl /= 7;
    long weeks = ttl;
    if (weeks > 0) {
      sb.append(weeks).append("W");
    }
    if (days > 0) {
      sb.append(days).append("D");
    }
    if (hours > 0) {
      sb.append(hours).append("H");
    }
    if (mins > 0) {
      sb.append(mins).append("M");
    }
    if (secs > 0 || (weeks == 0 && days == 0 && hours == 0 && mins == 0)) {
      sb.append(secs).append("S");
    }
    return sb.toString();
  }
}
