// Copyright 2017 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "bsdiff/brotli_compressor.h"

#include "bsdiff/logging.h"

namespace {

const size_t kBufferSize = 1024 * 1024;
const uint32_t kBrotliDefaultLgwin = 20;

}  // namespace

namespace bsdiff {
BrotliCompressor::BrotliCompressor(int quality) : comp_buffer_(kBufferSize) {
  brotli_encoder_state_ =
      BrotliEncoderCreateInstance(nullptr, nullptr, nullptr);
  if (!brotli_encoder_state_) {
    LOG(ERROR) << "Failed to initialize brotli decoder state";
  } else {
    int compression_quality = quality;
    if (compression_quality > BROTLI_MAX_QUALITY ||
        compression_quality < BROTLI_MIN_QUALITY) {
      LOG(ERROR) << "Invalid quality value: " << quality
                 << ", using default quality instead.";
      compression_quality = BROTLI_MAX_QUALITY;
    }

    BrotliEncoderSetParameter(brotli_encoder_state_, BROTLI_PARAM_QUALITY,
                              compression_quality);
    BrotliEncoderSetParameter(brotli_encoder_state_, BROTLI_PARAM_LGWIN,
                              kBrotliDefaultLgwin);
  }
}

BrotliCompressor::~BrotliCompressor() {
  if (brotli_encoder_state_) {
    BrotliEncoderDestroyInstance(brotli_encoder_state_);
  }
}

bool BrotliCompressor::Write(const uint8_t* buf, size_t size) {
  if (!brotli_encoder_state_)
    return false;

  const uint8_t* next_in = buf;
  size_t avail_in = size;
  while (avail_in > 0) {
    size_t avail_out = kBufferSize;
    uint8_t* next_out = comp_buffer_.buffer_data();
    if (!BrotliEncoderCompressStream(
            brotli_encoder_state_, BROTLI_OPERATION_PROCESS, &avail_in,
            &next_in, &avail_out, &next_out, nullptr)) {
      LOG(ERROR) << "BrotliCompressor failed to compress " << avail_in
                 << " bytes of data.";
      return false;
    }

    uint64_t output_bytes = comp_buffer_.buffer_size() - avail_out;
    if (output_bytes > 0) {
      comp_buffer_.AddDataToChunks(output_bytes);
    }
  }
  return true;
}

bool BrotliCompressor::Finish() {
  if (!brotli_encoder_state_)
    return false;

  const uint8_t* next_in = nullptr;
  size_t avail_in = 0;
  while (!BrotliEncoderIsFinished(brotli_encoder_state_)) {
    size_t avail_out = kBufferSize;
    uint8_t* next_out = comp_buffer_.buffer_data();
    if (!BrotliEncoderCompressStream(
            brotli_encoder_state_, BROTLI_OPERATION_FINISH, &avail_in, &next_in,
            &avail_out, &next_out, nullptr)) {
      LOG(ERROR) << "BrotliCompressor failed to finish compression";
      return false;
    }

    uint64_t output_bytes = comp_buffer_.buffer_size() - avail_out;
    if (output_bytes > 0) {
      comp_buffer_.AddDataToChunks(output_bytes);
    }
  }
  return true;
}

const std::vector<uint8_t>& BrotliCompressor::GetCompressedData() {
  return comp_buffer_.GetCompressedData();
}

}  // namespace bsdiff