// 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/ui/webui/chromeos/drive_internals_ui.h"

#include <stddef.h>
#include <stdint.h>

#include "base/bind.h"
#include "base/files/file_enumerator.h"
#include "base/files/file_util.h"
#include "base/format_macros.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/sys_info.h"
#include "chrome/browser/chromeos/drive/debug_info_collector.h"
#include "chrome/browser/chromeos/drive/drive_integration_service.h"
#include "chrome/browser/chromeos/drive/file_system_util.h"
#include "chrome/browser/chromeos/file_manager/path_util.h"
#include "chrome/browser/drive/drive_notification_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/drive/drive.pb.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/drive_notification_manager.h"
#include "components/drive/drive_pref_names.h"
#include "components/drive/event_logger.h"
#include "components/drive/job_list.h"
#include "components/drive/service/drive_service_interface.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
#include "content/public/browser/web_ui_message_handler.h"
#include "google_apis/drive/auth_service.h"
#include "google_apis/drive/drive_api_error_codes.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/drive/time_util.h"
#include "grit/browser_resources.h"

using content::BrowserThread;

namespace chromeos {

namespace {

// Gets metadata of all files and directories in |root_path|
// recursively. Stores the result as a list of dictionaries like:
//
// [{ path: 'GCache/v1/tmp/<local_id>',
//    size: 12345,
//    is_directory: false,
//    last_modified: '2005-08-09T09:57:00-08:00',
//  },...]
//
// The list is sorted by the path.
void GetGCacheContents(const base::FilePath& root_path,
                       base::ListValue* gcache_contents,
                       base::DictionaryValue* gcache_summary) {
  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(gcache_contents);
  DCHECK(gcache_summary);

  // Use this map to sort the result list by the path.
  std::map<base::FilePath, base::DictionaryValue*> files;

  const int options = (base::FileEnumerator::FILES |
                       base::FileEnumerator::DIRECTORIES |
                       base::FileEnumerator::SHOW_SYM_LINKS);
  base::FileEnumerator enumerator(root_path, true /* recursive */, options);

  int64_t total_size = 0;
  for (base::FilePath current = enumerator.Next(); !current.empty();
       current = enumerator.Next()) {
    base::FileEnumerator::FileInfo info = enumerator.GetInfo();
    int64_t size = info.GetSize();
    const bool is_directory = info.IsDirectory();
    const bool is_symbolic_link = base::IsLink(info.GetName());
    const base::Time last_modified = info.GetLastModifiedTime();

    base::DictionaryValue* entry = new base::DictionaryValue;
    entry->SetString("path", current.value());
    // Use double instead of integer for large files.
    entry->SetDouble("size", size);
    entry->SetBoolean("is_directory", is_directory);
    entry->SetBoolean("is_symbolic_link", is_symbolic_link);
    entry->SetString(
        "last_modified",
        google_apis::util::FormatTimeAsStringLocaltime(last_modified));
    // Print lower 9 bits in octal format.
    entry->SetString(
        "permission",
        base::StringPrintf("%03o", info.stat().st_mode & 0x1ff));
    files[current] = entry;

    total_size += size;
  }

  // Convert |files| into |gcache_contents|.
  for (std::map<base::FilePath, base::DictionaryValue*>::const_iterator
           iter = files.begin(); iter != files.end(); ++iter) {
    gcache_contents->Append(iter->second);
  }

  gcache_summary->SetDouble("total_size", total_size);
}

// Gets the available disk space for the path |home_path|.
void GetFreeDiskSpace(const base::FilePath& home_path,
                      base::DictionaryValue* local_storage_summary) {
  DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::UI));
  DCHECK(local_storage_summary);

  const int64_t free_space = base::SysInfo::AmountOfFreeDiskSpace(home_path);
  local_storage_summary->SetDouble("free_space", free_space);
}

// Formats |entry| into text.
std::string FormatEntry(const base::FilePath& path,
                        const drive::ResourceEntry& entry) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  using base::StringAppendF;

  std::string out;
  StringAppendF(&out, "%s\n", path.AsUTF8Unsafe().c_str());
  StringAppendF(&out, "  title: %s\n", entry.title().c_str());
  StringAppendF(&out, "  local_id: %s\n", entry.local_id().c_str());
  StringAppendF(&out, "  resource_id: %s\n", entry.resource_id().c_str());
  StringAppendF(&out, "  parent_local_id: %s\n",
                entry.parent_local_id().c_str());
  StringAppendF(&out, "  shared: %s\n", entry.shared() ? "true" : "false");
  StringAppendF(&out, "  shared_with_me: %s\n",
                entry.shared_with_me() ? "true" : "false");

  const drive::PlatformFileInfoProto& file_info = entry.file_info();
  StringAppendF(&out, "  file_info\n");
  StringAppendF(&out, "    size: %" PRId64 "\n", file_info.size());
  StringAppendF(&out, "    is_directory: %d\n", file_info.is_directory());
  StringAppendF(&out, "    is_symbolic_link: %d\n",
                file_info.is_symbolic_link());

  const base::Time last_modified = base::Time::FromInternalValue(
      file_info.last_modified());
  const base::Time last_accessed = base::Time::FromInternalValue(
      file_info.last_accessed());
  const base::Time creation_time = base::Time::FromInternalValue(
      file_info.creation_time());
  StringAppendF(&out, "    last_modified: %s\n",
                google_apis::util::FormatTimeAsString(last_modified).c_str());
  StringAppendF(&out, "    last_accessed: %s\n",
                google_apis::util::FormatTimeAsString(last_accessed).c_str());
  StringAppendF(&out, "    creation_time: %s\n",
                google_apis::util::FormatTimeAsString(creation_time).c_str());

  if (entry.has_file_specific_info()) {
    const drive::FileSpecificInfo& file_specific_info =
        entry.file_specific_info();
    StringAppendF(&out, "    alternate_url: %s\n",
                  file_specific_info.alternate_url().c_str());
    StringAppendF(&out, "    content_mime_type: %s\n",
                  file_specific_info.content_mime_type().c_str());
    StringAppendF(&out, "    file_md5: %s\n",
                  file_specific_info.md5().c_str());
    StringAppendF(&out, "    document_extension: %s\n",
                  file_specific_info.document_extension().c_str());
    StringAppendF(&out, "    is_hosted_document: %d\n",
                  file_specific_info.is_hosted_document());
  }

  if (entry.has_directory_specific_info()) {
    StringAppendF(&out, "  directory_info\n");
    const drive::DirectorySpecificInfo& directory_specific_info =
        entry.directory_specific_info();
    StringAppendF(&out, "    changestamp: %" PRId64 "\n",
                  directory_specific_info.changestamp());
  }

  return out;
}

std::string SeverityToString(logging::LogSeverity severity) {
  switch (severity) {
    case logging::LOG_INFO:
      return "info";
    case logging::LOG_WARNING:
      return "warning";
    case logging::LOG_ERROR:
      return "error";
    default:  // Treat all other higher severities as ERROR.
      return "error";
  }
}

// Appends {'key': key, 'value': value} dictionary to the |list|.
void AppendKeyValue(base::ListValue* list,
                    const std::string& key,
                    const std::string& value) {
  base::DictionaryValue* dict = new base::DictionaryValue;
  dict->SetString("key", key);
  dict->SetString("value", value);
  list->Append(dict);
}

// Class to handle messages from chrome://drive-internals.
class DriveInternalsWebUIHandler : public content::WebUIMessageHandler {
 public:
  DriveInternalsWebUIHandler()
      : last_sent_event_id_(-1),
        weak_ptr_factory_(this) {
  }

  ~DriveInternalsWebUIHandler() override {}

 private:
  // WebUIMessageHandler override.
  void RegisterMessages() override;

  // Returns a DriveIntegrationService.
  drive::DriveIntegrationService* GetIntegrationService();

  // Returns a DriveService instance.
  drive::DriveServiceInterface* GetDriveService();

  // Returns a DebugInfoCollector instance.
  drive::DebugInfoCollector* GetDebugInfoCollector();

  // Called when the page is first loaded.
  void OnPageLoaded(const base::ListValue* args);

  // Updates respective sections.
  void UpdateDriveRelatedPreferencesSection();
  void UpdateConnectionStatusSection(
      drive::DriveServiceInterface* drive_service);
  void UpdateAboutResourceSection(
      drive::DriveServiceInterface* drive_service);
  void UpdateAppListSection(
      drive::DriveServiceInterface* drive_service);
  void UpdateLocalMetadataSection(
      drive::DebugInfoCollector* debug_info_collector);
  void UpdateDeltaUpdateStatusSection(
      drive::DebugInfoCollector* debug_info_collector);
  void UpdateInFlightOperationsSection(drive::JobListInterface* job_list);
  void UpdateGCacheContentsSection();
  void UpdateFileSystemContentsSection();
  void UpdateLocalStorageUsageSection();
  void UpdateCacheContentsSection(
      drive::DebugInfoCollector* debug_info_collector);
  void UpdateEventLogSection();
  void UpdatePathConfigurationsSection();

  // Called when GetGCacheContents() is complete.
  void OnGetGCacheContents(base::ListValue* gcache_contents,
                           base::DictionaryValue* cache_summary);

  // Called when GetResourceEntryByPath() is complete.
  void OnGetResourceEntryByPath(const base::FilePath& path,
                                drive::FileError error,
                                scoped_ptr<drive::ResourceEntry> entry);

  // Called when ReadDirectoryByPath() is complete.
  void OnReadDirectoryByPath(const base::FilePath& parent_path,
                             drive::FileError error,
                             scoped_ptr<drive::ResourceEntryVector> entries);

  // Called as the iterator for DebugInfoCollector::IterateFileCache().
  void UpdateCacheEntry(const std::string& local_id,
                        const drive::FileCacheEntry& cache_entry);

  // Called when GetFreeDiskSpace() is complete.
  void OnGetFreeDiskSpace(base::DictionaryValue* local_storage_summary);

  // Called when GetAboutResource() call to DriveService is complete.
  void OnGetAboutResource(
      google_apis::DriveApiErrorCode status,
      scoped_ptr<google_apis::AboutResource> about_resource);

  // Called when GetAppList() call to DriveService is complete.
  void OnGetAppList(
      google_apis::DriveApiErrorCode status,
      scoped_ptr<google_apis::AppList> app_list);

  // Callback for DebugInfoCollector::GetMetadata for local update.
  void OnGetFilesystemMetadataForLocal(
      const drive::FileSystemMetadata& metadata);

  // Callback for DebugInfoCollector::GetMetadata for delta update.
  void OnGetFilesystemMetadataForDeltaUpdate(
      const drive::FileSystemMetadata& metadata);

  // Called when the page requests periodic update.
  void OnPeriodicUpdate(const base::ListValue* args);

  // Called when the corresponding button on the page is pressed.
  void ClearAccessToken(const base::ListValue* args);
  void ClearRefreshToken(const base::ListValue* args);
  void ResetDriveFileSystem(const base::ListValue* args);
  void ListFileEntries(const base::ListValue* args);

  // Called after file system reset for ResetDriveFileSystem is done.
  void ResetFinished(bool success);

  // The last event sent to the JavaScript side.
  int last_sent_event_id_;

  base::WeakPtrFactory<DriveInternalsWebUIHandler> weak_ptr_factory_;
  DISALLOW_COPY_AND_ASSIGN(DriveInternalsWebUIHandler);
};

void DriveInternalsWebUIHandler::OnGetAboutResource(
    google_apis::DriveApiErrorCode status,
    scoped_ptr<google_apis::AboutResource> parsed_about_resource) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (status != google_apis::HTTP_SUCCESS) {
    LOG(ERROR) << "Failed to get about resource";
    return;
  }
  DCHECK(parsed_about_resource);

  base::DictionaryValue about_resource;
  about_resource.SetDouble("account-quota-total",
                           parsed_about_resource->quota_bytes_total());
  about_resource.SetDouble("account-quota-used",
                           parsed_about_resource->quota_bytes_used_aggregate());
  about_resource.SetDouble("account-largest-changestamp-remote",
                           parsed_about_resource->largest_change_id());
  about_resource.SetString("root-resource-id",
                           parsed_about_resource->root_folder_id());

  web_ui()->CallJavascriptFunction("updateAboutResource", about_resource);
}

void DriveInternalsWebUIHandler::OnGetAppList(
    google_apis::DriveApiErrorCode status,
    scoped_ptr<google_apis::AppList> parsed_app_list) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (status != google_apis::HTTP_SUCCESS) {
    LOG(ERROR) << "Failed to get app list";
    return;
  }
  DCHECK(parsed_app_list);

  base::DictionaryValue app_list;
  app_list.SetString("etag", parsed_app_list->etag());

  base::ListValue* items = new base::ListValue();
  for (size_t i = 0; i < parsed_app_list->items().size(); ++i) {
    const google_apis::AppResource* app = parsed_app_list->items()[i];
    base::DictionaryValue* app_data = new base::DictionaryValue();
    app_data->SetString("name", app->name());
    app_data->SetString("application_id", app->application_id());
    app_data->SetString("object_type", app->object_type());
    app_data->SetBoolean("supports_create", app->supports_create());

    items->Append(app_data);
  }
  app_list.Set("items", items);

  web_ui()->CallJavascriptFunction("updateAppList", app_list);
}

void DriveInternalsWebUIHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "pageLoaded",
      base::Bind(&DriveInternalsWebUIHandler::OnPageLoaded,
                 weak_ptr_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "periodicUpdate",
      base::Bind(&DriveInternalsWebUIHandler::OnPeriodicUpdate,
                 weak_ptr_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "clearAccessToken",
      base::Bind(&DriveInternalsWebUIHandler::ClearAccessToken,
                 weak_ptr_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "clearRefreshToken",
      base::Bind(&DriveInternalsWebUIHandler::ClearRefreshToken,
                 weak_ptr_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "resetDriveFileSystem",
      base::Bind(&DriveInternalsWebUIHandler::ResetDriveFileSystem,
                 weak_ptr_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "listFileEntries",
      base::Bind(&DriveInternalsWebUIHandler::ListFileEntries,
                 weak_ptr_factory_.GetWeakPtr()));
}

drive::DriveIntegrationService*
DriveInternalsWebUIHandler::GetIntegrationService() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  Profile* profile = Profile::FromWebUI(web_ui());
  drive::DriveIntegrationService* service =
      drive::DriveIntegrationServiceFactory::FindForProfile(profile);
  if (!service || !service->is_enabled())
    return NULL;
  return service;
}

drive::DriveServiceInterface* DriveInternalsWebUIHandler::GetDriveService() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  Profile* profile = Profile::FromWebUI(web_ui());
  return drive::util::GetDriveServiceByProfile(profile);
}

drive::DebugInfoCollector* DriveInternalsWebUIHandler::GetDebugInfoCollector() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveIntegrationService* integration_service = GetIntegrationService();
  return integration_service ?
      integration_service->debug_info_collector() : NULL;
}

void DriveInternalsWebUIHandler::OnPageLoaded(const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveIntegrationService* integration_service =
      GetIntegrationService();
  // |integration_service| may be NULL in the guest/incognito mode.
  if (!integration_service)
    return;

  drive::DriveServiceInterface* drive_service =
      integration_service->drive_service();
  DCHECK(drive_service);
  drive::DebugInfoCollector* debug_info_collector =
      integration_service->debug_info_collector();
  DCHECK(debug_info_collector);

  UpdateDriveRelatedPreferencesSection();
  UpdateConnectionStatusSection(drive_service);
  UpdateAboutResourceSection(drive_service);
  UpdateAppListSection(drive_service);
  UpdateLocalMetadataSection(debug_info_collector);
  UpdateDeltaUpdateStatusSection(debug_info_collector);
  UpdateInFlightOperationsSection(integration_service->job_list());
  UpdateGCacheContentsSection();
  UpdateCacheContentsSection(debug_info_collector);
  UpdateLocalStorageUsageSection();
  UpdatePathConfigurationsSection();

  // When the drive-internals page is reloaded by the reload key, the page
  // content is recreated, but this WebUI object is not (instead, OnPageLoaded
  // is called again). In that case, we have to forget the last sent ID here,
  // and resent whole the logs to the page.
  last_sent_event_id_ = -1;
  UpdateEventLogSection();
}

void DriveInternalsWebUIHandler::UpdateDriveRelatedPreferencesSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  const char* kDriveRelatedPreferences[] = {
      drive::prefs::kDisableDrive,
      drive::prefs::kDisableDriveOverCellular,
      drive::prefs::kDisableDriveHostedFiles,
  };

  Profile* profile = Profile::FromWebUI(web_ui());
  PrefService* pref_service = profile->GetPrefs();

  base::ListValue preferences;
  for (size_t i = 0; i < arraysize(kDriveRelatedPreferences); ++i) {
    const std::string key = kDriveRelatedPreferences[i];
    // As of now, all preferences are boolean.
    const std::string value =
        (pref_service->GetBoolean(key.c_str()) ? "true" : "false");
    AppendKeyValue(&preferences, key, value);
  }

  web_ui()->CallJavascriptFunction("updateDriveRelatedPreferences",
                                   preferences);
}

void DriveInternalsWebUIHandler::UpdateConnectionStatusSection(
    drive::DriveServiceInterface* drive_service) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(drive_service);

  std::string status;
  switch (drive::util::GetDriveConnectionStatus(Profile::FromWebUI(web_ui()))) {
    case drive::util::DRIVE_DISCONNECTED_NOSERVICE:
      status = "no service";
      break;
    case drive::util::DRIVE_DISCONNECTED_NONETWORK:
      status = "no network";
      break;
    case drive::util::DRIVE_DISCONNECTED_NOTREADY:
      status = "not ready";
      break;
    case drive::util::DRIVE_CONNECTED_METERED:
      status = "metered";
      break;
    case drive::util::DRIVE_CONNECTED:
      status = "connected";
      break;
  }

  base::DictionaryValue connection_status;
  connection_status.SetString("status", status);
  connection_status.SetBoolean("has-refresh-token",
                               drive_service->HasRefreshToken());
  connection_status.SetBoolean("has-access-token",
                               drive_service->HasAccessToken());
  web_ui()->CallJavascriptFunction("updateConnectionStatus", connection_status);
}

void DriveInternalsWebUIHandler::UpdateAboutResourceSection(
    drive::DriveServiceInterface* drive_service) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(drive_service);

  drive_service->GetAboutResource(
      base::Bind(&DriveInternalsWebUIHandler::OnGetAboutResource,
                 weak_ptr_factory_.GetWeakPtr()));
}

void DriveInternalsWebUIHandler::UpdateAppListSection(
    drive::DriveServiceInterface* drive_service) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(drive_service);

  drive_service->GetAppList(
      base::Bind(&DriveInternalsWebUIHandler::OnGetAppList,
                 weak_ptr_factory_.GetWeakPtr()));
}

void DriveInternalsWebUIHandler::UpdateLocalMetadataSection(
    drive::DebugInfoCollector* debug_info_collector) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(debug_info_collector);

  debug_info_collector->GetMetadata(
      base::Bind(&DriveInternalsWebUIHandler::OnGetFilesystemMetadataForLocal,
                 weak_ptr_factory_.GetWeakPtr()));
}

void DriveInternalsWebUIHandler::OnGetFilesystemMetadataForLocal(
    const drive::FileSystemMetadata& metadata) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  base::DictionaryValue local_metadata;
  local_metadata.SetDouble("account-largest-changestamp-local",
                           metadata.largest_changestamp);
  local_metadata.SetBoolean("account-metadata-refreshing", metadata.refreshing);
  web_ui()->CallJavascriptFunction("updateLocalMetadata", local_metadata);
}

void DriveInternalsWebUIHandler::ClearAccessToken(const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveServiceInterface* drive_service = GetDriveService();
  if (drive_service)
    drive_service->ClearAccessToken();
}

void DriveInternalsWebUIHandler::ClearRefreshToken(
    const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveServiceInterface* drive_service = GetDriveService();
  if (drive_service)
    drive_service->ClearRefreshToken();
}

void DriveInternalsWebUIHandler::ResetDriveFileSystem(
    const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveIntegrationService* integration_service =
      GetIntegrationService();
  if (integration_service) {
    integration_service->ClearCacheAndRemountFileSystem(
        base::Bind(&DriveInternalsWebUIHandler::ResetFinished,
                   weak_ptr_factory_.GetWeakPtr()));
  }
}

void DriveInternalsWebUIHandler::ResetFinished(bool success) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  web_ui()->CallJavascriptFunction("updateResetStatus",
                                   base::FundamentalValue(success));
}

void DriveInternalsWebUIHandler::ListFileEntries(const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  UpdateFileSystemContentsSection();
}

void DriveInternalsWebUIHandler::UpdateDeltaUpdateStatusSection(
    drive::DebugInfoCollector* debug_info_collector) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(debug_info_collector);

  debug_info_collector->GetMetadata(
      base::Bind(
          &DriveInternalsWebUIHandler::OnGetFilesystemMetadataForDeltaUpdate,
          weak_ptr_factory_.GetWeakPtr()));
}

void DriveInternalsWebUIHandler::OnGetFilesystemMetadataForDeltaUpdate(
    const drive::FileSystemMetadata& metadata) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  Profile* profile = Profile::FromWebUI(web_ui());
  drive::DriveNotificationManager* drive_notification_manager =
      drive::DriveNotificationManagerFactory::FindForBrowserContext(profile);
  if (!drive_notification_manager)
    return;

  base::DictionaryValue delta_update_status;
  delta_update_status.SetBoolean(
      "push-notification-enabled",
      drive_notification_manager->push_notification_enabled());
  delta_update_status.SetString(
      "last-update-check-time",
      google_apis::util::FormatTimeAsStringLocaltime(
          metadata.last_update_check_time));
  delta_update_status.SetString(
      "last-update-check-error",
      drive::FileErrorToString(metadata.last_update_check_error));

  web_ui()->CallJavascriptFunction("updateDeltaUpdateStatus",
                                   delta_update_status);
}

void DriveInternalsWebUIHandler::UpdateInFlightOperationsSection(
    drive::JobListInterface* job_list) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(job_list);

  std::vector<drive::JobInfo> info_list = job_list->GetJobInfoList();

  base::ListValue in_flight_operations;
  for (size_t i = 0; i < info_list.size(); ++i) {
    const drive::JobInfo& info = info_list[i];

    base::DictionaryValue* dict = new base::DictionaryValue;
    dict->SetInteger("id", info.job_id);
    dict->SetString("type", drive::JobTypeToString(info.job_type));
    dict->SetString("file_path", info.file_path.AsUTF8Unsafe());
    dict->SetString("state", drive::JobStateToString(info.state));
    dict->SetDouble("progress_current", info.num_completed_bytes);
    dict->SetDouble("progress_total", info.num_total_bytes);
    in_flight_operations.Append(dict);
  }
  web_ui()->CallJavascriptFunction("updateInFlightOperations",
                                   in_flight_operations);
}

void DriveInternalsWebUIHandler::UpdateGCacheContentsSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Start updating the GCache contents section.
  Profile* profile = Profile::FromWebUI(web_ui());
  const base::FilePath root_path = drive::util::GetCacheRootPath(profile);
  base::ListValue* gcache_contents = new base::ListValue;
  base::DictionaryValue* gcache_summary = new base::DictionaryValue;
  BrowserThread::PostBlockingPoolTaskAndReply(
      FROM_HERE,
      base::Bind(&GetGCacheContents,
                 root_path,
                 gcache_contents,
                 gcache_summary),
      base::Bind(&DriveInternalsWebUIHandler::OnGetGCacheContents,
                 weak_ptr_factory_.GetWeakPtr(),
                 base::Owned(gcache_contents),
                 base::Owned(gcache_summary)));
}

void DriveInternalsWebUIHandler::UpdateFileSystemContentsSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DebugInfoCollector* debug_info_collector = GetDebugInfoCollector();
  if (!debug_info_collector)
    return;

  // Start rendering the file system tree as text.
  const base::FilePath root_path = drive::util::GetDriveGrandRootPath();

  debug_info_collector->GetResourceEntry(
      root_path,
      base::Bind(&DriveInternalsWebUIHandler::OnGetResourceEntryByPath,
                 weak_ptr_factory_.GetWeakPtr(),
                 root_path));

  debug_info_collector->ReadDirectory(
      root_path,
      base::Bind(&DriveInternalsWebUIHandler::OnReadDirectoryByPath,
                 weak_ptr_factory_.GetWeakPtr(),
                 root_path));
}

void DriveInternalsWebUIHandler::UpdateLocalStorageUsageSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Propagate the amount of local free space in bytes.
  base::FilePath home_path;
  if (PathService::Get(base::DIR_HOME, &home_path)) {
    base::DictionaryValue* local_storage_summary = new base::DictionaryValue;
    BrowserThread::PostBlockingPoolTaskAndReply(
        FROM_HERE,
        base::Bind(&GetFreeDiskSpace, home_path, local_storage_summary),
        base::Bind(&DriveInternalsWebUIHandler::OnGetFreeDiskSpace,
                   weak_ptr_factory_.GetWeakPtr(),
                   base::Owned(local_storage_summary)));
  } else {
    LOG(ERROR) << "Home directory not found";
  }
}

void DriveInternalsWebUIHandler::UpdateCacheContentsSection(
    drive::DebugInfoCollector* debug_info_collector) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(debug_info_collector);

  debug_info_collector->IterateFileCache(
      base::Bind(&DriveInternalsWebUIHandler::UpdateCacheEntry,
                 weak_ptr_factory_.GetWeakPtr()),
      base::Bind(&base::DoNothing));
}

void DriveInternalsWebUIHandler::UpdateEventLogSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveIntegrationService* integration_service =
      GetIntegrationService();
  if (!integration_service)
    return;

  const std::vector<drive::EventLogger::Event> log =
      integration_service->event_logger()->GetHistory();

  base::ListValue list;
  for (size_t i = 0; i < log.size(); ++i) {
    // Skip events which were already sent.
    if (log[i].id <= last_sent_event_id_)
      continue;

    std::string severity = SeverityToString(log[i].severity);

    base::DictionaryValue* dict = new base::DictionaryValue;
    dict->SetString("key",
        google_apis::util::FormatTimeAsStringLocaltime(log[i].when));
    dict->SetString("value", "[" + severity + "] " + log[i].what);
    dict->SetString("class", "log-" + severity);
    list.Append(dict);
    last_sent_event_id_ = log[i].id;
  }
  if (!list.empty())
    web_ui()->CallJavascriptFunction("updateEventLog", list);
}

void DriveInternalsWebUIHandler::UpdatePathConfigurationsSection() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  Profile* const profile = Profile::FromWebUI(web_ui());

  base::ListValue paths;

  AppendKeyValue(
      &paths, "Downloads",
      file_manager::util::GetDownloadsFolderForProfile(profile).AsUTF8Unsafe());
  AppendKeyValue(
      &paths, "Drive",
      drive::util::GetDriveMountPointPath(profile).AsUTF8Unsafe());

  const char* kPathPreferences[] = {
    prefs::kSelectFileLastDirectory,
    prefs::kSaveFileDefaultDirectory,
    prefs::kDownloadDefaultDirectory,
  };
  for (size_t i = 0; i < arraysize(kPathPreferences); ++i) {
    const char* const key = kPathPreferences[i];
    AppendKeyValue(&paths, key,
                   profile->GetPrefs()->GetFilePath(key).AsUTF8Unsafe());
  }

  web_ui()->CallJavascriptFunction("updatePathConfigurations", paths);
}

void DriveInternalsWebUIHandler::OnGetGCacheContents(
    base::ListValue* gcache_contents,
    base::DictionaryValue* gcache_summary) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(gcache_contents);
  DCHECK(gcache_summary);

  web_ui()->CallJavascriptFunction("updateGCacheContents",
                                   *gcache_contents,
                                   *gcache_summary);
}

void DriveInternalsWebUIHandler::OnGetResourceEntryByPath(
    const base::FilePath& path,
    drive::FileError error,
    scoped_ptr<drive::ResourceEntry> entry) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (error == drive::FILE_ERROR_OK) {
    DCHECK(entry.get());
    const base::StringValue value(FormatEntry(path, *entry) + "\n");
    web_ui()->CallJavascriptFunction("updateFileSystemContents", value);
  }
}

void DriveInternalsWebUIHandler::OnReadDirectoryByPath(
    const base::FilePath& parent_path,
    drive::FileError error,
    scoped_ptr<drive::ResourceEntryVector> entries) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  if (error == drive::FILE_ERROR_OK) {
    DCHECK(entries.get());

    drive::DebugInfoCollector* debug_info_collector = GetDebugInfoCollector();
    std::string file_system_as_text;
    for (size_t i = 0; i < entries->size(); ++i) {
      const drive::ResourceEntry& entry = (*entries)[i];
      const base::FilePath current_path = parent_path.Append(
          base::FilePath::FromUTF8Unsafe(entry.base_name()));

      file_system_as_text.append(FormatEntry(current_path, entry) + "\n");

      if (entry.file_info().is_directory()) {
        debug_info_collector->ReadDirectory(
            current_path,
            base::Bind(&DriveInternalsWebUIHandler::OnReadDirectoryByPath,
                       weak_ptr_factory_.GetWeakPtr(),
                       current_path));
      }
    }

    // There may be pending ReadDirectoryByPath() calls, but we can update
    // the page with what we have now. This results in progressive
    // updates, which is good for a large file system.
    const base::StringValue value(file_system_as_text);
    web_ui()->CallJavascriptFunction("updateFileSystemContents", value);
  }
}

void DriveInternalsWebUIHandler::UpdateCacheEntry(
    const std::string& local_id,
    const drive::FileCacheEntry& cache_entry) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  // Convert |cache_entry| into a dictionary.
  base::DictionaryValue value;
  value.SetString("local_id", local_id);
  value.SetString("md5", cache_entry.md5());
  value.SetBoolean("is_present", cache_entry.is_present());
  value.SetBoolean("is_pinned", cache_entry.is_pinned());
  value.SetBoolean("is_dirty", cache_entry.is_dirty());

  web_ui()->CallJavascriptFunction("updateCacheContents", value);
}

void DriveInternalsWebUIHandler::OnGetFreeDiskSpace(
    base::DictionaryValue* local_storage_summary) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  DCHECK(local_storage_summary);

  web_ui()->CallJavascriptFunction(
      "updateLocalStorageUsage", *local_storage_summary);
}

void DriveInternalsWebUIHandler::OnPeriodicUpdate(const base::ListValue* args) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);

  drive::DriveIntegrationService* integration_service =
      GetIntegrationService();
  // |integration_service| may be NULL in the guest/incognito mode.
  if (!integration_service)
    return;

  UpdateInFlightOperationsSection(integration_service->job_list());
  UpdateEventLogSection();
}

}  // namespace

DriveInternalsUI::DriveInternalsUI(content::WebUI* web_ui)
    : WebUIController(web_ui) {
  web_ui->AddMessageHandler(new DriveInternalsWebUIHandler());

  content::WebUIDataSource* source =
      content::WebUIDataSource::Create(chrome::kChromeUIDriveInternalsHost);
  source->AddResourcePath("drive_internals.css", IDR_DRIVE_INTERNALS_CSS);
  source->AddResourcePath("drive_internals.js", IDR_DRIVE_INTERNALS_JS);
  source->SetDefaultResource(IDR_DRIVE_INTERNALS_HTML);

  Profile* profile = Profile::FromWebUI(web_ui);
  content::WebUIDataSource::Add(profile, source);
}

}  // namespace chromeos