/*
 * Copyright (C) 2010 Google Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "third_party/blink/renderer/modules/filesystem/file_writer_base.h"

#include <memory>
#include "third_party/blink/renderer/core/events/progress_event.h"
#include "third_party/blink/renderer/core/fileapi/blob.h"
#include "third_party/blink/renderer/core/fileapi/file_error.h"

namespace blink {

FileWriterBase::~FileWriterBase() = default;

void FileWriterBase::Initialize(const KURL& path, int64_t length) {
  DCHECK_GE(length, 0);
  length_ = length;
  path_ = path;
}

FileWriterBase::FileWriterBase()
    : position_(0),
      operation_(kOperationNone),
      cancel_state_(kCancelNotInProgress) {}

void FileWriterBase::SeekInternal(int64_t position) {
  if (position > length_)
    position = length_;
  else if (position < 0)
    position = length_ + position;
  if (position < 0)
    position = 0;
  position_ = position;
}

void FileWriterBase::Truncate(int64_t length) {
  DCHECK_EQ(kOperationNone, operation_);
  DCHECK_EQ(kCancelNotInProgress, cancel_state_);
  operation_ = kOperationTruncate;
  DoTruncate(path_, length);
}

void FileWriterBase::Write(int64_t position, const Blob& blob) {
  DCHECK_EQ(kOperationNone, operation_);
  DCHECK_EQ(kCancelNotInProgress, cancel_state_);
  operation_ = kOperationWrite;
  DoWrite(path_, blob, position);
}

// When we cancel a write/truncate, we always get back the result of the write
// before the result of the cancel, no matter what happens.
// So we'll get back either
//   success [of the write/truncate, in a DidWrite(XXX, true)/DidSucceed() call]
//     followed by failure [of the cancel]; or
//   failure [of the write, either from cancel or other reasons] followed by
//     the result of the cancel.
// In the write case, there could also be queued up non-terminal DidWrite calls
// before any of that comes back, but there will always be a terminal write
// response [success or failure] after them, followed by the cancel result, so
// we can ignore non-terminal write responses, take the terminal write success
// or the first failure as the last write response, then know that the next
// thing to come back is the cancel response.  We only notify the
// AsyncFileWriterClient when it's all over.
void FileWriterBase::Cancel() {
  // Check for the cancel passing the previous operation's return in-flight.
  if (operation_ != kOperationWrite && operation_ != kOperationTruncate)
    return;
  if (cancel_state_ != kCancelNotInProgress)
    return;
  cancel_state_ = kCancelSent;
  DoCancel();
}

void FileWriterBase::DidFinish(base::File::Error error_code) {
  if (error_code == base::File::FILE_OK)
    DidSucceed();
  else
    DidFail(error_code);
}

void FileWriterBase::DidWrite(int64_t bytes, bool complete) {
  DCHECK_EQ(kOperationWrite, operation_);
  switch (cancel_state_) {
    case kCancelNotInProgress:
      if (complete)
        operation_ = kOperationNone;
      DidWriteImpl(bytes, complete);
      break;
    case kCancelSent:
      // This is the success call of the write, which we'll eat, even though
      // it succeeded before the cancel got there.  We accepted the cancel call,
      // so the write will eventually return an error.
      if (complete)
        cancel_state_ = kCancelReceivedWriteResponse;
      break;
    case kCancelReceivedWriteResponse:
    default:
      NOTREACHED();
  }
}

void FileWriterBase::DidSucceed() {
  // Write never gets a DidSucceed call, so this is either a cancel or truncate
  // response.
  switch (cancel_state_) {
    case kCancelNotInProgress:
      // A truncate succeeded, with no complications.
      DCHECK_EQ(kOperationTruncate, operation_);
      operation_ = kOperationNone;
      DidTruncateImpl();
      break;
    case kCancelSent:
      DCHECK_EQ(kOperationTruncate, operation_);
      // This is the success call of the truncate, which we'll eat, even though
      // it succeeded before the cancel got there.  We accepted the cancel call,
      // so the truncate will eventually return an error.
      cancel_state_ = kCancelReceivedWriteResponse;
      break;
    case kCancelReceivedWriteResponse:
      // This is the success of the cancel operation.
      FinishCancel();
      break;
    default:
      NOTREACHED();
  }
}

void FileWriterBase::DidFail(base::File::Error error_code) {
  DCHECK_NE(kOperationNone, operation_);
  switch (cancel_state_) {
    case kCancelNotInProgress:
      // A write or truncate failed.
      operation_ = kOperationNone;
      DidFailImpl(error_code);
      break;
    case kCancelSent:
      // This is the failure of a write or truncate; the next message should be
      // the result of the cancel.  We don't assume that it'll be a success, as
      // the write/truncate could have failed for other reasons.
      cancel_state_ = kCancelReceivedWriteResponse;
      break;
    case kCancelReceivedWriteResponse:
      // The cancel reported failure, meaning that the write or truncate
      // finished before the cancel got there.  But we suppressed the
      // write/truncate's response, and will now report that it was cancelled.
      FinishCancel();
      break;
    default:
      NOTREACHED();
  }
}

void FileWriterBase::FinishCancel() {
  DCHECK_EQ(kCancelReceivedWriteResponse, cancel_state_);
  DCHECK_NE(kOperationNone, operation_);
  cancel_state_ = kCancelNotInProgress;
  operation_ = kOperationNone;
  DidFailImpl(base::File::FILE_ERROR_ABORT);
}

}  // namespace blink
