/*
 * 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/dom_file_system_sync.h"

#include <memory>

#include "base/memory/ptr_util.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/fileapi/file.h"
#include "third_party/blink/renderer/core/fileapi/file_error.h"
#include "third_party/blink/renderer/modules/filesystem/directory_entry_sync.h"
#include "third_party/blink/renderer/modules/filesystem/dom_file_path.h"
#include "third_party/blink/renderer/modules/filesystem/file_entry_sync.h"
#include "third_party/blink/renderer/modules/filesystem/file_system_callbacks.h"
#include "third_party/blink/renderer/modules/filesystem/file_system_dispatcher.h"
#include "third_party/blink/renderer/modules/filesystem/file_writer_sync.h"
#include "third_party/blink/renderer/modules/filesystem/sync_callback_helper.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/file_metadata.h"

namespace blink {

class FileWriterBase;

DOMFileSystemSync::DOMFileSystemSync(DOMFileSystemBase* file_system)
    : DOMFileSystemSync(file_system->context_,
                        file_system->name(),
                        file_system->GetType(),
                        file_system->RootURL()) {}

DOMFileSystemSync::DOMFileSystemSync(ExecutionContext* context,
                                     const String& name,
                                     mojom::blink::FileSystemType type,
                                     const KURL& root_url)
    : DOMFileSystemBase(context, name, type, root_url),
      root_entry_(
          MakeGarbageCollected<DirectoryEntrySync>(this, DOMFilePath::kRoot)) {}

DOMFileSystemSync::~DOMFileSystemSync() = default;

void DOMFileSystemSync::ReportError(ErrorCallback error_callback,
                                    base::File::Error error) {
  std::move(error_callback).Run(error);
}

DirectoryEntrySync* DOMFileSystemSync::root() {
  return root_entry_.Get();
}

namespace {

class CreateFileHelper final : public SnapshotFileCallbackBase {
 public:
  class CreateFileResult : public GarbageCollected<CreateFileResult> {
   public:
    static CreateFileResult* Create() {
      return MakeGarbageCollected<CreateFileResult>();
    }

    CreateFileResult() : failed_(false), error_(base::File::FILE_OK) {}

    bool failed_;
    base::File::Error error_;
    Member<File> file_;

    void Trace(Visitor* visitor) const { visitor->Trace(file_); }
  };

  static std::unique_ptr<SnapshotFileCallbackBase> Create(
      ExecutionContext* context,
      CreateFileResult* result,
      const String& name,
      const KURL& url,
      mojom::blink::FileSystemType type) {
    return base::WrapUnique(static_cast<SnapshotFileCallbackBase*>(
        new CreateFileHelper(context, result, name, url, type)));
  }

  void DidFail(base::File::Error error) override {
    result_->failed_ = true;
    result_->error_ = error;
  }

  ~CreateFileHelper() override = default;

  void DidCreateSnapshotFile(const FileMetadata& metadata) override {
    result_->file_ =
        DOMFileSystemBase::CreateFile(context_, metadata, url_, type_, name_);
  }

 private:
  CreateFileHelper(ExecutionContext* context,
                   CreateFileResult* result,
                   const String& name,
                   const KURL& url,
                   mojom::blink::FileSystemType type)
      : context_(context),
        result_(result),
        name_(name),
        url_(url),
        type_(type) {}

  Persistent<ExecutionContext> context_;
  Persistent<CreateFileResult> result_;
  String name_;
  KURL url_;
  mojom::blink::FileSystemType type_;
};

}  // namespace

File* DOMFileSystemSync::CreateFile(const FileEntrySync* file_entry,
                                    ExceptionState& exception_state) {
  KURL file_system_url = CreateFileSystemURL(file_entry);
  CreateFileHelper::CreateFileResult* result(
      CreateFileHelper::CreateFileResult::Create());
  FileSystemDispatcher::From(context_).CreateSnapshotFileSync(
      file_system_url,
      CreateFileHelper::Create(context_, result, file_entry->name(),
                               file_system_url, GetType()));
  if (result->failed_) {
    file_error::ThrowDOMException(
        exception_state, result->error_,
        StrCat({"Could not create '", file_entry->name(), "'."}));
    return nullptr;
  }
  return result->file_.Get();
}

FileWriterSync* DOMFileSystemSync::CreateWriter(
    const FileEntrySync* file_entry,
    ExceptionState& exception_state) {
  DCHECK(file_entry);

  auto* file_writer = MakeGarbageCollected<FileWriterSync>(context_);

  auto* sync_helper = MakeGarbageCollected<FileWriterCallbacksSyncHelper>();

  auto success_callback_wrapper =
      BindOnce(&FileWriterCallbacksSyncHelper::OnSuccess,
               WrapPersistentIfNeeded(sync_helper));
  auto error_callback_wrapper =
      blink::BindOnce(&FileWriterCallbacksSyncHelper::OnError,
                      WrapPersistentIfNeeded(sync_helper));

  auto callbacks = std::make_unique<FileWriterCallbacks>(
      file_writer, std::move(success_callback_wrapper),
      std::move(error_callback_wrapper), context_);

  FileSystemDispatcher::From(context_).InitializeFileWriterSync(
      CreateFileSystemURL(file_entry), std::move(callbacks));

  FileWriterBase* success = sync_helper->GetResultOrThrow(exception_state);
  return success ? file_writer : nullptr;
}

void DOMFileSystemSync::Trace(Visitor* visitor) const {
  visitor->Trace(root_entry_);
  DOMFileSystemBase::Trace(visitor);
}

}  // namespace blink
