/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */


#ifndef PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_
#define PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_

#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"

namespace net_instaweb {

/* This class helps manage a reference count stored in an object where
 * references can be classified into separate types, to further check their use
 * and help in debugging. You would normally store an instance of
 * CategorizedRefcount in the object being managed.
 *
 * There are the following requirements on ObjectType:
 *   void LastRefRemoved();  // called when refcount goes to 0, with
 *                           // mutex_ held.
 *   StringPiece RefCategoryName(EnumType);
 *   const int kNumRefCategories which bounds the EnumType
 *
 * For example, you might have something like this (omitting RefCategoryName
 * implementation, which is only needed for DebugString()):
 *
 * class AsyncDoerOfThings() {
 *  public:
 *   AsyncDoerOfThings() : ref_counts_(this) {
 *     ...
 *     ref_counts_.set_mutex(mutex_.get());
 *   }
 *
 *   void ref() { ref_counts_.AddRef(kRefExternal); }
 *   void deref() { ref_counts_.ReleaseRef(kRefExternal); }
 *   void AsyncOp() {
 *     ref_counts_.AddRef(kRefInternal);
 *     DoSomeRpcOp(this, &AsyncDoerOfThings::AsyncOpComplete);
 *   }
 *
 *  private:
 *   void AsyncOpComplete() { ref_counts_.ReleaseRef(kRefInternal); }
 *   void LastRefRemoved() { delete this; }
 *
 *   enum RefCategory {
 *     kRefExternal,
 *     kRefInternal,
 *     kNumRefCategories
 *   };
 *   friend class CategorizedRefcount<AsyncDoerOfThings, RefCategory>;
 *   CategorizedRefcount<AsyncDoerOfThings, RefCategory> ref_counts_;
 *
 * };
 *
 * TODO(morlovich): Consider having a cap per kind, too? Some are meant to be
 * 0-1 only.
 */
template<typename ObjectType, typename EnumType>
class CategorizedRefcount {
 public:
  // Note: set_mutex must be called before calling any other method on this
  // class.
  // TODO(jud): Instead of holding the mutex in this class, pass in the mutex to
  // each function so that thread safety annotation can be used.
  explicit CategorizedRefcount(ObjectType* object)
      : total_refcount_(0), object_(object), mutex_(NULL) {
    for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
      ref_counts_[i] = 0;
    }
  }

  // Sets the mutex that should be held when manipulating reference count
  // of this object. Does not take ownership.
  void set_mutex(AbstractMutex* mutex) {
    mutex_ = mutex;
  }

  void AddRef(EnumType category) {
    ScopedMutex hold(mutex_);
    AddRefMutexHeld(category);
  }

  void AddRefMutexHeld(EnumType category) {
    mutex_->DCheckLocked();
    DCHECK_LE(0, category);
    DCHECK_LT(category, ObjectType::kNumRefCategories);
    ++ref_counts_[category];
    ++total_refcount_;
  }

  void ReleaseRef(EnumType category) {
    ScopedMutex hold(mutex_);
    ReleaseRefMutexHeld(category);
  }

  void ReleaseRefMutexHeld(EnumType category) {
    mutex_->DCheckLocked();
    DCHECK_LE(0, category);
    DCHECK_LT(category, ObjectType::kNumRefCategories);
    --ref_counts_[category];
    DCHECK_GE(ref_counts_[category], 0) << category;
    --total_refcount_;
    if (total_refcount_ == 0) {
      object_->LastRefRemoved();
    }
  }

  // QueryCount w/o mutex held externally makes no sense, since there
  // would be no way of using the data.

  int QueryCountMutexHeld(EnumType category) const {
    return ref_counts_[category];
  }

  GoogleString DebugString() const {
    ScopedMutex hold(mutex_);
    return DebugStringMutexHeld();
  }

  GoogleString DebugStringMutexHeld() const {
    mutex_->DCheckLocked();

    GoogleString out;
    for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
      StrAppend(&out, "\t", object_->RefCategoryName(static_cast<EnumType>(i)),
                ": ", IntegerToString(ref_counts_[i]));
    }
    return out;
  }

  void DCheckAllCountsZero() {
    ScopedMutex hold(mutex_);
    DCheckAllCountsZeroMutexHeld();
  }

  void DCheckAllCountsZeroMutexHeld() {
    mutex_->DCheckLocked();
    DCHECK_EQ(0, total_refcount_);
    for (int i = 0; i < ObjectType::kNumRefCategories; ++i) {
      DCHECK_EQ(0, ref_counts_[i]);
    }
  }

 private:
  int ref_counts_[ObjectType::kNumRefCategories];
  int total_refcount_;
  ObjectType* object_;
  AbstractMutex* mutex_;
  DISALLOW_COPY_AND_ASSIGN(CategorizedRefcount);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_KERNEL_UTIL_CATEGORIZED_REFCOUNT_H_
