// Copyright 2020 Google LLC
//
// 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.
//
///////////////////////////////////////////////////////////////////////////////

#include "tink/cc/python_input_stream.h"

#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>

#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/match.h"
#include "tink/subtle/subtle_util.h"
#include "tink/cc/python_file_object_adapter.h"

namespace crypto {
namespace tink {

namespace {

bool is_eof(const absl::Status& status) {
  return status.code() == absl::StatusCode::kUnknown &&
         absl::StrContains(status.message(), "EOFError");
}

}  // namespace

PythonInputStream::PythonInputStream(
    std::shared_ptr<PythonFileObjectAdapter> adapter, int buffer_size) {
  if (buffer_size <= 0) buffer_size = 128 * 1024;  // 128 KB
  adapter_ = adapter;
  count_in_buffer_ = 0;
  count_backedup_ = 0;
  position_ = 0;
  subtle::ResizeStringUninitialized(&buffer_, buffer_size);
  buffer_offset_ = 0;
  status_ = absl::OkStatus();
}

absl::StatusOr<int> PythonInputStream::Next(const void** data) {
  if (!status_.ok()) return status_;

  if (count_backedup_ > 0) {  // Return the backed-up bytes.
    buffer_offset_ += count_in_buffer_ - count_backedup_;
    count_in_buffer_ = count_backedup_;
    count_backedup_ = 0;
    position_ += count_in_buffer_;
    *data = &buffer_[buffer_offset_];
    return count_in_buffer_;
  }

  // Read new bytes to buffer_.
  auto read_result = adapter_->Read(buffer_.size());
  if (is_eof(read_result.status())) {
    return status_ = absl::Status(absl::StatusCode::kOutOfRange, "EOF");
  } else if (read_result.status().code() == absl::StatusCode::kOutOfRange) {
    // We need to change the error code because for InputStream OUT_OF_RANGE
    // status always means EOF.
    return status_ = absl::Status(absl::StatusCode::kUnknown,
                                  read_result.status().message());
  } else if (!read_result.ok()) {
    return status_ = read_result.status();
  }
  std::string read_string = read_result.value();
  int count_read = read_string.length();
  buffer_.replace(0, count_read, read_string);
  buffer_offset_ = 0;
  count_backedup_ = 0;
  count_in_buffer_ = count_read;
  position_ += count_in_buffer_;
  *data = &buffer_[0];
  return count_in_buffer_;
}

void PythonInputStream::BackUp(int count) {
  if (!status_.ok() || count < 1 || count_backedup_ == count_in_buffer_) return;
  int actual_count = std::min(count, count_in_buffer_ - count_backedup_);
  count_backedup_ += actual_count;
  position_ -= actual_count;
}

PythonInputStream::~PythonInputStream() = default;

int64_t PythonInputStream::Position() const { return position_; }

}  // namespace tink
}  // namespace crypto
