// Copyright 2007 Google Inc.
// Author: Lincoln Smith
//
// Licensed 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.
//
// Classes to implement the Address Cache and Address Encoding
// algorithms described in sections 5.1 - 5.4 of RFC 3284 -
// The VCDIFF Generic Differencing and Compression Data Format.
// The RFC text can be found at http://www.faqs.org/rfcs/rfc3284.html

#ifndef OPEN_VCDIFF_ADDRCACHE_H_
#define OPEN_VCDIFF_ADDRCACHE_H_

#include <config.h>
#include <vector>
#include "vcdiff_defs.h"  // VCDAddress

namespace open_vcdiff {

// Implements the "same" and "near" caches
// as described in RFC 3284, section 5.  The "near" cache allows
// efficient reuse of one of the last four referenced addresses
// plus a small offset, and the "same" cache allows efficient reuse
// of an exact recent address distinguished by its lowest-order bits.
//
// NOT threadsafe.
//
class VCDiffAddressCache {
 public:
  // The default cache sizes specified in the RFC
  static const int kDefaultNearCacheSize = 4;
  static const int kDefaultSameCacheSize = 3;

  VCDiffAddressCache(int near_cache_size, int same_cache_size);

  // This version of the constructor uses the default values
  // kDefaultNearCacheSize and kDefaultSameCacheSize.
  VCDiffAddressCache();

  // Initializes the object before use.  This method must be called after
  // constructing a VCDiffAddressCache/ object, before any other method may be
  // called.  This is because Init() validates near_cache_size_ and
  // same_cache_size_ before initializing the same and near caches.  After the
  // object has been initialized and used, Init() can be called again to reset
  // it to its initial state.
  //
  bool Init();

  int near_cache_size() const { return near_cache_size_; }

  int same_cache_size() const { return same_cache_size_; }

  // Returns the first mode number that represents one of the NEAR modes.
  // The number of NEAR modes is near_cache_size.  Each NEAR mode refers to
  // an element of the near_addresses_ array, where a recently-referenced
  // address is stored.
  //
  static unsigned char FirstNearMode() {
    return VCD_FIRST_NEAR_MODE;
  }

  // Returns the first mode number that represents one of the SAME modes.
  // The number of SAME modes is same_cache_size.  Each SAME mode refers to
  // a block of 256 elements of the same_addresses_ array; the lowest-order
  // 8 bits of the address are used to find the element of this block that
  // may match the desired address value.
  //
  unsigned char FirstSameMode() const {
    return VCD_FIRST_NEAR_MODE + near_cache_size();
  }

  // Returns the maximum valid mode number, which happens to be
  // the last SAME mode.
  //
  unsigned char LastMode() const {
    return FirstSameMode() + same_cache_size() - 1;
  }

  static unsigned char DefaultLastMode() {
    return VCD_FIRST_NEAR_MODE
        + kDefaultNearCacheSize + kDefaultSameCacheSize - 1;
  }

  // See the definition of enum VCDiffModes in vcdiff_defs.h,
  // as well as section 5.3 of the RFC, for a description of
  // each address mode type (SELF, HERE, NEAR, and SAME).
  static bool IsSelfMode(unsigned char mode) {
    return mode == VCD_SELF_MODE;
  }

  static bool IsHereMode(unsigned char mode) {
    return mode == VCD_HERE_MODE;
  }

  bool IsNearMode(unsigned char mode) const {
    return (mode >= FirstNearMode()) && (mode < FirstSameMode());
  }

  bool IsSameMode(unsigned char mode) const {
    return (mode >= FirstSameMode()) && (mode <= LastMode());
  }

  static VCDAddress DecodeSelfAddress(int32_t encoded_address) {
    return encoded_address;
  }

  static VCDAddress DecodeHereAddress(int32_t encoded_address,
                                      VCDAddress here_address) {
    return here_address - encoded_address;
  }

  VCDAddress DecodeNearAddress(unsigned char mode,
                               int32_t encoded_address) const {
    return NearAddress(mode - FirstNearMode()) + encoded_address;
  }

  VCDAddress DecodeSameAddress(unsigned char mode,
                               unsigned char encoded_address) const {
    return SameAddress(((mode - FirstSameMode()) * 256) + encoded_address);
  }

  // Returns true if, when using the given mode, an encoded address
  // should be written to the delta file as a variable-length integer;
  // returns false if the encoded address should be written
  // as a byte value (unsigned char).
  bool WriteAddressAsVarintForMode(unsigned char mode) const {
    return !IsSameMode(mode);
  }

  // An accessor for an element of the near_addresses_ array.
  // No bounds checking is performed; the caller must ensure that
  // Init() has already been called, and that
  //     0 <= pos < near_cache_size_
  //
  VCDAddress NearAddress(int pos) const {
    return near_addresses_[pos];
  }

  // An accessor for an element of the same_addresses_ array.
  // No bounds checking is performed; the caller must ensure that
  // Init() has already been called, and that
  //     0 <= pos < (same_cache_size_ * 256)
  //
  VCDAddress SameAddress(int pos) const {
    return same_addresses_[pos];
  }

  // This method will be called whenever an address is calculated for an
  // encoded or decoded COPY instruction, and will update the contents
  // of the SAME and NEAR caches.
  //
  void UpdateCache(VCDAddress address);

  // Determines the address mode that yields the most compact encoding
  // of the given address value.  The most compact encoding
  // is found by looking for the numerically lowest encoded address.
  // Sets *encoded_addr to the encoded representation of the address
  // and returns the mode used.
  //
  // The caller should pass the return value to the method
  // WriteAddressAsVarintForMode() to determine whether encoded_addr
  // should be written to the delta file as a variable-length integer
  // or as a byte (unsigned char).
  //
  unsigned char EncodeAddress(VCDAddress address,
                              VCDAddress here_address,
                              VCDAddress* encoded_addr);

  // Interprets the next value in the address_stream using the provided mode,
  // which may need to access the SAME or NEAR address cache.  Returns the
  // decoded address, or one of the following values:
  //   RESULT_ERROR: An invalid address value was found in address_stream.
  //   RESULT_END_OF_DATA: The limit address_stream_end was reached before
  //       the address could be decoded.  If more streamed data is expected,
  //       this means that the consumer should block and wait for more data
  //       before continuing to decode.  If no more data is expected, this
  //       return value signals an error condition.
  //
  // If successful, *address_stream will be incremented past the decoded address
  // position.  If RESULT_ERROR or RESULT_END_OF_DATA is returned,
  // then the value of *address_stream will not have changed.
  //
  VCDAddress DecodeAddress(VCDAddress here_address,
                           unsigned char mode,
                           const char** address_stream,
                           const char* address_stream_end);

 private:
  // The number of addresses to be kept in the NEAR cache.
  const int near_cache_size_;
  // The number of 256-byte blocks to store in the SAME cache.
  const int same_cache_size_;
  // The next position in the NEAR cache to which an address will be written.
  int       next_slot_;
  // NEAR cache contents
  std::vector<VCDAddress> near_addresses_;
  // SAME cache contents
  std::vector<VCDAddress> same_addresses_;

  // Making these private avoids implicit copy constructor & assignment operator
  VCDiffAddressCache(const VCDiffAddressCache&);  // NOLINT
  void operator=(const VCDiffAddressCache&);
};

}  // namespace open_vcdiff

#endif  // OPEN_VCDIFF_ADDRCACHE_H_