/*
 * 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_SHAREDMEM_SHARED_DYNAMIC_STRING_MAP_H_
#define PAGESPEED_KERNEL_SHAREDMEM_SHARED_DYNAMIC_STRING_MAP_H_

#include <cstddef>

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

namespace net_instaweb {

class MessageHandler;
class Writer;

struct Entry {
  int value;
  size_t string_offset;
};

// A shared memory string to int dictionary/map, no deletion.
// Currently the map is designed to fill with number_of_strings strings of
// average length average_string_length.  Once the map is full it ignores
// attempts to add additional information.
// TODO(jhoch): make map dynamically sized
class SharedDynamicStringMap {
 public:
  // Number of strings will be rounded up to a power of 2.
  // Average string length should include terminating null character.
  // Map will be able to hold exactly number_of_strings * average_string_length
  // chars worth of string data.
  SharedDynamicStringMap(size_t number_of_strings,
                         size_t average_string_length,
                         AbstractSharedMem* shm_runtime,
                         const GoogleString& filename_prefix,
                         const GoogleString& filename_suffix);

  // Initialize the shared memory segment.  This method should complete before
  // any other methods are executed.
  // parent = true means invoked in root process, initialize the shared memory
  //        = false means invoked in child process -- attach to existing segment
  bool InitSegment(bool parent, MessageHandler* message_handler);

  // Increments value corresponding to given string by 1.
  // Adds the string to the map with initial value 1 if the string is not
  // present.
  // Returns the new value corresponding to the element.
  // If the map is full it does nothing and returns 0.
  int IncrementElement(const StringPiece& string);

  // Retrieve the value corresponding to the string (returns 0 if the string is
  // not in the map)
  int LookupElement(const StringPiece& string) const;

  // Dumps table's strings into StringSet
  void GetKeys(StringSet* strings);

  // Retrieve the number of strings inserted into the table.
  int GetNumberInserted() const;

  // Destroy shared memory segment and other relevant clean-up
  void GlobalCleanup(MessageHandler* message_handler);

  // Iterates through the string data that is present at the time of calling
  // and dumps out each string with its associated value.  The value produced
  // for a given string is going to be the value present whenever that string
  // is dumped.
  void Dump(Writer* writer, MessageHandler* message_handler);

 private:
  void ClearSegment(MessageHandler* message_handler);

  // ***If lock = true, locks the mutex associated with the returned entry***
  //    (to be unlocked by caller)
  // Finds the index of the entry with the given string, or the first empty
  // entry encountered, which can either be used for insertion purposes or
  // to know that the string is not present, since the table does not support
  // deletion.
  //   -1 is returned if the entire table is traversed without finding the
  // string or an empty spot (this should not happen if the table never exceeds
  // 50% capacity).
  //   entry_pointer is an output parameter.
  //   Note that lock must always be set to true for any writing operation, and
  // setting write to false for a read operation can result in a false read,
  // albeit in very rare circumstances.  The circumstance is that an entry "AB"
  // is being added to the table, where A and B are arbitrary strings, and a
  // read of "A" is occurring, and the read of "A" catches the write of "AB"
  // mid-write at exactly the moment where it can only see "A."
  //   The fact that looking up without locking is possible relies on the
  // assumption that entries are not deleted and that null characters fill up
  // the char space upon initialization.
  //   If 100% accurate lookup is needed then a new LookupElement method could
  // be added that calls FindEntry(lock = true).
  int FindEntry(const StringPiece& string,
                bool lock,
                Entry** entry_pointer) const;
  Entry* GetEntry(size_t n) const;
  Entry* GetFirstEntry() const;

  // Gets the mutex for the nth table entry.
  AbstractMutex* GetMutex(size_t n) const;
  // Inserts the given string into the table (if there is room), by adding it
  // to char storage and setting the entry_pointer's char offset and value (the
  // former to string_offset and the latter to 1), and returns the resulting
  // value of the entry (1 if it was successfully inserted, 0 otherwise)
  //   The entry should be locked when this method is called.
  int InsertString(const StringPiece& string, Entry* entry_pointer);
  char* GetStringAtOffset(size_t offset) const;

  // Math utility function, returns the smallest power of two greater than or
  // equal to the input.
  static size_t NextPowerOfTwo(size_t n);

  //                               |
  // Structure content             | Offset
  //                               |
  //  ___________________________  | mutex_offset_ = 0
  // | Mutex0                    | |  - memory location = segment_->Base()
  // |                           | |
  // | Mutex1                    | | mutex_offset_ + mutex_size
  // |                           | |
  // | Mutex2                    | | mutex_offset_ + mutex_size_ * 2
  // | .                         | |
  // | .                         | |   - each mutex has size mutex_size
  // | .                         | |
  // |                           | |
  // | MutexN                    | | mutex_offset_ + mutex_size_ * N
  // | .                         | |
  // | .                         | |   - there are table_size_ + 1 mutexes
  // | .                         | |       (last mutex is for string_offset
  // | .                         | |        and number_inserted)
  // | .                         | |
  // |___________________________| | strings_offset_ = mutex_offset_ +
  // | String0======= |            |     kTableSize * mutex_size_
  // |                |            |   = String offset0
  // |                |            |
  // | String1======= |            | String offset1
  // |           _____|            |
  // |          |                  |
  // | String2= |                  | String offset2
  // |          |________________  |
  // |                           | |
  // | String3================== | | etc.
  // |                  _________| |
  // |                 |           |
  // | String4======== |           |   - strings are variable length, null
  // |                 |_          |       terminated
  // |                   |         |
  // | String5========== |         |   - total allocated space is
  // |                  _|         |       number_of_strings times average
  // |                 |           |       string length
  // | String6======== |           |
  // |                 |_          |   - there are as many strings as have been
  // | .                 |         |       added
  // | .                 |         |
  // | .                 |         |   - location at which to add next string is
  // |___________________|         |       stored at string_offset_offset_
  // |                 |           |       (see below)
  // |                 |           |
  // |  String offset  |           | string_offset_offset_ = strings_offset_ +
  // |_________________|           |     number_of_strings_ *
  // |        |                    |     average_string_length_
  // | Number |                    |
  // |  Inse- |                    | number_inserted_offset_ =
  // |   rted |                    |     string_offset_offset_ + kOffsetSize
  // |        |                    |
  // |________|________________    | table_offset_ = number_inserted_offset_ +
  // | Value0 | String offset0 |   |     kIntSize
  // |  (int) |  (size_t)      |   |
  // |        |                |   |
  // | Value1 | String offset1 |   | table_offset_ + kEntrySize
  // |        |                |   |
  // | Value2 | String offset2 |   | table_offset_ + kEntrySize* 2
  // | .      | .              |   |
  // | .      | .              |   |   - each value and string offset makes an
  // | .      | .              |   |       Entry struct
  // |        |                |   |
  // | ValueN | String offsetN |   | table_offset_ + kEntrySize * N
  // | .      | .              |   |
  // | .      | .              |   |   - there are table_size entries
  // | .      | .              |   |
  // |________|________________|   |

  size_t number_of_strings_;
  size_t average_string_length_;
  // Sizes of various portions of the memory
  size_t mutex_size_;
  size_t table_size_;
  // Offset from segment_->Base() at which various portions of the structure
  // begin (see above depiction).
  //   mutex_offset_ is the beginning of the (table_size_ + 1) mutexes
  //   strings_offset_ is the beginning of the strings
  //   string_offset_offset_ is where the offset of the next string to be
  //     inserted is located
  //   number_inserted_offset_ is where the number of inserted strings is
  //     located
  //   table_offset_ is the beginning of the table_size_ entries
  size_t mutex_offset_;
  size_t strings_offset_;
  size_t string_offset_offset_;
  size_t number_inserted_offset_;
  size_t table_offset_;
  // Total size of shared memory segment
  size_t total_size_;

  // The mutex for inserting strings, i.e. the one shared by the
  // string_offset_ and number_inserted_ values.
  scoped_ptr<AbstractMutex> insert_string_mutex_;

  const GoogleString segment_name_;
  AbstractSharedMem* shm_runtime_;
  scoped_ptr<AbstractSharedMemSegment> segment_;

  DISALLOW_COPY_AND_ASSIGN(SharedDynamicStringMap);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_KERNEL_SHAREDMEM_SHARED_DYNAMIC_STRING_MAP_H_
