// Copyright (c) 2016 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.

#include "atom/browser/atom_blob_reader.h"

#include <utility>

#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_reader.h"
#include "storage/browser/blob/blob_storage_context.h"

#include "atom/common/node_includes.h"

using content::BrowserThread;

namespace atom {

namespace {

void FreeNodeBufferData(char* data, void* hint) {
  delete[] data;
}

void RunCallbackInUI(const AtomBlobReader::CompletionCallback& callback,
                     char* blob_data,
                     int size) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  v8::Isolate* isolate = v8::Isolate::GetCurrent();
  v8::Locker locker(isolate);
  v8::HandleScope handle_scope(isolate);
  if (blob_data) {
    v8::Local<v8::Value> buffer =
        node::Buffer::New(isolate, blob_data, static_cast<size_t>(size),
                          &FreeNodeBufferData, nullptr)
            .ToLocalChecked();
    callback.Run(buffer);
  } else {
    callback.Run(v8::Null(isolate));
  }
}

}  // namespace

AtomBlobReader::AtomBlobReader(content::ChromeBlobStorageContext* blob_context)
    : blob_context_(blob_context) {}

AtomBlobReader::~AtomBlobReader() {}

void AtomBlobReader::StartReading(
    const std::string& uuid,
    const AtomBlobReader::CompletionCallback& completion_callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  auto blob_data_handle = blob_context_->context()->GetBlobDataFromUUID(uuid);
  auto callback = base::Bind(&RunCallbackInUI, completion_callback);
  if (!blob_data_handle) {
    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                            base::BindOnce(callback, nullptr, 0));
    return;
  }

  auto blob_reader = blob_data_handle->CreateReader();
  BlobReadHelper* blob_read_helper =
      new BlobReadHelper(std::move(blob_reader), callback);
  blob_read_helper->Read();
}

AtomBlobReader::BlobReadHelper::BlobReadHelper(
    std::unique_ptr<storage::BlobReader> blob_reader,
    const BlobReadHelper::CompletionCallback& callback)
    : blob_reader_(std::move(blob_reader)), completion_callback_(callback) {}

AtomBlobReader::BlobReadHelper::~BlobReadHelper() {}

void AtomBlobReader::BlobReadHelper::Read() {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  storage::BlobReader::Status size_status = blob_reader_->CalculateSize(
      base::Bind(&AtomBlobReader::BlobReadHelper::DidCalculateSize,
                 base::Unretained(this)));
  if (size_status != storage::BlobReader::Status::IO_PENDING)
    DidCalculateSize(net::OK);
}

void AtomBlobReader::BlobReadHelper::DidCalculateSize(int result) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  if (result != net::OK) {
    DidReadBlobData(nullptr, 0);
    return;
  }

  uint64_t total_size = blob_reader_->total_size();
  int bytes_read = 0;
  scoped_refptr<net::IOBuffer> blob_data =
      new net::IOBuffer(static_cast<size_t>(total_size));
  auto callback =
      base::Bind(&AtomBlobReader::BlobReadHelper::DidReadBlobData,
                 base::Unretained(this), base::RetainedRef(blob_data));
  storage::BlobReader::Status read_status =
      blob_reader_->Read(blob_data.get(), total_size, &bytes_read, callback);
  if (read_status != storage::BlobReader::Status::IO_PENDING)
    callback.Run(bytes_read);
}

void AtomBlobReader::BlobReadHelper::DidReadBlobData(
    const scoped_refptr<net::IOBuffer>& blob_data,
    int size) {
  DCHECK_CURRENTLY_ON(BrowserThread::IO);

  char* data = new char[size];
  memcpy(data, blob_data->data(), size);
  BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
                          base::BindOnce(completion_callback_, data, size));
  delete this;
}

}  // namespace atom
