// 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.
//
// Unit tests for the class VCDiffCodeTableReader, found in decodetable.h.

#include <config.h>
#include "decodetable.h"
#include <stdint.h>  // int32_t
#include <vector>
#include "addrcache.h"
#include "codetable.h"
#include "testing.h"
#include "varint_bigendian.h"

namespace open_vcdiff {
namespace {

class DecodeTableTest : public testing::Test {
 protected:
  DecodeTableTest()
  : instructions_and_sizes_(instruction_buffer_size),
    found_size_(0),
    found_mode_(0) {
    instructions_and_sizes_ptr_ = &instructions_and_sizes_[0];
    reader_.Init(&instructions_and_sizes_ptr_,
                 instructions_and_sizes_ptr_ + instruction_buffer_size);
  }

  static void AddExerciseOpcode(unsigned char inst1,
                                unsigned char mode1,
                                unsigned char size1,
                                unsigned char inst2,
                                unsigned char mode2,
                                unsigned char size2,
                                int opcode) {
    g_exercise_code_table_->inst1[opcode] = inst1;
    g_exercise_code_table_->mode1[opcode] = mode1;
    g_exercise_code_table_->size1[opcode] = (inst1 == VCD_NOOP) ? 0 : size1;
    g_exercise_code_table_->inst2[opcode] = inst2;
    g_exercise_code_table_->mode2[opcode] = mode2;
    g_exercise_code_table_->size2[opcode] = (inst2 == VCD_NOOP) ? 0 : size2;
  }

  static void SetUpTestCase() {
    g_exercise_code_table_ = new VCDiffCodeTableData;
    int opcode = 0;
    for (unsigned char inst_mode1 = 0;
         inst_mode1 <= VCD_LAST_INSTRUCTION_TYPE + kLastExerciseMode;
         ++inst_mode1) {
      unsigned char inst1 = inst_mode1;
      unsigned char mode1 = 0;
      if (inst_mode1 > VCD_COPY) {
        inst1 = VCD_COPY;
        mode1 = inst_mode1 - VCD_COPY;
      }
      for (unsigned char inst_mode2 = 0;
           inst_mode2 <= VCD_LAST_INSTRUCTION_TYPE + kLastExerciseMode;
           ++inst_mode2) {
        unsigned char inst2 = inst_mode2;
        unsigned char mode2 = 0;
        if (inst_mode2 > VCD_COPY) {
          inst2 = VCD_COPY;
          mode2 = inst_mode2 - VCD_COPY;
        }
        AddExerciseOpcode(inst1, mode1, 0, inst2, mode2, 0, opcode++);
        AddExerciseOpcode(inst1, mode1, 0, inst2, mode2, 255, opcode++);
        AddExerciseOpcode(inst1, mode1, 255, inst2, mode2, 0, opcode++);
        AddExerciseOpcode(inst1, mode1, 255, inst2, mode2, 255, opcode++);
      }
    }
    CHECK_EQ(VCDiffCodeTableData::kCodeTableSize, opcode);
    EXPECT_TRUE(VCDiffCodeTableData::kDefaultCodeTableData.Validate());
    EXPECT_TRUE(g_exercise_code_table_->Validate(kLastExerciseMode));
  }

  static void TearDownTestCase() {
    delete g_exercise_code_table_;
  }

  void VerifyInstModeSize(unsigned char inst,
                          unsigned char mode,
                          unsigned char size,
                          unsigned char opcode) {
    if (inst == VCD_NOOP) return;  // GetNextInstruction skips NOOPs
    int32_t found_size = 0;
    unsigned char found_mode = 0;
    unsigned char found_inst = reader_.GetNextInstruction(&found_size,
                                                          &found_mode);
    EXPECT_EQ(inst, found_inst);
    EXPECT_EQ(mode, found_mode);
    if (size == 0) {
      EXPECT_EQ(1000 + opcode, found_size);
    } else {
      EXPECT_EQ(size, found_size);
    }
  }

  void VerifyInstModeSize1(unsigned char inst,
                           unsigned char mode,
                           unsigned char size,
                           unsigned char opcode) {
    if (inst == VCD_NOOP) size = 0;
    EXPECT_EQ(g_exercise_code_table_->inst1[opcode], inst);
    EXPECT_EQ(g_exercise_code_table_->mode1[opcode], mode);
    EXPECT_EQ(g_exercise_code_table_->size1[opcode], size);
    VerifyInstModeSize(inst, mode, size, opcode);
  }

  void VerifyInstModeSize2(unsigned char inst,
                           unsigned char mode,
                           unsigned char size,
                           unsigned char opcode) {
    if (inst == VCD_NOOP) size = 0;
    EXPECT_EQ(g_exercise_code_table_->inst2[opcode], inst);
    EXPECT_EQ(g_exercise_code_table_->mode2[opcode], mode);
    EXPECT_EQ(g_exercise_code_table_->size2[opcode], size);
    VerifyInstModeSize(inst, mode, size, opcode);
  }

  // This value is designed so that the total number of inst values and modes
  // will equal 8 (VCD_NOOP, VCD_ADD, VCD_RUN, VCD_COPY modes 0 - 4).
  // Eight combinations of inst and mode, times two possible size values,
  // squared (because there are two instructions per opcode), makes
  // exactly 256 possible instruction combinations, which fits kCodeTableSize
  // (the number of opcodes in the table.)
  static const int kLastExerciseMode = 4;

  // The buffer size (in bytes) needed to store kCodeTableSize opcodes plus
  // up to kCodeTableSize VarintBE-encoded size values.
  static const int instruction_buffer_size;

  // A code table that exercises as many combinations as possible:
  // 2 instructions, each is a NOOP, ADD, RUN, or one of 5 copy modes
  // (== 8 total combinations of inst and mode), and each has
  // size == 0 or 255 (2 possibilities.)
  static VCDiffCodeTableData* g_exercise_code_table_;

  VCDiffCodeTableReader reader_;

  // A buffer to which instructions and sizes will be added manually
  // in order to exercise VCDiffCodeTableReader.
  std::vector<char> instructions_and_sizes_;

  // The buffer pointer used by the VCDiffCodeTableReader.
  const char* instructions_and_sizes_ptr_;

  // The size and mode returned by GetNextInstruction().
  int32_t found_size_;
  unsigned char found_mode_;
};

VCDiffCodeTableData* DecodeTableTest::g_exercise_code_table_ = NULL;

const int DecodeTableTest::instruction_buffer_size =
    VCDiffCodeTableData::kCodeTableSize *
        (1 + (VarintBE<VCDAddress>::kMaxBytes));

TEST_F(DecodeTableTest, ReadAdd) {
  instructions_and_sizes_[0] = 1;
  VarintBE<VCDAddress>::Encode(257, &instructions_and_sizes_[1]);
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(257, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, ReadRun) {
  instructions_and_sizes_[0] = 0;
  VarintBE<VCDAddress>::Encode(111, &instructions_and_sizes_[1]);
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_RUN, found_inst);
  EXPECT_EQ(111, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, ReadCopy) {
  instructions_and_sizes_[0] = 58;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(10, found_size_);
  EXPECT_EQ(2, found_mode_);
}

TEST_F(DecodeTableTest, ReadAddCopy) {
  instructions_and_sizes_[0] = 175;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(1, found_mode_);
}

TEST_F(DecodeTableTest, ReadCopyAdd) {
  instructions_and_sizes_[0] = 255;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, UnGetAdd) {
  instructions_and_sizes_[0] = 1;
  instructions_and_sizes_[1] = 255;
  VarintBE<VCDAddress>::Encode(257, &instructions_and_sizes_[1]);
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(257, found_size_);
  EXPECT_EQ(0, found_mode_);
  reader_.UnGetInstruction();
  found_size_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(257, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, UnGetCopy) {
  instructions_and_sizes_[0] = 58;
  instructions_and_sizes_[1] = 0;
  instructions_and_sizes_[2] = 255;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(10, found_size_);
  EXPECT_EQ(2, found_mode_);
  reader_.UnGetInstruction();
  found_size_ = 0;
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(10, found_size_);
  EXPECT_EQ(2, found_mode_);
}

TEST_F(DecodeTableTest, UnGetCopyAdd) {
  instructions_and_sizes_[0] = 255;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  reader_.UnGetInstruction();
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, UnGetTwice) {
  instructions_and_sizes_[0] = 255;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  reader_.UnGetInstruction();
  reader_.UnGetInstruction();
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  found_mode_ = 0;
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
}

TEST_F(DecodeTableTest, UnGetBeforeGet) {
  instructions_and_sizes_[0] = 255;
  instructions_and_sizes_[1] = 0;
  reader_.UnGetInstruction();
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
}

TEST_F(DecodeTableTest, UnGetAddCopy) {
  instructions_and_sizes_[0] = 175;
  instructions_and_sizes_[1] = 0;
  unsigned char found_inst = reader_.GetNextInstruction(&found_size_,
                                                        &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
  reader_.UnGetInstruction();
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_ADD, found_inst);
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
  found_inst = reader_.GetNextInstruction(&found_size_, &found_mode_);
  EXPECT_EQ(VCD_COPY, found_inst);
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(1, found_mode_);
}

TEST_F(DecodeTableTest, ReReadIncomplete) {
  instructions_and_sizes_[0] = 175;  // Add(1) + Copy1(4)
  instructions_and_sizes_[1] = 1;    // Add(0)
  instructions_and_sizes_[2] = 111;  // with size 111
  instructions_and_sizes_[3] = 255;  // Copy8(4) + Add(1)

  reader_.Init(&instructions_and_sizes_ptr_,
               instructions_and_sizes_ptr_ + 0);  // 0 bytes available
  EXPECT_EQ(VCD_INSTRUCTION_END_OF_DATA,
            reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(&instructions_and_sizes_[0], instructions_and_sizes_ptr_);

  reader_.Init(&instructions_and_sizes_ptr_,
              instructions_and_sizes_ptr_ + 1);  // 1 more byte available
  EXPECT_EQ(VCD_ADD, reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
  EXPECT_EQ(VCD_COPY, reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(1, found_mode_);
  EXPECT_EQ(VCD_INSTRUCTION_END_OF_DATA,
            reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(&instructions_and_sizes_[1], instructions_and_sizes_ptr_);

  reader_.Init(&instructions_and_sizes_ptr_,
              instructions_and_sizes_ptr_ + 1);  // 1 more byte available
  // The opcode is available, but the separately encoded size is not
  EXPECT_EQ(VCD_INSTRUCTION_END_OF_DATA,
            reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(&instructions_and_sizes_[1], instructions_and_sizes_ptr_);

  reader_.Init(&instructions_and_sizes_ptr_,
              instructions_and_sizes_ptr_ + 2);  // 2 more bytes available
  EXPECT_EQ(VCD_ADD, reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(111, found_size_);
  EXPECT_EQ(0, found_mode_);
  EXPECT_EQ(VCD_INSTRUCTION_END_OF_DATA,
            reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(&instructions_and_sizes_[3], instructions_and_sizes_ptr_);

  reader_.Init(&instructions_and_sizes_ptr_,
              instructions_and_sizes_ptr_ + 1);  // 1 more byte available
  EXPECT_EQ(VCD_COPY, reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(4, found_size_);
  EXPECT_EQ(8, found_mode_);
  EXPECT_EQ(VCD_ADD, reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(1, found_size_);
  EXPECT_EQ(0, found_mode_);
  EXPECT_EQ(VCD_INSTRUCTION_END_OF_DATA,
            reader_.GetNextInstruction(&found_size_, &found_mode_));
  EXPECT_EQ(&instructions_and_sizes_[4], instructions_and_sizes_ptr_);
}

TEST_F(DecodeTableTest, ExerciseCodeTableReader) {
  char* instruction_ptr = &instructions_and_sizes_[0];
  for (int opcode = 0; opcode < VCDiffCodeTableData::kCodeTableSize; ++opcode) {
    *instruction_ptr = opcode;
    ++instruction_ptr;
    if ((g_exercise_code_table_->inst1[opcode] != VCD_NOOP) &&
        (g_exercise_code_table_->size1[opcode] == 0)) {
      // A separately-encoded size value
      int encoded_size = VarintBE<VCDAddress>::Encode(1000 + opcode,
                                                      instruction_ptr);
      EXPECT_LT(0, encoded_size);
      instruction_ptr += encoded_size;
    }
    if ((g_exercise_code_table_->inst2[opcode] != VCD_NOOP) &&
        (g_exercise_code_table_->size2[opcode] == 0)) {
      int encoded_size = VarintBE<VCDAddress>::Encode(1000 + opcode,
                                                      instruction_ptr);
      EXPECT_LT(0, encoded_size);
      instruction_ptr += encoded_size;
    }
  }
  EXPECT_TRUE(reader_.UseCodeTable(*g_exercise_code_table_, kLastExerciseMode));
  int opcode = 0;
  // This loop has the same bounds as the one in SetUpTestCase.
  // Iterate over the instruction types and make sure that the opcodes,
  // interpreted in order, return exactly those instruction types.
  for (unsigned char inst_mode1 = 0;
       inst_mode1 <= VCD_LAST_INSTRUCTION_TYPE + kLastExerciseMode;
       ++inst_mode1) {
    unsigned char inst1 = inst_mode1;
    unsigned char mode1 = 0;
    if (inst_mode1 > VCD_COPY) {
      inst1 = VCD_COPY;
      mode1 = inst_mode1 - VCD_COPY;
    }
    for (unsigned char inst_mode2 = 0;
         inst_mode2 <= VCD_LAST_INSTRUCTION_TYPE + kLastExerciseMode;
         ++inst_mode2) {
      unsigned char inst2 = inst_mode2;
      unsigned char mode2 = 0;
      if (inst_mode2 > VCD_COPY) {
        inst2 = VCD_COPY;
        mode2 = inst_mode2 - VCD_COPY;
      }
      VerifyInstModeSize1(inst1, mode1, 0, opcode);
      VerifyInstModeSize2(inst2, mode2, 0, opcode);
      ++opcode;
      VerifyInstModeSize1(inst1, mode1, 0, opcode);
      VerifyInstModeSize2(inst2, mode2, 255, opcode);
      ++opcode;
      VerifyInstModeSize1(inst1, mode1, 255, opcode);
      VerifyInstModeSize2(inst2, mode2, 0, opcode);
      ++opcode;
      VerifyInstModeSize1(inst1, mode1, 255, opcode);
      VerifyInstModeSize2(inst2, mode2, 255, opcode);
      ++opcode;
    }
  }
  CHECK_EQ(VCDiffCodeTableData::kCodeTableSize, opcode);
}

}  // unnamed namespace
}  // namespace open_vcdiff
