/*
 * 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.
 */


#include "pagespeed/opt/http/property_cache.h"

#include <algorithm>
#include <utility>

#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/callback.h"
#include "pagespeed/kernel/base/stl_util.h"
#include "pagespeed/kernel/base/timer.h"
#include "pagespeed/kernel/cache/cache_stats.h"
#include "pagespeed/opt/http/abstract_property_store_get_callback.h"
#include "pagespeed/opt/http/property_cache.pb.h"
#include "pagespeed/opt/http/property_store.h"
#include "pagespeed/opt/logging/log_record.h"

namespace net_instaweb {

namespace {

const int kDefaultMutationsPer1000WritesThreshold = 300;

// http://stackoverflow.com/questions/109023/
// best-algorithm-to-count-the-number-of-set-bits-in-a-32-bit-integer
//
// Note that gcc has __builtin_popcount(i) and we could conceivably
// exploit that.  Also note that g++ does not seem to support that on
// MacOS.  In the future we could consider switching to that
// implementation if this ever was measured as a performance
// bottleneck.
inline uint32 NumberOfSetBits32(uint32 i) {
  uint32 num_set = i - ((i >> 1) & 0x55555555);
  num_set = (num_set & 0x33333333) + ((num_set >> 2) & 0x33333333);
  return (((num_set + (num_set >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24;
}

inline int NumberOfSetBits64(uint64 i) {
  return (NumberOfSetBits32(static_cast<uint32>(i)) +
          NumberOfSetBits32(static_cast<uint32>(i >> 32)));
}

}  // namespace

PropertyCache::PropertyCache(PropertyStore* property_store,
                             Timer* timer,
                             Statistics* stats,
                             ThreadSystem* threads)
    : property_store_(property_store),
      timer_(timer),
      stats_(stats),
      thread_system_(threads),
      mutations_per_1000_writes_threshold_(
          kDefaultMutationsPer1000WritesThreshold),
      enabled_(true) {
}

PropertyCache::~PropertyCache() {
  STLDeleteValues(&cohorts_);
}

void PropertyPage::AddValueFromProtobuf(
    const PropertyCache::Cohort* cohort,
    const PropertyValueProtobuf& pcache_value) {
  ScopedMutex lock(mutex_.get());
  CohortDataMap::iterator cohort_itr = cohort_data_map_.find(cohort);
  CHECK(cohort_itr != cohort_data_map_.end());
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  PropertyMap* pmap = &pmap_struct->pmap;
  PropertyValue* property = (*pmap)[pcache_value.name()];
  if (property == NULL) {
    property = new PropertyValue;
    (*pmap)[pcache_value.name()] = property;
    log_record()->AddFoundPropertyToCohortInfo(
        page_type_, cohort->name(), pcache_value.name());
  }
  pmap_struct->has_value = true;
  property->InitFromProtobuf(pcache_value);
}

void PropertyPage::SetupCohorts(
    const PropertyCache::CohortVector& cohort_list) {
  for (int j = 0, n = cohort_list.size(); j < n; ++j) {
    const PropertyCache::Cohort* cohort = cohort_list[j];
    PropertyMapStruct* pmap_struct = new PropertyMapStruct(log_record());
    cohort_data_map_[cohort] = pmap_struct;
  }
}

bool PropertyPage::EncodePropertyCacheValues(
    const PropertyCache::Cohort* cohort, PropertyCacheValues* values) {
  ScopedMutex lock(mutex_.get());
  CohortDataMap::const_iterator p = cohort_data_map_.find(cohort);
  if (p == cohort_data_map_.end()) {
    return false;
  }

  bool ret = false;
  PropertyMap* pmap = &p->second->pmap;
  for (PropertyMap::iterator p = pmap->begin(), e = pmap->end();
       p != e; ++p) {
    PropertyValue* property = p->second;
    PropertyValueProtobuf* pcache_value = property->protobuf();
    if (pcache_value->name().empty()) {
      pcache_value->set_name(p->first);
    }

    // Why might the value be empty? If a cache lookup is performed, misses,
    // and UpdateValue() is never called. In this case, we can skip the
    // write.
    if (!pcache_value->body().empty()) {
      *values->add_value() = *pcache_value;
      ret = true;
    }
  }
  return ret;
}

StringPiece PropertyPage::PageTypeSuffix(PageType type) {
  switch (type) {
  case kPropertyCachePage:
  case kPropertyCacheFallbackPage:
    return "";
  case kPropertyCachePerOriginPage:
    return "@PerSite";
  }
  LOG(DFATAL) << "Weird property page type:" << type;
  return "";
}

bool PropertyPage::HasPropertyValueDeleted(
    const PropertyCache::Cohort* cohort) {
  bool ret = false;
  {
    ScopedMutex lock(mutex_.get());
    CohortDataMap::const_iterator p = cohort_data_map_.find(cohort);
    if (p != cohort_data_map_.end()) {
      ret = p->second->has_deleted_property;
    }
  }
  return ret;
}

PropertyValue::PropertyValue()
  : proto_(new PropertyValueProtobuf),
    changed_(true),
    valid_(false),
    was_read_(false) {
}

PropertyValue::~PropertyValue() {
}

void PropertyValue::InitFromProtobuf(const PropertyValueProtobuf& value) {
  proto_.reset(new PropertyValueProtobuf(value));
  changed_ = false;
  valid_ = true;
  was_read_ = true;
}

void PropertyValue::SetValue(const StringPiece& value, int64 now_ms) {
  if (!valid_ || (value != proto_->body())) {
    valid_ = true;
    changed_ = true;
    value.CopyToString(proto_->mutable_body());
  }
  proto_->set_update_mask((proto_->update_mask() << 1) | changed_);
  proto_->set_num_writes(proto_->num_writes() + 1);
  proto_->set_write_timestamp_ms(now_ms);
}

StringPiece PropertyValue::value() const {
  return StringPiece(proto_->body());
}

int64 PropertyValue::write_timestamp_ms() const {
  return proto_->write_timestamp_ms();
}

// TODO(hujie): Remove Read after all original Read calls  are changed to
// ReadWithCohorts.
void PropertyCache::Read(PropertyPage* page) const {
  ReadWithCohorts(cohort_list_, page);
}

void PropertyCache::ReadWithCohorts(const CohortVector& cohort_list,
                                    PropertyPage* page) const {
  if (!enabled_ || cohort_list.empty()) {
    page->Abort();
    return;
  }
  page->Read(cohort_list);
}

void PropertyPage::Abort() {
  CallDone(false);
}

void PropertyPage::Read(const PropertyCache::CohortVector& cohort_list) {
  DCHECK(!cohort_list.empty());
  DCHECK(property_store_callback_ == NULL);
  SetupCohorts(cohort_list);
  property_cache_->property_store()->Get(
      url_,
      options_signature_hash_,
      cache_key_suffix_,
      cohort_list,
      this,
      NewCallback(this, &PropertyPage::CallDone),
      &property_store_callback_);
}

bool PropertyValue::IsStable(int mutations_per_1000_threshold) const {
  // We allocate a 64-bit mask to record whether recent calls to Write
  // actually changed the data.  So although we keep a total number of
  // Writes that is not clamped to 64, we need to clamp between 1-64 so
  // we can use this as a divisor to determine stability.
  int num_writes = std::max(static_cast<int64>(1),
                            std::min(static_cast<int64>(64),
                                     static_cast<int64>(proto_->num_writes())));
  int num_changes = NumberOfSetBits64(proto_->update_mask());
  int changes_per_1000_writes = (1000 * num_changes) / num_writes;
  return (changes_per_1000_writes < mutations_per_1000_threshold);
}

bool PropertyValue::IsRecentlyConstant(int num_writes_unchanged) const {
  int num_pcache_writes = proto_->num_writes();
  if (num_writes_unchanged > 64) {
    // We track at most 64 writes in update_mask.
    return false;
  }
  // If we have not seen num_writes_unchanged writes then just check whether all
  // the writes were for the same value.
  if (num_writes_unchanged > num_pcache_writes) {
    num_writes_unchanged = num_pcache_writes;
  }
  uint64 update_mask =  proto_->update_mask();
  // Check if the index of least set bit for update_mask is >=
  // num_writes_unchanged. OR all writes are for the same value.
  return !IsIndexOfLeastSetBitSmaller(update_mask, num_writes_unchanged) ||
      (update_mask == 0);
}

bool PropertyValue::IsIndexOfLeastSetBitSmaller(uint64 value, int index) {
  uint64 check_mask = static_cast<uint64>(1) << std::max(index - 1, 0);
  return ((value & ~(value - 1)) < check_mask);
}

bool PropertyCache::IsExpired(const PropertyValue* property_value,
                              int64 ttl_ms) const {
  DCHECK(property_value->has_value());
  int64 expiration_time_ms = property_value->write_timestamp_ms() + ttl_ms;
  return timer_->NowMs() > expiration_time_ms;
}

const PropertyCache::Cohort* PropertyCache::AddCohort(
    const StringPiece& cohort_name) {
  PropertyCache::Cohort* cohort = new PropertyCache::Cohort(cohort_name);
  std::pair<CohortMap::iterator, bool> insertions = cohorts_.insert(
        make_pair(cohort->name(), static_cast<Cohort*>(NULL)));
  CHECK(insertions.second) << cohort->name() << " is added twice.";
  insertions.first->second = cohort;
  cohort_list_.push_back(insertions.first->second);
  return cohort;
}

const PropertyCache::Cohort* PropertyCache::GetCohort(
    const StringPiece& cohort_name) const {
  GoogleString cohort_string;
  cohort_name.CopyToString(&cohort_string);
  CohortMap::const_iterator p = cohorts_.find(cohort_string);
  if (p == cohorts_.end()) {
    return NULL;
  }
  const Cohort* cohort = p->second;
  return cohort;
}

GoogleString PropertyCache::GetStatsPrefix(const GoogleString& cohort_name) {
  return StrCat("pcache-cohorts-", cohort_name);
}

void PropertyCache::InitCohortStats(const GoogleString& cohort,
                                    Statistics* statistics) {
  CacheStats::InitStats(GetStatsPrefix(cohort), statistics);
}

AbstractPropertyPage::~AbstractPropertyPage() {
}

PropertyPage::PropertyPage(
    PageType page_type,
    StringPiece url,
    StringPiece options_signature_hash,
    StringPiece cache_key_suffix,
    const RequestContextPtr& request_context,
    AbstractMutex* mutex,
    PropertyCache* property_cache)
    : mutex_(mutex),
      url_(url.as_string()),
      options_signature_hash_(options_signature_hash.as_string()),
      cache_key_suffix_(
          StrCat(cache_key_suffix, PageTypeSuffix(page_type))),
      request_context_(request_context),
      was_read_(false),
      property_cache_(property_cache),
      property_store_callback_(NULL),
      page_type_(page_type) {
}

PropertyPage::~PropertyPage() {
  if (property_store_callback_ != NULL) {
    property_store_callback_->DeleteWhenDone();
  }
  while (!cohort_data_map_.empty()) {
    CohortDataMap::iterator p = cohort_data_map_.begin();
    PropertyMapStruct* pmap_struct = p->second;
    PropertyMap* pmap = &p->second->pmap;
    cohort_data_map_.erase(p);

    // TODO(jmarantz): Not currently Using STLDeleteValues because
    // ~PropertyValue is private, the syntax for friending template
    // functions eludes me.
    for (PropertyMap::iterator p = pmap->begin(), e = pmap->end(); p != e;
         ++p) {
      PropertyValue* value = p->second;
      delete value;
    }
    delete pmap_struct;
  }
}

PropertyValue* PropertyPage::GetProperty(
    const PropertyCache::Cohort* cohort,
    const StringPiece& property_name) {
  ScopedMutex lock(mutex_.get());
  DCHECK(was_read_);
  DCHECK(cohort != NULL);
  PropertyValue* property = NULL;
  GoogleString property_name_str(property_name.data(), property_name.size());
  CohortDataMap::const_iterator cohort_itr = cohort_data_map_.find(cohort);
  CHECK(cohort_itr != cohort_data_map_.end());
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  PropertyMap* pmap = &pmap_struct->pmap;
  property = (*pmap)[property_name_str];
  log_record()->AddRetrievedPropertyToCohortInfo(
      page_type_, cohort->name(), property_name.as_string());
  if (property == NULL) {
    property = new PropertyValue;
    (*pmap)[property_name_str] = property;
    property->set_was_read(was_read_);
  }
  return property;
}

void PropertyPage::UpdateValue(
    const PropertyCache::Cohort* cohort, const StringPiece& property_name,
    const StringPiece& value) {
  if (cohort == NULL) {
    // TODO(pulkitg): Change LOG(WARNING) to LOG(DFATAL).
    LOG(WARNING) << "Cohort is NULL in PropertyPage::UpdateValue()";
    return;
  }
  PropertyValue* property = GetProperty(cohort, property_name);
  int64 now_ms = property_cache_->timer()->NowMs();

  // TODO(jmarantz): the policy of not having old timestamps override
  // new timestamps can cause us to discard some writes when
  // system-time jumps backwards, which can happen for various
  // reasons.  I think will need to revisit this policy as we learn how
  // to use the property cache & get the dynamics we want.
  if (property->write_timestamp_ms() <= now_ms) {
    property->SetValue(value, now_ms);
  }
}

void PropertyPage::WriteCohort(const PropertyCache::Cohort* cohort) {
  if (cohort == NULL) {
    // TODO(pulkitg): Change LOG(WARNING) to LOG(DFATAL).
    LOG(WARNING) << "Cohort is NULL in PropertyPage::WriteCohort()";
    return;
  }
  if (property_cache_->enabled()) {
    PropertyCacheValues values;
    if (EncodePropertyCacheValues(cohort, &values) ||
        HasPropertyValueDeleted(cohort)) {
      property_cache_->property_store()->Put(
          url_,
          options_signature_hash_,
          cache_key_suffix_,
          cohort,
          &values,
          NULL);
    }
  }
}

CacheInterface::KeyState PropertyPage::GetCacheState(
    const PropertyCache::Cohort* cohort) {
  ScopedMutex lock(mutex_.get());
  DCHECK(was_read_);
  DCHECK(cohort != NULL);
  CohortDataMap::iterator cohort_itr = cohort_data_map_.find(cohort);
  CHECK(cohort_itr != cohort_data_map_.end());
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  return pmap_struct->cache_state;
}

void PropertyPage::SetCacheState(
    const PropertyCache::Cohort* cohort,
    CacheInterface::KeyState x) {
  ScopedMutex lock(mutex_.get());
  DCHECK(cohort != NULL);
  CohortDataMap::iterator cohort_itr = cohort_data_map_.find(cohort);
  CHECK(cohort_itr != cohort_data_map_.end());
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  pmap_struct->cache_state = x;
}

void PropertyPage::DeleteProperty(
    const PropertyCache::Cohort* cohort, const StringPiece& property_name) {
  DCHECK(was_read_);
  DCHECK(cohort != NULL);
  ScopedMutex lock(mutex_.get());
  CohortDataMap::iterator cohort_itr = cohort_data_map_.find(cohort);
  if (cohort_itr == cohort_data_map_.end()) {
    return;
  }
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  PropertyMap* pmap = &pmap_struct->pmap;
  PropertyMap::iterator pmap_itr = pmap->find(property_name.as_string());
  if (pmap_itr == pmap->end()) {
    return;
  }
  PropertyValue* property = pmap_itr->second;
  pmap->erase(pmap_itr);
  pmap_struct->has_deleted_property = true;
  delete property;
}

bool PropertyPage::IsCohortPresent(const PropertyCache::Cohort* cohort) {
  ScopedMutex lock(mutex_.get());
  DCHECK(cohort != NULL);
  CohortDataMap::iterator cohort_itr = cohort_data_map_.find(cohort);
  CHECK(cohort_itr != cohort_data_map_.end());
  PropertyMapStruct* pmap_struct = cohort_itr->second;
  return pmap_struct->has_value;
}

void PropertyPage::FastFinishLookup() {
  if (property_store_callback_ != NULL) {
    property_store_callback_->FastFinishLookup();
  }
}

}  // namespace net_instaweb
