// Copyright 2008 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.

#ifndef OPEN_VCDIFF_ENCODETABLE_H_
#define OPEN_VCDIFF_ENCODETABLE_H_

#include <config.h>
#include <stddef.h>  // size_t
#include <stdint.h>  // int32_t
#include <string>
#include "addrcache.h"
#include "checksum.h"
#include "codetable.h"
#include "codetablewriter_interface.h"

namespace open_vcdiff {

class OutputStringInterface;
class VCDiffInstructionMap;

// The method calls after construction *must* conform
// to the following pattern:
//    {{Add|Copy|Run}* [AddChecksum] Output}*
//
// When Output has been called in this sequence, a complete target window
// (as defined in RFC 3284 section 4.3) will have been appended to
// out (unless no calls to Add, Run, or Copy were made, in which
// case Output will do nothing.)  The output will not be available for use
// until after each call to Output().
//
// NOT threadsafe.
//
class VCDiffCodeTableWriter : public CodeTableWriterInterface {
 public:
  // This constructor uses the default code table.
  // If interleaved is true, the encoder writes each delta file window
  // by interleaving instructions and sizes with their corresponding
  // addresses and data, rather than placing these elements into three
  // separate sections.  This facilitates providing partially
  // decoded results when only a portion of a delta file window
  // is received (e.g. when HTTP over TCP is used as the
  // transmission protocol.)  The interleaved format is
  // not consistent with the VCDIFF draft standard.
  //
  explicit VCDiffCodeTableWriter(bool interleaved);

  // Uses a non-standard code table and non-standard cache sizes.  The caller
  // must guarantee that code_table_data remains allocated for the lifetime of
  // the VCDiffCodeTableWriter object.  Note that this is different from how
  // VCDiffCodeTableReader::UseCodeTable works.  It is assumed that a given
  // encoder will use either the default code table or a statically-defined
  // non-standard code table, whereas the decoder must have the ability to read
  // an arbitrary non-standard code table from a delta file and discard it once
  // the file has been decoded.
  //
  VCDiffCodeTableWriter(bool interleaved,
                        int near_cache_size,
                        int same_cache_size,
                        const VCDiffCodeTableData& code_table_data,
                        unsigned char max_mode);

  virtual ~VCDiffCodeTableWriter();

  // Initializes the constructed object for use.
  // This method must be called after a VCDiffCodeTableWriter is constructed
  // and before any of its other methods can be called.  It will return
  // false if there was an error initializing the object, or true if it
  // was successful.  After the object has been initialized and used,
  // Init() can be called again to restore the initial state of the object.
  //
  virtual bool Init(size_t dictionary_size);

  // Write the header (as defined in section 4.1 of the RFC) to *out.
  // This includes information that can be gathered
  // before the first chunk of input is available.
  virtual void WriteHeader(OutputStringInterface* out,
                           VCDiffFormatExtensionFlags format_extensions);

  virtual size_t target_length() const { return target_length_; }

  // Encode an ADD opcode with the "size" bytes starting at data
  virtual void Add(const char* data, size_t size);

  // Encode a COPY opcode with args "offset" (into dictionary) and "size" bytes.
  virtual void Copy(int32_t offset, size_t size);

  // Encode a RUN opcode for "size" copies of the value "byte".
  virtual void Run(size_t size, unsigned char byte);

  virtual void AddChecksum(VCDChecksum checksum) {
    add_checksum_ = true;
    checksum_ = checksum;
  }

  // Appends the encoded delta window to the output
  // string.  The output string is not null-terminated and may contain embedded
  // '\0' characters.
  virtual void Output(OutputStringInterface* out);

  // There should not be any need to output more data
  // since EncodeChunk() encodes a complete target window
  // and there is no end-of-delta-file marker.
  virtual void FinishEncoding(OutputStringInterface* /*out*/) {}

 private:
  typedef std::string string;

  // The maximum value for the mode of a COPY instruction.
  const unsigned char max_mode_;

  // If interleaved is true, sets data_for_add_and_run_ and
  // addresses_for_copy_ to point at instructions_and_sizes_,
  // so that instructions, sizes, addresses and data will be
  // combined into a single interleaved stream.
  // If interleaved is false, sets data_for_add_and_run_ and
  // addresses_for_copy_ to point at their corresponding
  // separate_... strings, so that the three sections will
  // be generated separately from one another.
  //
  void InitSectionPointers(bool interleaved);

  // Determines the best opcode to encode an instruction, and appends
  // or substitutes that opcode and its size into the
  // instructions_and_sizes_ string.
  //
  void EncodeInstruction(VCDiffInstructionType inst,
                         size_t size,
                         unsigned char mode);

  void EncodeInstruction(VCDiffInstructionType inst, size_t size) {
    return EncodeInstruction(inst, size, 0);
  }

  // Calculates the number of bytes needed to store the given size value as a
  // variable-length integer (VarintBE).
  static size_t CalculateLengthOfSizeAsVarint(size_t size);

  // Appends the size value to the string as a variable-length integer.
  static void AppendSizeToString(size_t size, string* out);

  // Appends the size value to the output string as a variable-length integer.
  static void AppendSizeToOutputString(size_t size, OutputStringInterface* out);

  // Calculates the "Length of the delta encoding" field for the delta window
  // header, based on the sizes of the sections and of the other header
  // elements.
  size_t CalculateLengthOfTheDeltaEncoding() const;

  // None of the following 'string' objects are null-terminated.

  // A series of instruction opcodes, each of which may be followed
  // by one or two Varint values representing the size parameters
  // of the first and second instruction in the opcode.
  string instructions_and_sizes_;

  // A series of data arguments (byte values) used for ADD and RUN
  // instructions.  Depending on whether interleaved output is used
  // for streaming or not, the pointer may point to
  // separate_data_for_add_and_run_ or to instructions_and_sizes_.
  string *data_for_add_and_run_;
  string separate_data_for_add_and_run_;

  // A series of Varint addresses used for COPY instructions.
  // For the SAME mode, a byte value is stored instead of a Varint.
  // Depending on whether interleaved output is used
  // for streaming or not, the pointer may point to
  // separate_addresses_for_copy_ or to instructions_and_sizes_.
  string *addresses_for_copy_;
  string separate_addresses_for_copy_;

  VCDiffAddressCache address_cache_;

  size_t dictionary_size_;

  // The number of bytes of target data that has been encoded so far.
  // Each time Add(), Copy(), or Run() is called, this will be incremented.
  // The target length is used to compute HERE mode addresses
  // for COPY instructions, and is also written into the header
  // of the delta window when Output() is called.
  //
  size_t target_length_;

  const VCDiffCodeTableData* code_table_data_;

  // The instruction map facilitates finding an opcode quickly given an
  // instruction inst, size, and mode.  This is an alternate representation
  // of the same information that is found in code_table_data_.
  //
  const VCDiffInstructionMap* instruction_map_;

  // The zero-based index within instructions_and_sizes_ of the byte
  // that contains the last single-instruction opcode generated by
  // EncodeInstruction().  (See that function for exhaustive details.)
  // It is necessary to use an index rather than a pointer for this value
  // because instructions_and_sizes_ may be resized, which would invalidate
  // any pointers into its data buffer.  The value -1 is reserved to mean that
  // either no opcodes have been generated yet, or else the last opcode
  // generated was a double-instruction opcode.
  //
  int last_opcode_index_;

  // If true, an Adler32 checksum of the target window data will be written as
  // a variable-length integer, just after the size of the addresses section.
  //
  bool add_checksum_;

  // The checksum to be written to the current target window,
  // if add_checksum_ is true.
  // This will not be calculated based on the individual calls to Add(), Run(),
  // and Copy(), which would be unnecessarily expensive.  Instead, the code
  // that uses the VCDiffCodeTableWriter object is expected to calculate
  // the checksum all at once and to call AddChecksum() with that value.
  // Must be called sometime before calling Output(), though it can be called
  // either before or after the calls to Add(), Run(), and Copy().
  //
  VCDChecksum checksum_;

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

};  // namespace open_vcdiff

#endif  // OPEN_VCDIFF_ENCODETABLE_H_