// Copyright (c) 2012 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 "chrome/browser/chromeos/file_manager/open_util.h"

#include <set>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/file_tasks.h"
#include "chrome/browser/chromeos/file_manager/fileapi_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/chromeos/file_manager/url_util.h"
#include "chrome/browser/extensions/api/file_handlers/directory_util.h"
#include "chrome/browser/extensions/api/file_handlers/mime_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/user_metrics.h"
#include "extensions/browser/entry_info.h"
#include "storage/browser/fileapi/file_system_backend.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_operation_runner.h"
#include "storage/browser/fileapi/file_system_url.h"

using content::BrowserThread;
using storage::FileSystemURL;

namespace file_manager {
namespace util {
namespace {

bool shell_operations_allowed = true;

// Executes the |task| for the file specified by |url|.
void ExecuteFileTaskForUrl(Profile* profile,
                           const file_tasks::TaskDescriptor& task,
                           const GURL& url) {
  if (!shell_operations_allowed)
    return;
  storage::FileSystemContext* file_system_context =
      GetFileSystemContextForExtensionId(profile, kFileManagerAppId);

  file_tasks::ExecuteFileTask(
      profile,
      GetFileManagerMainPageUrl(),  // Executing task on behalf of Files.app.
      task, std::vector<FileSystemURL>(1, file_system_context->CrackURL(url)),
      file_tasks::FileTaskFinishedCallback());
}

// Opens the file manager for the specified |url|. Used to implement
// internal handlers of special action IDs:
//
// "open" - Open the file manager for the given folder.
// "select" - Open the file manager for the given file. The folder containing
//            the file will be opened with the file selected.
void OpenFileManagerWithInternalActionId(Profile* profile,
                                         const GURL& url,
                                         const std::string& action_id) {
  DCHECK(action_id == "open" || action_id == "select");
  if (!shell_operations_allowed)
    return;
  content::RecordAction(base::UserMetricsAction("ShowFileBrowserFullTab"));

  file_tasks::TaskDescriptor task(kFileManagerAppId,
                                  file_tasks::TASK_TYPE_FILE_BROWSER_HANDLER,
                                  action_id);
  ExecuteFileTaskForUrl(profile, task, url);
}

// Opens the file with fetched MIME type and calls the callback.
void OpenFileWithMimeType(Profile* profile,
                          const base::FilePath& path,
                          const GURL& url,
                          const platform_util::OpenOperationCallback& callback,
                          const std::string& mime_type) {
  std::vector<extensions::EntryInfo> entries;
  entries.push_back(extensions::EntryInfo(path, mime_type, false));

  std::vector<GURL> file_urls;
  file_urls.push_back(url);

  std::vector<file_tasks::FullTaskDescriptor> tasks;
  file_tasks::FindAllTypesOfTasks(
      profile, drive::util::GetDriveAppRegistryByProfile(profile), entries,
      file_urls, &tasks);

  // Select a default handler. If a default handler is not available, select
  // a non-generic file handler.
  const file_tasks::FullTaskDescriptor* chosen_task = nullptr;
  for (const auto& task : tasks) {
    if (!task.is_generic_file_handler()) {
      chosen_task = &task;
      if (task.is_default())
        break;
    }
  }

  if (chosen_task != nullptr) {
    if (shell_operations_allowed)
      ExecuteFileTaskForUrl(profile, chosen_task->task_descriptor(), url);
    callback.Run(platform_util::OPEN_SUCCEEDED);
  } else {
    callback.Run(platform_util::OPEN_FAILED_NO_HANLDER_FOR_FILE_TYPE);
  }
}

// Opens the file specified by |url| by finding and executing a file task for
// the file. Calls |callback| with the result.
void OpenFile(Profile* profile,
              const base::FilePath& path,
              const GURL& url,
              const platform_util::OpenOperationCallback& callback) {
  extensions::app_file_handler_util::GetMimeTypeForLocalPath(
      profile, path,
      base::Bind(&OpenFileWithMimeType, profile, path, url, callback));
}

void OpenItemWithMetadata(Profile* profile,
                          const base::FilePath& file_path,
                          const GURL& url,
                          platform_util::OpenItemType expected_type,
                          const platform_util::OpenOperationCallback& callback,
                          base::File::Error error,
                          const base::File::Info& file_info) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (error != base::File::FILE_OK) {
    callback.Run(error == base::File::FILE_ERROR_NOT_FOUND
                     ? platform_util::OPEN_FAILED_PATH_NOT_FOUND
                     : platform_util::OPEN_FAILED_FILE_ERROR);
    return;
  }

  // Note that there exists a TOCTOU race between the time the metadata for
  // |file_path| was determined and when it is opened based on the metadata.
  if (expected_type == platform_util::OPEN_FOLDER && file_info.is_directory) {
    OpenFileManagerWithInternalActionId(profile, url, "open");
    callback.Run(platform_util::OPEN_SUCCEEDED);
    return;
  }

  if (expected_type == platform_util::OPEN_FILE && !file_info.is_directory) {
    OpenFile(profile, file_path, url, callback);
    return;
  }

  callback.Run(platform_util::OPEN_FAILED_INVALID_TYPE);
}

}  // namespace

void OpenItem(Profile* profile,
              const base::FilePath& file_path,
              platform_util::OpenItemType expected_type,
              const platform_util::OpenOperationCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // This is unfortunately necessary as file browser handlers operate on URLs.
  GURL url;
  if (!ConvertAbsoluteFilePathToFileSystemUrl(profile, file_path,
                                              kFileManagerAppId, &url)) {
    callback.Run(platform_util::OPEN_FAILED_PATH_NOT_FOUND);
    return;
  }

  GetMetadataForPath(
      GetFileSystemContextForExtensionId(profile, kFileManagerAppId), file_path,
      storage::FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY,
      base::Bind(&OpenItemWithMetadata, profile, file_path, url, expected_type,
                 callback));
}

void ShowItemInFolder(Profile* profile,
                      const base::FilePath& file_path,
                      const platform_util::OpenOperationCallback& callback) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  GURL url;
  if (!ConvertAbsoluteFilePathToFileSystemUrl(profile, file_path,
                                              kFileManagerAppId, &url)) {
    callback.Run(platform_util::OPEN_FAILED_PATH_NOT_FOUND);
    return;
  }

  // This action changes the selection so we do not reuse existing tabs.
  OpenFileManagerWithInternalActionId(profile, url, "select");
  callback.Run(platform_util::OPEN_SUCCEEDED);
}

void DisableShellOperationsForTesting() {
  shell_operations_allowed = false;
}

}  // namespace util
}  // namespace file_manager