/*
 * 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_BASE_ARENA_H_
#define PAGESPEED_KERNEL_BASE_ARENA_H_

#include <vector>
#include <cstddef>

#include "base/logging.h"
#include "pagespeed/kernel/base/basictypes.h"

namespace net_instaweb {

// This template keeps a packed set of objects inheriting from the same base
// type (which must have a virtual destructor) where all of the
// objects in the same arena are expected to be destroyed at once.
template<typename T>
class Arena {
 public:
  // All allocations we make will be aligned to this. We will also reserve
  // this much room for our work area, as it keeps things simple.
  static const size_t kAlign = 8;

  Arena() {
    InitEmpty();
  }

  ~Arena() {
    CHECK(chunks_.empty());
  }

  void* Allocate(size_t size) {
    size += kAlign;  // Need room to link the next object.
    size = ExpandToAlign(size);

    DCHECK(sizeof(void*) <= kAlign);
    DCHECK(size < Chunk::kSize);

    if (next_alloc_ + size > chunk_end_) {
      AddChunk();
    }

    char* base = next_alloc_;

    // Update the links -- the previous object should point to our
    // chunk's base, our base should point to NULL, and last_link_
    // should point to our base
    char** our_last_link_field = reinterpret_cast<char**>(base);
    *last_link_ = base;
    *our_last_link_field = NULL;
    last_link_ = our_last_link_field;

    next_alloc_ += size;

    char* out = base + kAlign;
    // Warning: the following line is very type-sensitive and can't
    // easily be turned into a DCHECK_EQ as lint would like.
    DCHECK((reinterpret_cast<uintptr_t>(out) & (kAlign - 1)) == 0);
    return out;
  }

  // Cleans up all the objects in the arena. You must call this explicitly.
  void DestroyObjects();

  // Rounds block size up to 8; we always align to it, even on 32-bit.
  static size_t ExpandToAlign(size_t in) {
    return (in + kAlign - 1) & ~(kAlign - 1);
  }

 private:
  struct Chunk {
    // Representation: the arena is a vector of fixed-size (kSize, 8k) chunks;
    // we allocate objects from the end of the most recently created chunk until
    // an allocation doesn't fit (in which case we make a fresh chunk)
    //
    // Each chunk is independently organized into a singly-linked list, where
    // we precede each object with a pointer to the next allocated block, e.g.:
    //
    //                  /-----------------|
    //                  |                \|/
    // -----------------|---------------|----|-----------|
    // |   | object 1 | | | object 2    | NU | object 3  |
    // | | |          |   |             | LL |           |
    // |_|_|----------|---|-------------|----|-----------|
    //   |              ^
    //   \--------------/
    // We need this because objects may have different sizes, and we'll need
    // to find everyone to call their destructor
    static const size_t kSize = 8192;
    char buf[kSize];
  };

  // Adds in a new chunk and initializes all the fields below to refer to it
  void AddChunk();

  // Sets up all the pointers below to denote us being empty.
  void InitEmpty();

  // First free byte of the current chunk
  char* next_alloc_;

  // The point where to link in a new object we allocate.
  // We need this so that the last object in a chunk has
  // a null 'next' link.
  char** last_link_;

  // First address after the last byte of the currently active chunk
  char* chunk_end_;

  // Scratch location in case we want a write to go nowhere.
  // Does not point anywhere, just bitbuckets writes
  char* scratch_;

  std::vector<Chunk*> chunks_;
};

template<typename T>
void Arena<T>::AddChunk() {
  Chunk* chunk = new Chunk();
  chunks_.push_back(chunk);
  next_alloc_ = chunk->buf;
  chunk_end_ = next_alloc_ + Chunk::kSize;
  last_link_ = &scratch_;
}

template<typename T>
void Arena<T>::DestroyObjects() {
  for (int i = 0; i < static_cast<int>(chunks_.size()); ++i) {
    // Walk through objects in this chunk.
    char* base = chunks_[i]->buf;
    while (base != NULL) {
      reinterpret_cast<T*>(base + kAlign)->~T();
      base = *reinterpret_cast<char**>(base);
    }
    delete chunks_[i];
  }
  chunks_.clear();
  InitEmpty();
}

template<typename T>
void Arena<T>::InitEmpty() {
  // The way this is initialized ensures that the next call to allocate
  // will call AddChunk(). Doing it this way rather than calling NewChunk()
  // from here establishes the invariant that all the chunks are non-empty,
  // which helps in DestroyObjects()
  next_alloc_ = NULL;
  last_link_ = NULL;
  chunk_end_ = NULL;
}

}  // namespace  net_instaweb

#endif  // PAGESPEED_KERNEL_BASE_ARENA_H_
