/*
 * 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/kernel/base/fast_wildcard_group.h"

#include <algorithm>
#include <vector>

#include "base/logging.h"
#include "pagespeed/kernel/base/atomic_int32.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/stl_util.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/rolling_hash.h"
#include "pagespeed/kernel/base/wildcard.h"

namespace net_instaweb {

namespace {
// Maximum rolling hash window size
const int32 kMaxRollingHashWindow = 256;

// Special index value for unused hash table entry.
const int kNoEntry = -1;

StringPiece LongestLiteralStringInWildcard(const Wildcard* wildcard) {
  StringPiece spec = wildcard->spec();
  const char kWildcardChars[] = { Wildcard::kMatchAny, Wildcard::kMatchOne };
  StringPiece wildcardChars(kWildcardChars, arraysize(kWildcardChars));
  int longest_pos = 0;
  int longest_len = 0;
  int next_wildcard = 0;
  for (int pos = 0;
       pos < static_cast<int>(spec.size()); pos = next_wildcard + 1) {
    next_wildcard = spec.find_first_of(wildcardChars, pos);
    if (next_wildcard == static_cast<int>(StringPiece::npos)) {
      next_wildcard = spec.size();
    }
    int len = next_wildcard - pos;
    if (len > longest_len) {
      longest_pos = pos;
      longest_len = len;
    }
  }
  return spec.substr(longest_pos, longest_len);
}

}  // namespace

FastWildcardGroup::~FastWildcardGroup() {
  Clear();
}

void FastWildcardGroup::Uncompile() {
  if (rolling_hash_length_.value() == kUncompiled) {
    return;
  }
  rolling_hash_length_.set_value(kUncompiled);
  rolling_hashes_.clear();
  effective_indices_.clear();
  wildcard_only_indices_.clear();
  pattern_hash_index_.clear();
}

void FastWildcardGroup::Clear() {
  Uncompile();
  STLDeleteElements(&wildcards_);
  allow_.clear();
}

inline int& FastWildcardGroup::pattern_hash_index(uint64 rolling_hash) const {
  // We exploit the fact that pattern_hash_index.size() is a power of two
  // to do integer modulus by bit masking.
  return pattern_hash_index_[rolling_hash & (pattern_hash_index_.size() - 1)];
}

void FastWildcardGroup::CompileNonTrivial() const {
  // First, assemble longest literal strings of each pattern
  std::vector<StringPiece> longest_literal_strings;
  int num_nontrivial_patterns = 0;
  int32 rolling_hash_length = kMaxRollingHashWindow;
  for (int i = 0; i < static_cast<int>(wildcards_.size()); ++i) {
    longest_literal_strings.push_back(
        LongestLiteralStringInWildcard(wildcards_[i]));
    DCHECK_EQ(i + 1, static_cast<int>(longest_literal_strings.size()));
    int length = longest_literal_strings[i].size();
    if (length > 0) {
      ++num_nontrivial_patterns;
      rolling_hash_length = std::min(rolling_hash_length, length);
    }
  }
  if (num_nontrivial_patterns < kMinPatterns) {
    // Not enough non-trivial patterns.
    DCHECK_EQ(kDontHash, rolling_hash_length_.value());
    return;
  }
  // Allocate a hash table that's power-of-2 sized and >=
  // 2*num_nontrivial_patterns.
  int hash_index_size;
  for (hash_index_size = 8;
       hash_index_size < 2 * num_nontrivial_patterns;
       hash_index_size *= 2) { }
  pattern_hash_index_.resize(hash_index_size, kNoEntry);
  rolling_hashes_.resize(wildcards_.size());
  effective_indices_.resize(allow_.size());
  int current_effective_index = allow_.size() - 1;
  bool current_allow = allow_[current_effective_index];
  // Fill in the hash table with a rolling hash.  We do this in
  // reverse order so that collisions will result in the later
  // pattern being matched first (if that succeeds, no further
  // matching will be required).
  for (int i = longest_literal_strings.size() - 1; i >= 0; --i) {
    const StringPiece literal(longest_literal_strings[i]);
    if (allow_[i] != current_allow) {
      // Change from allow to deny or vice versa;
      // change the current effective index and allow state.
      current_effective_index = i;
      current_allow = allow_[i];
    }
    effective_indices_[i] = current_effective_index;
    DCHECK_LE(i, current_effective_index);
    DCHECK_EQ(allow_[i], current_allow);
    DCHECK_EQ(current_allow, allow_[effective_indices_[i]]);
    if (literal.size() == 0) {
      // All-wildcard pattern.
      wildcard_only_indices_.push_back(i);
      rolling_hashes_[i] = 0;
    } else {
      DCHECK_GE(static_cast<int>(literal.size()), rolling_hash_length);
      // If possible, find a non-colliding rolling hash taken from literal.  If
      // the first hash collides, using a different hash is OK; we'll still end
      // up checking both matches in the table for an input that matches both.
      // The goal is to avoid chaining by spreading the entries out across the
      // table.
      // TODO(jmaessen): Consider re-hashing the current entry as well on
      // collision to favor collision-free hash tables.
      int max_start = literal.size() - rolling_hash_length;
      int start = 0;
      uint64 rolling_hash =
          RollingHash(literal.data(), start, rolling_hash_length);
      for (start = 1;
           start <= max_start && pattern_hash_index(rolling_hash) != kNoEntry;
           ++start) {
        rolling_hash = NextRollingHash(literal.data(), start,
                                       rolling_hash_length, rolling_hash);
      }
      // Now insert the entry, dealing with any collisions.
      rolling_hashes_[i] = rolling_hash;
      while (pattern_hash_index(rolling_hash) != kNoEntry) {
        ++rolling_hash;
      }
      pattern_hash_index(rolling_hash) = i;
    }
  }
  // Finally, after all the metadata is initialized, make rolling_hash_length
  // visible to the world.  This has release semantics, meaning that if another
  // thread reads rolling_hash_length_ (with acquire semantics) and gets the
  // value we set here, it is guaranteed to see all the preceding writes we did
  // to the other compilation metadata.
  rolling_hash_length_.set_value(rolling_hash_length);
}

void FastWildcardGroup::Compile() const {
  // Basic invariant
  CHECK_EQ(wildcards_.size(), allow_.size());
  // Make sure we don't have cruft left around from a previous compile.
  CHECK_EQ(0, static_cast<int>(rolling_hashes_.size()));
  CHECK_EQ(0, static_cast<int>(effective_indices_.size()));
  CHECK_EQ(0, static_cast<int>(wildcard_only_indices_.size()));
  CHECK_EQ(0, static_cast<int>(pattern_hash_index_.size()));
  CHECK_EQ(kDontHash, rolling_hash_length_.value());

  if (static_cast<int>(wildcards_.size()) >= kMinPatterns) {
    // Slow path, compute metadata and set rolling_hash_length_ to
    // its final value.
    CompileNonTrivial();
  }

  // When we're done, things should be in a sensible state.
  int32 rolling_hash_length = rolling_hash_length_.value();
  DCHECK_NE(kUncompiled, rolling_hash_length);
  if (rolling_hash_length == kDontHash) {
    DCHECK_EQ(0, static_cast<int>(rolling_hashes_.size()));
    DCHECK_EQ(0, static_cast<int>(effective_indices_.size()));
    DCHECK_EQ(0, static_cast<int>(wildcard_only_indices_.size()));
    DCHECK_EQ(0, static_cast<int>(pattern_hash_index_.size()));
  } else {
    DCHECK_LT(0, rolling_hash_length);
    DCHECK_EQ(wildcards_.size(), rolling_hashes_.size());
    DCHECK_EQ(wildcards_.size(), effective_indices_.size());
    int hash_pats = wildcards_.size() - wildcard_only_indices_.size();
    DCHECK_LE(kMinPatterns, hash_pats);
    DCHECK_LE(2 * hash_pats, static_cast<int>(pattern_hash_index_.size()));
  }
}

void FastWildcardGroup::Allow(const StringPiece& expr) {
  Uncompile();
  Wildcard* wildcard = new Wildcard(expr);
  wildcards_.push_back(wildcard);
  allow_.push_back(true);
}

void FastWildcardGroup::Disallow(const StringPiece& expr) {
  Uncompile();
  Wildcard* wildcard = new Wildcard(expr);
  wildcards_.push_back(wildcard);
  allow_.push_back(false);
}

bool FastWildcardGroup::Match(const StringPiece& str, bool allow) const {
  int32 rolling_hash_length = rolling_hash_length_.value();
  // The previous read has acquire semantics, and all writes to
  // rolling_hash_length_ have release semantics.  This means we'll see the
  // results of compilation if rolling_hash_length > 0.
  //
  // NOTE: it is unsafe (and expensive) to just CompareAndSwap (CAS) here.
  //  AtomicInt32::CAS guarantees release semantics but not acquire semantics.
  //  As a result we would potentially miss the results of compilation released
  //  by a prior write to rolling_hash_length_.  This would cause us to read
  //  inconsistent compilation metadata, possibly resulting in a crash.
  if (rolling_hash_length == kUncompiled) {
    if (rolling_hash_length_.CompareAndSwap(kUncompiled, kDontHash) ==
        kUncompiled) {
      // During compilation other Match attempts will see kDontHash
      // and will perform matching naively.  Only the caller that
      // does the kUncompiled -> kDontHash transition is permitted
      // to compile.
      Compile();
    }
    // rolling_hash_length is no longer kUncompiled, due to some call to
    // Compile().  Re-acquire it so that we can safely view the results of
    // compilation so far.
    rolling_hash_length = rolling_hash_length_.value();
  }
  if (rolling_hash_length == kDontHash) {
    // Set of wildcards is small, or compilation was ongoing when we last read
    // rolling_hash_length_.
    // Just match against each pattern in reverse order (starting with most
    // recent, which overrides less recent), returning when a match succeeds.
    for (int i = wildcards_.size() - 1; i >= 0; --i) {
      if (wildcards_[i]->Match(str)) {
        return allow_[i];
      }
    }
    return allow;
  }
  int max_effective_index = kNoEntry;
  // Start by matching against all-wildcard patterns in reverse order.  Their
  // indices are stored in reverse index order in wildcard_only_indices, so
  // traverse it forwards.  Stop if a match is found (since earlier matches will
  // have a smaller index and be overridden by the already-found match).
  // TODO(jmaessen): These patterns all devolve to a string length check (== or
  // >=).  Consider optimizing them.
  for (int i = 0, sz = wildcard_only_indices_.size(); i < sz; ++i) {
    int index = wildcard_only_indices_[i];
    if (wildcards_[index]->Match(str)) {
      max_effective_index = effective_indices_[index];
      break;
    }
  }
  int exit_effective_index = wildcards_.size() - 1;
  int rolling_end = str.size() - rolling_hash_length;
  if (max_effective_index < exit_effective_index && rolling_end >= 0) {
    // Do a Rabin-Karp rolling match through the string.
    uint64 rolling_hash = RollingHash(str.data(), 0, rolling_hash_length);
      // Uses signed arithmetic for correct comparison below.
    for (int ofs = 0;
         max_effective_index < exit_effective_index && ofs <= rolling_end; ) {
      // Look up rolling_hash in table, stopping if we find a:
      //   1) Smaller index than max_effective_index
      //   2) Matching string (update max_effective_index).
      // In either case, all subsequent hash matches will be overridden by
      // max_effective_index, so we need not search the bucket anymore.  This is
      // guaranteed by the order of table insertion (largest index first)
      // and the fact that later smaller colliding entries are further along
      // in the linear probe.  This is true even if intervening slots are
      // occupied by entries with completely different hash values - those
      // entries will still have larger indices as they were inserted earlier.
      for (uint64 probe = 0; ; ++probe) {
        // The following invariant (and thus loop termination) should be
        // guaranteed by the sparseness of pattern_hash_index_.
        DCHECK_GT(pattern_hash_index_.size(), probe);
        int index = pattern_hash_index(rolling_hash + probe);
        if (index <= max_effective_index) {
          // This also includes the kNoEntry case.
          break;
        }
        if (rolling_hash == rolling_hashes_[index] &&
            wildcards_[index]->Match(str)) {
          max_effective_index = effective_indices_[index];
          break;
        }
      }
      if (++ofs <= rolling_end) {
        rolling_hash = NextRollingHash(str.data(), ofs, rolling_hash_length,
                                       rolling_hash);
      }
    }
  }
  if (max_effective_index == kNoEntry) {
    return allow;
  } else {
    return allow_[max_effective_index];
  }
}

void FastWildcardGroup::CopyFrom(const FastWildcardGroup& src) {
  Clear();
  AppendFrom(src);
}

void FastWildcardGroup::AppendFrom(const FastWildcardGroup& src) {
  Uncompile();
  CHECK_EQ(src.wildcards_.size(), src.allow_.size());
  for (int i = 0, n = src.wildcards_.size(); i < n; ++i) {
    wildcards_.push_back(src.wildcards_[i]->Duplicate());
    allow_.push_back(src.allow_[i]);
  }
}

GoogleString FastWildcardGroup::Signature() const {
  GoogleString signature;
  for (int i = 0, n = wildcards_.size(); i < n; ++i) {
    StrAppend(&signature, wildcards_[i]->spec(), (allow_[i] ? "A" : "D"), ",");
  }
  return signature;
}

}  // namespace net_instaweb
