// 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_page_actions_module.h"

#include <string>

#include "base/string_number_conversions.h"
#include "chrome/browser/extensions/extension_page_actions_module_constants.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_helper.h"
#include "chrome/browser/extensions/extension_tabs_module.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_error_utils.h"
#include "chrome/common/render_messages.h"
#include "content/browser/tab_contents/navigation_entry.h"
#include "content/browser/tab_contents/tab_contents.h"

namespace keys = extension_page_actions_module_constants;

namespace {
// Errors.
const char kNoTabError[] = "No tab with id: *.";
const char kNoPageActionError[] =
    "This extension has no page action specified.";
const char kUrlNotActiveError[] = "This url is no longer active: *.";
const char kIconIndexOutOfBounds[] = "Page action icon index out of bounds.";
const char kNoIconSpecified[] = "Page action has no icons to show.";
}

// TODO(EXTENSIONS_DEPRECATED): obsolete API.
bool PageActionFunction::SetPageActionEnabled(bool enable) {
  std::string page_action_id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &page_action_id));
  DictionaryValue* action;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &action));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(action->GetInteger(keys::kTabIdKey, &tab_id));
  std::string url;
  EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kUrlKey, &url));

  std::string title;
  int icon_id = 0;
  if (enable) {
    // Both of those are optional.
    if (action->HasKey(keys::kTitleKey))
      EXTENSION_FUNCTION_VALIDATE(action->GetString(keys::kTitleKey, &title));
    if (action->HasKey(keys::kIconIdKey)) {
      EXTENSION_FUNCTION_VALIDATE(action->GetInteger(keys::kIconIdKey,
                                                     &icon_id));
    }
  }

  ExtensionAction* page_action = GetExtension()->page_action();
  if (!page_action) {
    error_ = kNoPageActionError;
    return false;
  }

  if (icon_id < 0 ||
      static_cast<size_t>(icon_id) >= page_action->icon_paths()->size()) {
    error_ = (icon_id == 0) ? kNoIconSpecified : kIconIndexOutOfBounds;
    return false;
  }

  // Find the TabContents that contains this tab id.
  TabContentsWrapper* contents = NULL;
  bool result = ExtensionTabUtil::GetTabById(
      tab_id, profile(), include_incognito(), NULL, NULL, &contents, NULL);
  if (!result || !contents) {
    error_ = ExtensionErrorUtils::FormatErrorMessage(
        kNoTabError, base::IntToString(tab_id));
    return false;
  }

  // Make sure the URL hasn't changed.
  NavigationEntry* entry = contents->controller().GetActiveEntry();
  if (!entry || url != entry->url().spec()) {
    error_ = ExtensionErrorUtils::FormatErrorMessage(kUrlNotActiveError, url);
    return false;
  }

  // Set visibility and broadcast notifications that the UI should be updated.
  page_action->SetIsVisible(tab_id, enable);
  page_action->SetTitle(tab_id, title);
  page_action->SetIconIndex(tab_id, icon_id);
  contents->extension_tab_helper()->PageActionStateChanged();

  return true;
}

bool PageActionFunction::InitCommon(int tab_id) {
  page_action_ = GetExtension()->page_action();
  if (!page_action_) {
    error_ = kNoPageActionError;
    return false;
  }

  // Find the TabContents that contains this tab id.
  contents_ = NULL;
  TabContentsWrapper* wrapper = NULL;
  bool result = ExtensionTabUtil::GetTabById(
      tab_id, profile(), include_incognito(), NULL, NULL, &wrapper, NULL);
  if (!result || !wrapper) {
    error_ = ExtensionErrorUtils::FormatErrorMessage(
        kNoTabError, base::IntToString(tab_id));
    return false;
  }
  contents_ = wrapper;

  return true;
}

bool PageActionFunction::SetVisible(bool visible) {
  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &tab_id));
  if (!InitCommon(tab_id))
    return false;

  page_action_->SetIsVisible(tab_id, visible);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

bool EnablePageActionFunction::RunImpl() {
  return SetPageActionEnabled(true);
}

bool DisablePageActionFunction::RunImpl() {
  return SetPageActionEnabled(false);
}

bool PageActionShowFunction::RunImpl() {
  return SetVisible(true);
}

bool PageActionHideFunction::RunImpl() {
  return SetVisible(false);
}

bool PageActionSetIconFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  // setIcon can take a variant argument: either a canvas ImageData, or an
  // icon index.
  BinaryValue* binary;
  int icon_index;
  if (args->GetBinary("imageData", &binary)) {
    IPC::Message bitmap_pickle(binary->GetBuffer(), binary->GetSize());
    void* iter = NULL;
    scoped_ptr<SkBitmap> bitmap(new SkBitmap);
    EXTENSION_FUNCTION_VALIDATE(
        IPC::ReadParam(&bitmap_pickle, &iter, bitmap.get()));
    page_action_->SetIcon(tab_id, *bitmap);
  } else if (args->GetInteger("iconIndex", &icon_index)) {
    if (icon_index < 0 || static_cast<size_t>(icon_index) >=
                              page_action_->icon_paths()->size()) {
      error_ = kIconIndexOutOfBounds;
      return false;
    }
    page_action_->SetIcon(tab_id, SkBitmap());
    page_action_->SetIconIndex(tab_id, icon_index);
  } else {
    EXTENSION_FUNCTION_VALIDATE(false);
  }

  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

bool PageActionSetTitleFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  std::string title;
  EXTENSION_FUNCTION_VALIDATE(args->GetString("title", &title));

  page_action_->SetTitle(tab_id, title);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

bool PageActionSetPopupFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  // TODO(skerner): Consider allowing null and undefined to mean the popup
  // should be removed.
  std::string popup_string;
  EXTENSION_FUNCTION_VALIDATE(args->GetString("popup", &popup_string));

  GURL popup_url;
  if (!popup_string.empty())
    popup_url = GetExtension()->GetResourceURL(popup_string);

  page_action_->SetPopupUrl(tab_id, popup_url);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

// Not currently exposed to extensions. To re-enable, add mapping in
// extension_function_dispatcher.
bool PageActionSetBadgeBackgroundColorFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  ListValue* color_value;
  EXTENSION_FUNCTION_VALIDATE(args->GetList("color", &color_value));
  EXTENSION_FUNCTION_VALIDATE(color_value->GetSize() == 4);

  int color_array[4] = {0};
  for (size_t i = 0; i < arraysize(color_array); ++i)
    EXTENSION_FUNCTION_VALIDATE(color_value->GetInteger(i, &color_array[i]));

  SkColor color = SkColorSetARGB(color_array[3], color_array[0], color_array[1],
                                 color_array[2]);
  page_action_->SetBadgeBackgroundColor(tab_id, color);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

// Not currently exposed to extensions. To re-enable, add mapping in
// extension_function_dispatcher.
bool PageActionSetBadgeTextColorFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  ListValue* color_value;
  EXTENSION_FUNCTION_VALIDATE(args->GetList("color", &color_value));
  EXTENSION_FUNCTION_VALIDATE(color_value->GetSize() == 4);

  int color_array[4] = {0};
  for (size_t i = 0; i < arraysize(color_array); ++i)
    EXTENSION_FUNCTION_VALIDATE(color_value->GetInteger(i, &color_array[i]));

  SkColor color = SkColorSetARGB(color_array[3], color_array[0], color_array[1],
                                 color_array[2]);
  page_action_->SetBadgeTextColor(tab_id, color);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}

// Not currently exposed to extensions. To re-enable, add mapping in
// extension_function_dispatcher.
bool PageActionSetBadgeTextFunction::RunImpl() {
  DictionaryValue* args;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &args));

  int tab_id;
  EXTENSION_FUNCTION_VALIDATE(args->GetInteger("tabId", &tab_id));
  if (!InitCommon(tab_id))
    return false;

  std::string text;
  EXTENSION_FUNCTION_VALIDATE(args->GetString("text", &text));

  page_action_->SetBadgeText(tab_id, text);
  contents_->extension_tab_helper()->PageActionStateChanged();
  return true;
}