// Copyright 2013 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 "content/child/fileapi/webfilewriter_base.h"

#include "base/logging.h"
#include "storage/common/fileapi/file_system_util.h"
#include "third_party/WebKit/public/platform/WebFileError.h"
#include "third_party/WebKit/public/platform/WebFileWriterClient.h"
#include "third_party/WebKit/public/platform/WebURL.h"

using storage::FileErrorToWebFileError;

namespace content {

WebFileWriterBase::WebFileWriterBase(const GURL& path,
                                     blink::WebFileWriterClient* client)
    : path_(path),
      client_(client),
      operation_(kOperationNone),
      cancel_state_(kCancelNotInProgress) {}

WebFileWriterBase::~WebFileWriterBase() {}

void WebFileWriterBase::truncate(long long length) {
  DCHECK(kOperationNone == operation_);
  DCHECK(kCancelNotInProgress == cancel_state_);
  operation_ = kOperationTruncate;
  DoTruncate(path_, length);
}

void WebFileWriterBase::write(
      long long position,
      const blink::WebString& id) {
  DCHECK_EQ(kOperationNone, operation_);
  DCHECK_EQ(kCancelNotInProgress, cancel_state_);
  operation_ = kOperationWrite;
  DoWrite(path_, id.utf8(), 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 WebFileWriterBase::cancel() {
  // Check for the cancel passing the previous operation's return in-flight.
  if (kOperationWrite != operation_ && kOperationTruncate != operation_)
    return;
  if (kCancelNotInProgress != cancel_state_)
    return;
  cancel_state_ = kCancelSent;
  DoCancel();
}

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

void WebFileWriterBase::DidWrite(int64_t bytes, bool complete) {
  DCHECK(kOperationWrite == operation_);
  switch (cancel_state_) {
    case kCancelNotInProgress:
      if (complete)
        operation_ = kOperationNone;
      client_->didWrite(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 WebFileWriterBase::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(kOperationTruncate == operation_);
      operation_ = kOperationNone;
      client_->didTruncate();
      break;
    case kCancelSent:
      DCHECK(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 WebFileWriterBase::DidFail(base::File::Error error_code) {
  DCHECK(kOperationNone != operation_);
  switch (cancel_state_) {
    case kCancelNotInProgress:
      // A write or truncate failed.
      operation_ = kOperationNone;
      client_->didFail(FileErrorToWebFileError(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 WebFileWriterBase::FinishCancel() {
  DCHECK(kCancelReceivedWriteResponse == cancel_state_);
  DCHECK(kOperationNone != operation_);
  cancel_state_ = kCancelNotInProgress;
  operation_ = kOperationNone;
  client_->didFail(blink::WebFileErrorAbort);
}

}  // namespace content
