// Copyright (c) 2012 The Chromium 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 "net/base/elements_upload_data_stream.h"

#include "base/bind.h"
#include "base/logging.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "net/base/upload_bytes_element_reader.h"
#include "net/base/upload_element_reader.h"

namespace net {

ElementsUploadDataStream::ElementsUploadDataStream(
    std::vector<std::unique_ptr<UploadElementReader>> element_readers,
    int64_t identifier)
    : UploadDataStream(false, identifier),
      element_readers_(std::move(element_readers)),
      element_index_(0),
      read_error_(OK),
      weak_ptr_factory_(this) {}

ElementsUploadDataStream::~ElementsUploadDataStream() = default;

std::unique_ptr<UploadDataStream> ElementsUploadDataStream::CreateWithReader(
    std::unique_ptr<UploadElementReader> reader,
    int64_t identifier) {
  std::vector<std::unique_ptr<UploadElementReader>> readers;
  readers.push_back(std::move(reader));
  return std::unique_ptr<UploadDataStream>(
      new ElementsUploadDataStream(std::move(readers), identifier));
}

int ElementsUploadDataStream::InitInternal(const NetLogWithSource& net_log) {
  return InitElements(0);
}

int ElementsUploadDataStream::ReadInternal(
    IOBuffer* buf,
    int buf_len) {
  DCHECK_GT(buf_len, 0);
  return ReadElements(new DrainableIOBuffer(buf, buf_len));
}

bool ElementsUploadDataStream::IsInMemory() const {
  for (const std::unique_ptr<UploadElementReader>& it : element_readers_) {
    if (!it->IsInMemory())
      return false;
  }
  return true;
}

const std::vector<std::unique_ptr<UploadElementReader>>*
ElementsUploadDataStream::GetElementReaders() const {
  return &element_readers_;
}

void ElementsUploadDataStream::ResetInternal() {
  weak_ptr_factory_.InvalidateWeakPtrs();
  read_error_ = OK;
  element_index_ = 0;
}

int ElementsUploadDataStream::InitElements(size_t start_index) {
  // Call Init() for all elements.
  for (size_t i = start_index; i < element_readers_.size(); ++i) {
    UploadElementReader* reader = element_readers_[i].get();
    // When new_result is ERR_IO_PENDING, InitInternal() will be called
    // with start_index == i + 1 when reader->Init() finishes.
    int result = reader->Init(
        base::BindOnce(&ElementsUploadDataStream::OnInitElementCompleted,
                       weak_ptr_factory_.GetWeakPtr(), i));
    DCHECK(result != ERR_IO_PENDING || !reader->IsInMemory());
    DCHECK_LE(result, OK);
    if (result != OK)
      return result;
  }

  uint64_t total_size = 0;
  for (const std::unique_ptr<UploadElementReader>& it : element_readers_) {
    total_size += it->GetContentLength();
  }
  SetSize(total_size);
  return OK;
}

void ElementsUploadDataStream::OnInitElementCompleted(size_t index,
                                                      int result) {
  DCHECK_NE(ERR_IO_PENDING, result);

  // Check the last result.
  if (result == OK)
    result = InitElements(index + 1);

  if (result != ERR_IO_PENDING)
    OnInitCompleted(result);
}

int ElementsUploadDataStream::ReadElements(
    const scoped_refptr<DrainableIOBuffer>& buf) {
  while (read_error_ == OK && element_index_ < element_readers_.size()) {
    UploadElementReader* reader = element_readers_[element_index_].get();

    if (reader->BytesRemaining() == 0) {
      ++element_index_;
      continue;
    }

    if (buf->BytesRemaining() == 0)
      break;

    int result = reader->Read(
        buf.get(), buf->BytesRemaining(),
        base::BindOnce(&ElementsUploadDataStream::OnReadElementCompleted,
                       weak_ptr_factory_.GetWeakPtr(), buf));
    if (result == ERR_IO_PENDING)
      return ERR_IO_PENDING;
    ProcessReadResult(buf, result);
  }

  if (buf->BytesConsumed() > 0)
    return buf->BytesConsumed();

  return read_error_;
}

void ElementsUploadDataStream::OnReadElementCompleted(
    const scoped_refptr<DrainableIOBuffer>& buf,
    int result) {
  ProcessReadResult(buf, result);

  result = ReadElements(buf);
  if (result != ERR_IO_PENDING)
    OnReadCompleted(result);
}

void ElementsUploadDataStream::ProcessReadResult(
    const scoped_refptr<DrainableIOBuffer>& buf,
    int result) {
  DCHECK_NE(ERR_IO_PENDING, result);
  DCHECK(!read_error_);

  if (result >= 0) {
    buf->DidConsume(result);
  } else {
    read_error_ = result;
  }
}

}  // namespace net
