// Copyright (c) 2011 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/extensions/extension_processes_api.h"

#include "base/callback.h"
#include "base/json/json_writer.h"
#include "base/message_loop.h"
#include "base/string_number_conversions.h"
#include "base/task.h"
#include "base/utf_string_conversions.h"
#include "base/values.h"

#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_processes_api_constants.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/extensions/extension_tabs_module_constants.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/task_manager/task_manager.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "content/browser/renderer_host/render_process_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_type.h"

namespace keys = extension_processes_api_constants;

DictionaryValue* CreateProcessValue(int process_id,
                                    const std::string& type,
                                    double cpu,
                                    int64 net,
                                    int64 pr_mem,
                                    int64 sh_mem) {
  DictionaryValue* result = new DictionaryValue();
  result->SetInteger(keys::kIdKey, process_id);
  result->SetString(keys::kTypeKey, type);
  result->SetDouble(keys::kCpuKey, cpu);
  result->SetDouble(keys::kNetworkKey, static_cast<double>(net));
  result->SetDouble(keys::kPrivateMemoryKey, static_cast<double>(pr_mem));
  result->SetDouble(keys::kSharedMemoryKey, static_cast<double>(sh_mem));
  return result;
}

ExtensionProcessesEventRouter* ExtensionProcessesEventRouter::GetInstance() {
  return Singleton<ExtensionProcessesEventRouter>::get();
}

ExtensionProcessesEventRouter::ExtensionProcessesEventRouter() {
  model_ = TaskManager::GetInstance()->model();
  model_->AddObserver(this);
}

ExtensionProcessesEventRouter::~ExtensionProcessesEventRouter() {
  model_->RemoveObserver(this);
}

void ExtensionProcessesEventRouter::ObserveProfile(Profile* profile) {
  profiles_.insert(profile);
}

void ExtensionProcessesEventRouter::ListenerAdded() {
  model_->StartUpdating();
}

void ExtensionProcessesEventRouter::ListenerRemoved() {
  model_->StopUpdating();
}

void ExtensionProcessesEventRouter::OnItemsChanged(int start, int length) {
  if (model_) {
    ListValue args;
    DictionaryValue* processes = new DictionaryValue();
    for (int i = start; i < start + length; i++) {
      if (model_->IsResourceFirstInGroup(i)) {
        int id = model_->GetProcessId(i);

        // Determine process type
        std::string type = keys::kProcessTypeOther;
        TaskManager::Resource::Type resource_type = model_->GetResourceType(i);
        switch (resource_type) {
          case TaskManager::Resource::BROWSER:
            type = keys::kProcessTypeBrowser;
            break;
          case TaskManager::Resource::RENDERER:
            type = keys::kProcessTypeRenderer;
            break;
          case TaskManager::Resource::EXTENSION:
            type = keys::kProcessTypeExtension;
            break;
          case TaskManager::Resource::NOTIFICATION:
            type = keys::kProcessTypeNotification;
            break;
          case TaskManager::Resource::PLUGIN:
            type = keys::kProcessTypePlugin;
            break;
          case TaskManager::Resource::WORKER:
            type = keys::kProcessTypeWorker;
            break;
          case TaskManager::Resource::NACL:
            type = keys::kProcessTypeNacl;
            break;
          case TaskManager::Resource::UTILITY:
            type = keys::kProcessTypeUtility;
            break;
          case TaskManager::Resource::GPU:
            type = keys::kProcessTypeGPU;
            break;
          case TaskManager::Resource::PROFILE_IMPORT:
          case TaskManager::Resource::ZYGOTE:
          case TaskManager::Resource::SANDBOX_HELPER:
          case TaskManager::Resource::UNKNOWN:
            type = keys::kProcessTypeOther;
            break;
          default:
            NOTREACHED() << "Unknown resource type.";
        }

        // Get process metrics as numbers
        double cpu = model_->GetCPUUsage(i);

        // TODO(creis): Network is actually reported per-resource (tab),
        // not per-process.  We should aggregate it here.
        int64 net = model_->GetNetworkUsage(i);
        size_t mem;
        int64 pr_mem = model_->GetPrivateMemory(i, &mem) ?
            static_cast<int64>(mem) : -1;
        int64 sh_mem = model_->GetSharedMemory(i, &mem) ?
            static_cast<int64>(mem) : -1;

        // Store each process indexed by the string version of its id
        processes->Set(base::IntToString(id),
                       CreateProcessValue(id, type, cpu, net, pr_mem, sh_mem));
      }
    }
    args.Append(processes);

    std::string json_args;
    base::JSONWriter::Write(&args, false, &json_args);

    // Notify each profile that is interested.
    for (ProfileSet::iterator it = profiles_.begin();
         it != profiles_.end(); it++) {
      Profile* profile = *it;
      DispatchEvent(profile, keys::kOnUpdated, json_args);
    }
  }
}

void ExtensionProcessesEventRouter::DispatchEvent(Profile* profile,
    const char* event_name,
    const std::string& json_args) {
  if (profile && profile->GetExtensionEventRouter()) {
    profile->GetExtensionEventRouter()->DispatchEventToRenderers(
        event_name, json_args, NULL, GURL());
  }
}

bool GetProcessIdForTabFunction::RunImpl() {
  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));

  TabContentsWrapper* contents = NULL;
  int tab_index = -1;
  if (!ExtensionTabUtil::GetTabById(tab_id, profile(), include_incognito(),
                                    NULL, NULL, &contents, &tab_index)) {
    error_ = ExtensionErrorUtils::FormatErrorMessage(
        extension_tabs_module_constants::kTabNotFoundError,
        base::IntToString(tab_id));
    return false;
  }

  // Return the process ID of the tab as an integer.
  int id = base::GetProcId(contents->tab_contents()->
      GetRenderProcessHost()->GetHandle());
  result_.reset(Value::CreateIntegerValue(id));
  return true;
}