// Copyright (C) 2017 The Android Open Source Project
//
// 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 com.googlesource.gerrit.plugins.quota;

import com.google.common.collect.ArrayTable;
import com.google.common.collect.Table;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.ConfigEnum;
import org.eclipse.jgit.lib.Config.SectionParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AccountLimitsConfig {
  private static final int DEFAULT_BURST_COUNT = 30;
  private static final int DEFAULT_INTERVAL_SECONDS = 60;
  private static final Logger log =
      LoggerFactory.getLogger(AccountLimitsConfig.class);
  static final String GROUP_SECTION = "group";
  static final SectionParser<AccountLimitsConfig> KEY =
      new SectionParser<AccountLimitsConfig>() {
        @Override
        public AccountLimitsConfig parse(final Config cfg) {
          return new AccountLimitsConfig(cfg);
        }
      };

  public static class RateLimit {
    public Type getType() {
      return type;
    }

    public double getRatePerSecond() {
      return ratePerSecond;
    }

    public int getMaxBurstSeconds() {
      return maxBurstSeconds;
    }

    private Type type;
    private double ratePerSecond;
    private int maxBurstSeconds;

    public RateLimit(Type type, double ratePerSecond, int maxBurstSeconds) {
      this.type = type;
      this.ratePerSecond = ratePerSecond;
      this.maxBurstSeconds = maxBurstSeconds;
    }
  }

  public static enum Type implements ConfigEnum {
    UPLOADPACK;

    @Override
    public String toConfigValue() {
      return name().toLowerCase(Locale.ROOT);
    }

    @Override
    public boolean matchConfigValue(String in) {
      return name().equalsIgnoreCase(in);
    }
  }

  private Table<Type, String, RateLimit> rateLimits;

  private AccountLimitsConfig(final Config c) {
    Set<String> groups = c.getSubsections(GROUP_SECTION);
    if (groups.size() == 0) {
      return;
    }
    rateLimits = ArrayTable.create(Arrays.asList(Type.values()), groups);
    for (String groupName : groups) {
      Type type = Type.UPLOADPACK;
      rateLimits.put(type, groupName,
          parseRateLimit(c, groupName, type));
    }
  }

  RateLimit parseRateLimit(Config c, String groupName, Type type) {
    String name = type.toConfigValue();
    String value = c.getString(GROUP_SECTION, groupName, name).trim();
    if (value == null) {
      return defaultRateLimit(type);
    }

    Matcher m = Pattern.compile("^\\s*(\\d+)\\s*/\\s*(.*)\\s*burst\\s*(\\d+)$")
        .matcher(value);
    if (!m.matches()) {
      log.warn(
          "Invalid ''{}'' ratelimit configuration ''{}'', use default ratelimit {}/hour",
          name, value, 3600.0D / DEFAULT_INTERVAL_SECONDS);
      return defaultRateLimit(type);
    }

    String digits = m.group(1);
    String unitName = m.group(2).trim();
    String storeCountString = m.group(3).trim();
    long burstCount = DEFAULT_BURST_COUNT;
    try {
      burstCount = Long.parseLong(storeCountString);
    } catch (NumberFormatException e) {
      log.warn(
          "Invalid ''{}'' ratelimit store configuration ''{}'', use default burst count ''{}''",
          name, storeCountString, burstCount);
    }

    TimeUnit inputUnit = TimeUnit.HOURS;
    double ratePerSecond = 1.0D / DEFAULT_INTERVAL_SECONDS;
    if (unitName.isEmpty()) {
      inputUnit = TimeUnit.SECONDS;
    } else if (match(unitName, "s", "sec", "second")) {
      inputUnit = TimeUnit.SECONDS;
    } else if (match(unitName, "m", "min", "minute")) {
      inputUnit = TimeUnit.MINUTES;
    } else if (match(unitName, "h", "hr", "hour")) {
      inputUnit = TimeUnit.HOURS;
    } else if (match(unitName, "d", "day")) {
      inputUnit = TimeUnit.DAYS;
    } else {
      logNotRateUnit(GROUP_SECTION, groupName, name, value);
    }
    try {
      ratePerSecond = 1.0D * Long.parseLong(digits)
          / TimeUnit.SECONDS.convert(1, inputUnit);
    } catch (NumberFormatException nfe) {
      logNotRateUnit(GROUP_SECTION, groupName, unitName, value);
    }

    int maxBurstSeconds = (int) (burstCount / ratePerSecond);
    return new RateLimit(type, ratePerSecond, maxBurstSeconds);
  }

  private static boolean match(final String a, final String... cases) {
    for (final String b : cases) {
      if (b != null && b.equalsIgnoreCase(a)) {
        return true;
      }
    }
    return false;
  }

  private void logNotRateUnit(String section, String subsection, String name,
      String valueString) {
    if (subsection != null) {
      log.error(MessageFormat.format("Invalid rate unit value: {0}.{1}.{2}={3}",
          section, subsection, name, valueString));
    } else {
      log.error(MessageFormat.format("Invalid rate unit value: {0}.{1}={2}",
          section, name, valueString));
    }
  }

  private RateLimit defaultRateLimit(Type type) {
    return new RateLimit(type, 1.0D / DEFAULT_INTERVAL_SECONDS,
        DEFAULT_INTERVAL_SECONDS * DEFAULT_BURST_COUNT);
  }

  /**
   * @param type type of rate limit
   * @return map of rate limits per group name
   */
  Optional<Map<String, RateLimit>> getRatelimits(Type type) {
    if (rateLimits != null) {
      return Optional.ofNullable(rateLimits.row(type));
    }
    return Optional.empty();
  }
}

@@ -4120,81 +4120,8 @@
  if 
-(unitName.isEmpty()) %7B%0A      inputUnit = TimeUnit.SECONDS;%0A    %7D else if 
 (mat
@@ -4335,16 +4335,38 @@
  %22hour%22)
+ %7C%7C unitName.isEmpty()
 ) %7B%0A    

Use hours as default time unit value