// 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 "ppapi/proxy/file_ref_resource.h"

#include <stddef.h>

#include "base/numerics/safe_conversions.h"
#include "ppapi/c/pp_directory_entry.h"
#include "ppapi/c/pp_instance.h"
#include "ppapi/c/pp_resource.h"
#include "ppapi/proxy/ppapi_messages.h"
#include "ppapi/shared_impl/array_writer.h"
#include "ppapi/shared_impl/file_ref_util.h"
#include "ppapi/shared_impl/resource.h"
#include "ppapi/shared_impl/resource_tracker.h"
#include "ppapi/shared_impl/var.h"
#include "ppapi/thunk/enter.h"
#include "ppapi/thunk/ppb_file_system_api.h"

namespace ppapi {
namespace proxy {

FileRefResource::FileRefResource(
    Connection connection,
    PP_Instance instance,
    const FileRefCreateInfo& create_info)
    : PluginResource(connection, instance),
      create_info_(create_info),
      file_system_resource_(create_info.file_system_plugin_resource) {
  if (uses_internal_paths()) {
    // If path ends with a slash, then normalize it away unless path is
    // the root path.
    int path_size = base::checked_cast<int>(create_info_.internal_path.size());
    if (path_size > 1 && create_info_.internal_path.at(path_size - 1) == '/')
      create_info_.internal_path.erase(path_size - 1, 1);

    path_var_ = new StringVar(create_info_.internal_path);
    create_info_.display_name = GetNameForInternalFilePath(
        create_info_.internal_path);
  } else {
    DCHECK(!create_info_.display_name.empty());
  }
  name_var_ = new StringVar(create_info_.display_name);

  if (create_info_.browser_pending_host_resource_id != 0 &&
      create_info_.renderer_pending_host_resource_id != 0) {
    AttachToPendingHost(BROWSER, create_info_.browser_pending_host_resource_id);
    AttachToPendingHost(RENDERER,
                        create_info_.renderer_pending_host_resource_id);
  } else {
    CHECK_EQ(0, create_info_.browser_pending_host_resource_id);
    CHECK_EQ(0, create_info_.renderer_pending_host_resource_id);
    CHECK(uses_internal_paths());
    SendCreate(BROWSER, PpapiHostMsg_FileRef_CreateForFileAPI(
        create_info.file_system_plugin_resource,
        create_info.internal_path));
    SendCreate(RENDERER, PpapiHostMsg_FileRef_CreateForFileAPI(
        create_info.file_system_plugin_resource,
        create_info.internal_path));
  }
}

FileRefResource::~FileRefResource() {
}

// static
PP_Resource FileRefResource::CreateFileRef(
    Connection connection,
    PP_Instance instance,
    const FileRefCreateInfo& create_info) {
  // If we have a valid file_system resource, ensure that its type matches that
  // of the fs_type parameter.
  if (create_info.file_system_plugin_resource != 0) {
    thunk::EnterResourceNoLock<thunk::PPB_FileSystem_API> enter(
        create_info.file_system_plugin_resource, true);
    if (enter.failed())
      return 0;
    if (enter.object()->GetType() != create_info.file_system_type) {
      NOTREACHED() << "file system type mismatch with resource";
      return 0;
    }
  }

  if (create_info.file_system_type == PP_FILESYSTEMTYPE_LOCALPERSISTENT ||
      create_info.file_system_type == PP_FILESYSTEMTYPE_LOCALTEMPORARY) {
    if (!IsValidInternalPath(create_info.internal_path))
      return 0;
  }
  return (new FileRefResource(connection,
                              instance,
                              create_info))->GetReference();
}

thunk::PPB_FileRef_API* FileRefResource::AsPPB_FileRef_API() {
  return this;
}

PP_FileSystemType FileRefResource::GetFileSystemType() const {
  return create_info_.file_system_type;
}

PP_Var FileRefResource::GetName() const {
  return name_var_->GetPPVar();
}

PP_Var FileRefResource::GetPath() const {
  if (!uses_internal_paths())
    return PP_MakeUndefined();
  return path_var_->GetPPVar();
}

PP_Resource FileRefResource::GetParent() {
  if (!uses_internal_paths())
    return 0;

  size_t pos = create_info_.internal_path.rfind('/');
  CHECK(pos != std::string::npos);
  if (pos == 0)
    pos++;
  std::string parent_path = create_info_.internal_path.substr(0, pos);

  ppapi::FileRefCreateInfo parent_info;
  parent_info.file_system_type = create_info_.file_system_type;
  parent_info.internal_path = parent_path;
  parent_info.display_name = GetNameForInternalFilePath(parent_path);
  parent_info.file_system_plugin_resource =
      create_info_.file_system_plugin_resource;

  return (new FileRefResource(connection(),
                              pp_instance(),
                              parent_info))->GetReference();
}

int32_t FileRefResource::MakeDirectory(
    int32_t make_directory_flags,
    scoped_refptr<TrackedCallback> callback) {
  Call<PpapiPluginMsg_FileRef_MakeDirectoryReply>(
      BROWSER,
      PpapiHostMsg_FileRef_MakeDirectory(make_directory_flags),
      base::Bind(&FileRefResource::RunTrackedCallback, this, callback));
  return PP_OK_COMPLETIONPENDING;
}

int32_t FileRefResource::Touch(PP_Time last_access_time,
                               PP_Time last_modified_time,
                               scoped_refptr<TrackedCallback> callback) {
  Call<PpapiPluginMsg_FileRef_TouchReply>(
      BROWSER,
      PpapiHostMsg_FileRef_Touch(last_access_time,
                                 last_modified_time),
      base::Bind(&FileRefResource::RunTrackedCallback, this, callback));
  return PP_OK_COMPLETIONPENDING;
}

int32_t FileRefResource::Delete(scoped_refptr<TrackedCallback> callback) {
  Call<PpapiPluginMsg_FileRef_DeleteReply>(
      BROWSER,
      PpapiHostMsg_FileRef_Delete(),
      base::Bind(&FileRefResource::RunTrackedCallback, this, callback));
  return PP_OK_COMPLETIONPENDING;
}

int32_t FileRefResource::Rename(PP_Resource new_file_ref,
                                scoped_refptr<TrackedCallback> callback) {
  Call<PpapiPluginMsg_FileRef_RenameReply>(
      BROWSER,
      PpapiHostMsg_FileRef_Rename(new_file_ref),
      base::Bind(&FileRefResource::RunTrackedCallback, this, callback));
  return PP_OK_COMPLETIONPENDING;
}

int32_t FileRefResource::Query(PP_FileInfo* info,
                               scoped_refptr<TrackedCallback> callback) {
  if (info == NULL)
    return PP_ERROR_BADARGUMENT;

  Call<PpapiPluginMsg_FileRef_QueryReply>(
      BROWSER,
      PpapiHostMsg_FileRef_Query(),
      base::Bind(&FileRefResource::OnQueryReply, this, info, callback));
  return PP_OK_COMPLETIONPENDING;
}

int32_t FileRefResource::ReadDirectoryEntries(
    const PP_ArrayOutput& output,
    scoped_refptr<TrackedCallback> callback) {
  Call<PpapiPluginMsg_FileRef_ReadDirectoryEntriesReply>(
      BROWSER,
      PpapiHostMsg_FileRef_ReadDirectoryEntries(),
      base::Bind(&FileRefResource::OnDirectoryEntriesReply,
                 this, output, callback));
  return PP_OK_COMPLETIONPENDING;
}

const FileRefCreateInfo& FileRefResource::GetCreateInfo() const {
  return create_info_;
}

PP_Var FileRefResource::GetAbsolutePath() {
  if (!absolute_path_var_.get()) {
    std::string absolute_path;
    int32_t result = SyncCall<PpapiPluginMsg_FileRef_GetAbsolutePathReply>(
        BROWSER, PpapiHostMsg_FileRef_GetAbsolutePath(), &absolute_path);
    if (result != PP_OK)
      return PP_MakeUndefined();
    absolute_path_var_ = new StringVar(absolute_path);
  }
  return absolute_path_var_->GetPPVar();
}

void FileRefResource::RunTrackedCallback(
    scoped_refptr<TrackedCallback> callback,
    const ResourceMessageReplyParams& params) {
  if (TrackedCallback::IsPending(callback))
    callback->Run(params.result());
}

void FileRefResource::OnQueryReply(
    PP_FileInfo* out_info,
    scoped_refptr<TrackedCallback> callback,
    const ResourceMessageReplyParams& params,
    const PP_FileInfo& info) {
  if (!TrackedCallback::IsPending(callback))
    return;

  if (params.result() == PP_OK)
    *out_info = info;
  callback->Run(params.result());
}

void FileRefResource::OnDirectoryEntriesReply(
    const PP_ArrayOutput& output,
    scoped_refptr<TrackedCallback> callback,
    const ResourceMessageReplyParams& params,
    const std::vector<ppapi::FileRefCreateInfo>& infos,
    const std::vector<PP_FileType>& file_types) {
  if (!TrackedCallback::IsPending(callback))
    return;

  if (params.result() == PP_OK) {
    ArrayWriter writer(output);
    if (!writer.is_valid()) {
      callback->Run(PP_ERROR_BADARGUMENT);
      return;
    }

    std::vector<PP_DirectoryEntry> entries;
    for (size_t i = 0; i < infos.size(); ++i) {
      PP_DirectoryEntry entry;
      entry.file_ref = FileRefResource::CreateFileRef(connection(),
                                                      pp_instance(),
                                                      infos[i]);
      entry.file_type = file_types[i];
      entries.push_back(entry);
    }

    writer.StoreVector(entries);
  }
  callback->Run(params.result());
}

bool FileRefResource::uses_internal_paths() const {
  return (create_info_.file_system_type != PP_FILESYSTEMTYPE_EXTERNAL) ||
         !create_info_.internal_path.empty();
}

}  // namespace proxy
}  // namespace ppapi
