/*
 * 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 com.google.appengine.tools.mapreduce.impl;

import com.google.appengine.api.memcache.MemcacheService;

/**
 * A best effort quota service based on memcache.
 *
 *
 */
public class QuotaManager {
// --------------------------- STATIC FIELDS ---------------------------

  // memcache doesn't allow you to decrement below 0, so we add this offset
  // to all memcache values.
  private static final long OFFSET = 1L << 32;

// ------------------------------ FIELDS ------------------------------

  private final MemcacheService memcacheService;

// --------------------------- CONSTRUCTORS ---------------------------

  /**
   *
   * @param memcacheService the memcache service to use for storing quota
   */
  public QuotaManager(MemcacheService memcacheService) {
    this.memcacheService = memcacheService;
  }

// -------------------------- INSTANCE METHODS --------------------------

  /**
   * Attempts to consume the given amount of quota from the given bucket.
   * Fails there is not enough quota available to satisfy the request.
   *
   * @param bucket the name of the bucket to consume quota from
   * @param amount the amount of quota to consume
   * @return amount if there is enough quota, 0 otherwise.
   */
  public long consume(String bucket, long amount) {
    return consume(bucket, amount, false);
  }

  /**
   * Attempts to consume the given amount of quota from the given bucket.
   * If there is not enough quota available to satisfy the request:
   * <ul><li>If consumeSome is true, then consumes all available quota.
   *     <li>If consumeSome is false, doesn't consume any quota.
   * </ul>
   *
   * @param bucket the name of the bucket to consume quota from
   * @param amount the amount of quota to consume
   * @param consumeSome whether to settle for less quota than amount
   * @return the amount of quota actually consumed
   */
  public long consume(String bucket, long amount, boolean consumeSome) {
    long newQuota = memcacheService.increment(bucket, -amount, OFFSET);

    if (newQuota >= OFFSET) {
      return amount;
    }

    if (consumeSome && OFFSET - newQuota < amount) {
      put(bucket, OFFSET - newQuota);
      return amount - (OFFSET - newQuota);
    } else {
      put(bucket, amount);
      return 0L;
    }
  }

  /**
   * Get the amount of quota in the given bucket.
   *
   * @param bucket the name of the bucket to get quota from
   * @return the amount of quota in the bucket
   */
  public long get(String bucket) {
    Object amountObject = memcacheService.get(bucket);
    if (amountObject == null) {
      return 0L;
    }
    return (Long) amountObject - OFFSET;
  }

  /**
   * Add the given amount of quota to the bucket.
   *
   * @param bucket the name of the bucket to add quota to
   * @param amount the amount of quota to add
   */
  public void put(String bucket, long amount) {
    memcacheService.increment(bucket, amount, OFFSET);
  }

  /**
   * Sets the amount of quota available to the given amount.
   *
   * @param bucket the name of the bucket to set quota for
   * @param amount the amount of quota to set available
   */
  public void set(String bucket, long amount) {
    memcacheService.put(bucket, amount + OFFSET);
  }
}
