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

//         morlovich@google.com (Maksim Orlovich)

#include "pagespeed/kernel/base/symbol_table.h"

#include <cstddef>
#include <cstdlib>
#include <vector>
#include <utility>

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

namespace {

// Strategically select a chunk size that will allow for some fixed
// overhead imposed by some versions of malloc.  If we have a
// zero-overhead malloc like tcmalloc there's no big deal in missing
// out on 16 bytes on a chunk this big.
const size_t kChunkSize = 32768 - 16;

}  // namespace

namespace net_instaweb {

template<class CharTransform>
SymbolTable<CharTransform>::SymbolTable()
    : next_ptr_(NULL),
      string_bytes_allocated_(0) {
  // We can use an empty string piece as the empty value, since
  // ::Intern has a quick exit on empty inputs.
  string_map_.set_empty_key(StringPiece());
}

template<class CharTransform>
void SymbolTable<CharTransform>::Clear() {
  string_map_.clear();
  for (int i = 0, n = storage_.size(); i < n; ++i) {
    std::free(storage_[i]);
  }
  storage_.clear();
  pieces_.clear();
  next_ptr_ = NULL;
  string_bytes_allocated_ = 0;
}

template<class CharTransform>
void SymbolTable<CharTransform>::NewStorage() {
  next_ptr_ = static_cast<char*>(std::malloc(kChunkSize));
  storage_.push_back(next_ptr_);
}

template<class CharTransform>
Atom SymbolTable<CharTransform>::Intern(const StringPiece& src) {
  if (src.empty()) {
    return Atom();
  }

  typename SymbolMap::const_iterator iter = string_map_.find(src);
  if (iter == string_map_.end()) {
    // Lazy-initialize to ensure at least one available block.
    if (storage_.empty()) {
      NewStorage();
    }

    size_t bytes_required = src.size();
    char* new_symbol_storage = NULL;
    if (bytes_required > kChunkSize / 4) {
      // The string we are trying to put into the symbol table is sufficiently
      // large that it might waste a lot of our chunked storage, so just
      // allocate it directly.
      new_symbol_storage = static_cast<char*>(std::malloc(bytes_required));

      // Insert this large chunk into the second-to-last position in the
      // storage array so that we can keep using the last normal chunk.
      int last_pos = storage_.size() - 1;
      storage_.push_back(storage_[last_pos]);
      storage_[last_pos] = new_symbol_storage;
    } else {
      DCHECK_GE(next_ptr_, storage_.back());
      size_t bytes_used = next_ptr_ - storage_.back();
      size_t bytes_remaining = kChunkSize - bytes_used;
      if (bytes_remaining < bytes_required) {
        NewStorage();
      }
      new_symbol_storage = next_ptr_;
      next_ptr_ += bytes_required;
    }
    memcpy(new_symbol_storage, src.data(), src.size());

    StringPiece new_sp(new_symbol_storage, src.size());
    pieces_.push_back(new_sp);
    StringPiece* canonical_sp = &pieces_.back();
    string_map_.insert(make_pair(new_sp, canonical_sp));
    string_bytes_allocated_ += bytes_required;
    return Atom(canonical_sp);
  }
  return Atom(iter->second);
}

// We explicitly instantiate since we want ::Intern to be out-of-line
template class SymbolTable<CaseFold>;
template class SymbolTable<CasePreserve>;

}  // namespace net_instaweb
