// Copyright (c) 2010 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/content_setting_bubble_model.h"

#include "app/l10n_util.h"
#include "base/command_line.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/blocked_popup_container.h"
#include "chrome/browser/geolocation/geolocation_content_settings_map.h"
#include "chrome/browser/host_content_settings_map.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/renderer_host/render_view_host.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_delegate.h"
#include "chrome/browser/tab_contents/tab_specific_content_settings.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notification_service.h"
#include "chrome/common/pref_names.h"
#include "grit/generated_resources.h"
#include "net/base/net_util.h"

class ContentSettingTitleAndLinkModel : public ContentSettingBubbleModel {
 public:
  ContentSettingTitleAndLinkModel(TabContents* tab_contents, Profile* profile,
      ContentSettingsType content_type)
      : ContentSettingBubbleModel(tab_contents, profile, content_type) {
     // Notifications do not have a bubble.
     DCHECK_NE(content_type, CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
     SetBlockedResources();
     SetTitle();
     SetManageLink();
  }

 private:
  void SetBlockedResources() {
    TabSpecificContentSettings* settings =
        tab_contents()->GetTabSpecificContentSettings();
    const std::set<std::string>& resources = settings->BlockedResourcesForType(
        content_type());
    for (std::set<std::string>::const_iterator it = resources.begin();
        it != resources.end(); ++it) {
      AddBlockedResource(*it);
    }
  }

  void SetTitle() {
    static const int kBlockedTitleIDs[] = {
      IDS_BLOCKED_COOKIES_TITLE,
      IDS_BLOCKED_IMAGES_TITLE,
      IDS_BLOCKED_JAVASCRIPT_TITLE,
      IDS_BLOCKED_PLUGINS_MESSAGE,
      IDS_BLOCKED_POPUPS_TITLE,
      0,  // Geolocation does not have an overall title.
      0,  // Notifications do not have a bubble.
    };
    // Fields as for kBlockedTitleIDs, above.
    static const int kResourceSpecificBlockedTitleIDs[] = {
      0,
      0,
      0,
      IDS_BLOCKED_PLUGINS_TITLE,
      0,
      0,
      0,
    };
    static const int kAccessedTitleIDs[] = {
      IDS_ACCESSED_COOKIES_TITLE,
      0,
      0,
      0,
      0,
      0,
      0,
    };
    COMPILE_ASSERT(arraysize(kAccessedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    COMPILE_ASSERT(arraysize(kBlockedTitleIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    COMPILE_ASSERT(arraysize(kResourceSpecificBlockedTitleIDs) ==
        CONTENT_SETTINGS_NUM_TYPES,
        Need_a_setting_for_every_content_settings_type);
    const int *title_ids = kBlockedTitleIDs;
    if (tab_contents() &&
        tab_contents()->GetTabSpecificContentSettings()->IsContentAccessed(
            content_type()) &&
        !tab_contents()->GetTabSpecificContentSettings()->IsContentBlocked(
            content_type()) &&
        !CommandLine::ForCurrentProcess()->HasSwitch(
            switches::kEnableCookiePrompt)) {
      title_ids = kAccessedTitleIDs;
    } else if (!bubble_content().resource_identifiers.empty()) {
      title_ids = kResourceSpecificBlockedTitleIDs;
    }
    if (title_ids[content_type()])
      set_title(l10n_util::GetStringUTF8(title_ids[content_type()]));
  }

  void SetManageLink() {
    static const int kLinkIDs[] = {
      IDS_BLOCKED_COOKIES_LINK,
      IDS_BLOCKED_IMAGES_LINK,
      IDS_BLOCKED_JAVASCRIPT_LINK,
      IDS_BLOCKED_PLUGINS_LINK,
      IDS_BLOCKED_POPUPS_LINK,
      IDS_GEOLOCATION_BUBBLE_MANAGE_LINK,
      0,  // Notifications do not have a bubble.
    };
    COMPILE_ASSERT(arraysize(kLinkIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    set_manage_link(l10n_util::GetStringUTF8(kLinkIDs[content_type()]));
  }

  virtual void OnManageLinkClicked() {
    if (tab_contents())
      tab_contents()->delegate()->ShowContentSettingsWindow(content_type());
  }
};

class ContentSettingTitleLinkAndInfoModel
    : public ContentSettingTitleAndLinkModel {
 public:
  ContentSettingTitleLinkAndInfoModel(TabContents* tab_contents,
                                      Profile* profile,
                                      ContentSettingsType content_type)
      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
    if (!CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kEnableCookiePrompt))
      SetInfoLink();
  }

 private:
  void SetInfoLink() {
    static const int kInfoIDs[] = {
      IDS_BLOCKED_COOKIES_INFO,
      0,  // Images do not have an info link.
      0,  // Javascript doesn't have an info link.
      0,  // Plugins do not have an info link.
      0,  // Popups do not have an info link.
      0,  // Geolocation does not have an info link.
      0,  // Notifications do not have a bubble.
    };
    COMPILE_ASSERT(arraysize(kInfoIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    if (kInfoIDs[content_type()])
      set_info_link(l10n_util::GetStringUTF8(kInfoIDs[content_type()]));
  }

  virtual void OnInfoLinkClicked() {
    DCHECK(content_type() == CONTENT_SETTINGS_TYPE_COOKIES);
    if (tab_contents()) {
      NotificationService::current()->Notify(
          NotificationType::COLLECTED_COOKIES_SHOWN,
          Source<TabSpecificContentSettings>(
              tab_contents()->GetTabSpecificContentSettings()),
          NotificationService::NoDetails());
      tab_contents()->delegate()->ShowCollectedCookiesDialog(tab_contents());
    }
  }
};


class ContentSettingSingleRadioGroup : public ContentSettingTitleAndLinkModel {
 public:
  ContentSettingSingleRadioGroup(TabContents* tab_contents, Profile* profile,
      ContentSettingsType content_type)
      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
    SetRadioGroup();
  }

 private:
  void SetRadioGroup() {
    GURL url = tab_contents()->GetURL();
    std::wstring display_host_wide;
    net::AppendFormattedHost(url,
        UTF8ToWide(profile()->GetPrefs()->GetString(prefs::kAcceptLanguages)),
        &display_host_wide, NULL, NULL);
    std::string display_host(WideToUTF8(display_host_wide));

    const std::set<std::string>& resources =
        bubble_content().resource_identifiers;

    RadioGroup radio_group;
    radio_group.url = url;

    static const int kAllowIDs[] = {
      0,  // We don't manage cookies here.
      IDS_BLOCKED_IMAGES_UNBLOCK,
      IDS_BLOCKED_JAVASCRIPT_UNBLOCK,
      IDS_BLOCKED_PLUGINS_UNBLOCK_ALL,
      IDS_BLOCKED_POPUPS_UNBLOCK,
      0,  // We don't manage geolocation here.
      0,  // Notifications do not have a bubble.
    };
    COMPILE_ASSERT(arraysize(kAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
     // Fields as for kAllowIDs, above.
    static const int kResourceSpecificAllowIDs[] = {
      0,
      0,
      0,
      IDS_BLOCKED_PLUGINS_UNBLOCK,
      0,
      0,
      0,
    };
    COMPILE_ASSERT(
        arraysize(kResourceSpecificAllowIDs) == CONTENT_SETTINGS_NUM_TYPES,
        Need_a_setting_for_every_content_settings_type);
    std::string radio_allow_label;
    const int* allowIDs = resources.empty() ?
        kAllowIDs : kResourceSpecificAllowIDs;
    radio_allow_label = l10n_util::GetStringFUTF8(
        allowIDs[content_type()], UTF8ToUTF16(display_host));

    static const int kBlockIDs[] = {
      0,  // We don't manage cookies here.
      IDS_BLOCKED_IMAGES_NO_ACTION,
      IDS_BLOCKED_JAVASCRIPT_NO_ACTION,
      IDS_BLOCKED_PLUGINS_NO_ACTION,
      IDS_BLOCKED_POPUPS_NO_ACTION,
      0,  // We don't manage geolocation here.
      0,  // Notifications do not have a bubble.
    };
    COMPILE_ASSERT(arraysize(kBlockIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    std::string radio_block_label;
    radio_block_label = l10n_util::GetStringFUTF8(
        kBlockIDs[content_type()], UTF8ToUTF16(display_host));

    radio_group.radio_items.push_back(radio_allow_label);
    radio_group.radio_items.push_back(radio_block_label);
    HostContentSettingsMap* map = profile()->GetHostContentSettingsMap();
    if (resources.empty()) {
      ContentSetting setting = map->GetContentSetting(url, content_type(),
                                                      std::string());
      radio_group.default_item = (setting == CONTENT_SETTING_ALLOW) ? 0 : 1;
    } else {
      // The default item is "block" if at least one of the resources
      // is blocked.
      radio_group.default_item = 0;
      for (std::set<std::string>::const_iterator it = resources.begin();
           it != resources.end(); ++it) {
        ContentSetting setting = map->GetContentSetting(
            url, content_type(), *it);
        if (setting == CONTENT_SETTING_BLOCK) {
          radio_group.default_item = 1;
          break;
        }
      }
    }
    set_radio_group(radio_group);
  }

  void AddException(ContentSetting setting,
                    const std::string& resource_identifier) {
    profile()->GetHostContentSettingsMap()->AddExceptionForURL(
        bubble_content().radio_group.url, content_type(), resource_identifier,
        setting);
  }

  virtual void OnRadioClicked(int radio_index) {
    ContentSetting setting =
        radio_index == 0 ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
    const std::set<std::string>& resources =
        bubble_content().resource_identifiers;
    if (resources.empty()) {
      AddException(setting, std::string());
    } else {
      for (std::set<std::string>::const_iterator it = resources.begin();
           it != resources.end(); ++it) {
        AddException(setting, *it);
      }
    }
  }
};

class ContentSettingPluginBubbleModel : public ContentSettingSingleRadioGroup {
 public:
  ContentSettingPluginBubbleModel(TabContents* tab_contents, Profile* profile,
                                  ContentSettingsType content_type)
      : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
    DCHECK_EQ(content_type, CONTENT_SETTINGS_TYPE_PLUGINS);
    SetLoadPluginsLinkTitle();
  }

 private:
  void SetLoadPluginsLinkTitle() {
    if (!CommandLine::ForCurrentProcess()->HasSwitch(
        switches::kDisableClickToPlay)) {
      set_load_plugins_link_title(
          l10n_util::GetStringUTF8(IDS_BLOCKED_PLUGINS_LOAD_ALL));
     }
   }

  virtual void OnLoadPluginsLinkClicked() {
    DCHECK(!CommandLine::ForCurrentProcess()->HasSwitch(
           switches::kDisableClickToPlay));
    UserMetrics::RecordAction(UserMetricsAction("ClickToPlay_LoadAll_Bubble"));
    if (tab_contents()) {
      tab_contents()->render_view_host()->LoadBlockedPlugins();
    }
    set_load_plugins_link_enabled(false);
    TabSpecificContentSettings* settings =
        tab_contents()->GetTabSpecificContentSettings();
    settings->set_load_plugins_link_enabled(false);
  }
};

class ContentSettingPopupBubbleModel : public ContentSettingSingleRadioGroup {
 public:
  ContentSettingPopupBubbleModel(TabContents* tab_contents, Profile* profile,
      ContentSettingsType content_type)
      : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
    SetPopups();
  }

 private:
  void SetPopups() {
    // check for crbug.com/53176
    if (!tab_contents()->blocked_popup_container())
      return;
    BlockedPopupContainer::BlockedContents blocked_contents;
    tab_contents()->blocked_popup_container()->GetBlockedContents(
        &blocked_contents);
    for (BlockedPopupContainer::BlockedContents::const_iterator
         i(blocked_contents.begin()); i != blocked_contents.end(); ++i) {
      std::string title(UTF16ToUTF8((*i)->GetTitle()));
      // The popup may not have committed a load yet, in which case it won't
      // have a URL or title.
      if (title.empty())
        title = l10n_util::GetStringUTF8(IDS_TAB_LOADING_TITLE);
      PopupItem popup_item;
      popup_item.title = title;
      popup_item.bitmap = (*i)->GetFavIcon();
      popup_item.tab_contents = (*i);
      add_popup(popup_item);
    }
  }

  virtual void OnPopupClicked(int index) {
    if (tab_contents() && tab_contents()->blocked_popup_container()) {
      tab_contents()->blocked_popup_container()->LaunchPopupForContents(
          bubble_content().popup_items[index].tab_contents);
    }
  }
};

class ContentSettingDomainListBubbleModel
    : public ContentSettingTitleAndLinkModel {
 public:
  ContentSettingDomainListBubbleModel(TabContents* tab_contents,
                                      Profile* profile,
                                      ContentSettingsType content_type)
      : ContentSettingTitleAndLinkModel(tab_contents, profile, content_type) {
    DCHECK_EQ(CONTENT_SETTINGS_TYPE_GEOLOCATION, content_type) <<
        "SetDomains currently only supports geolocation content type";
    SetDomainsAndClearLink();
  }

 private:
  void MaybeAddDomainList(const std::set<std::string>& hosts, int title_id) {
    if (!hosts.empty()) {
      DomainList domain_list;
      domain_list.title = l10n_util::GetStringUTF8(title_id);
      domain_list.hosts = hosts;
      add_domain_list(domain_list);
    }
  }
  void SetDomainsAndClearLink() {
    TabSpecificContentSettings* content_settings =
        tab_contents()->GetTabSpecificContentSettings();
    const GeolocationSettingsState& settings =
        content_settings->geolocation_settings_state();
    GeolocationSettingsState::FormattedHostsPerState formatted_hosts_per_state;
    unsigned int tab_state_flags = 0;
    settings.GetDetailedInfo(&formatted_hosts_per_state, &tab_state_flags);
    // Divide the tab's current geolocation users into sets according to their
    // permission state.
    MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_ALLOW],
                       IDS_GEOLOCATION_BUBBLE_SECTION_ALLOWED);

    MaybeAddDomainList(formatted_hosts_per_state[CONTENT_SETTING_BLOCK],
                       IDS_GEOLOCATION_BUBBLE_SECTION_DENIED);

    if (tab_state_flags & GeolocationSettingsState::TABSTATE_HAS_EXCEPTION) {
      set_clear_link(
          l10n_util::GetStringUTF8(IDS_GEOLOCATION_BUBBLE_CLEAR_LINK));
    } else if (tab_state_flags &
               GeolocationSettingsState::TABSTATE_HAS_CHANGED) {
      // It is a slight abuse of the domain list field to use it for the reload
      // hint, but works fine for now. TODO(joth): If we need to style it
      // differently, consider adding an explicit field, or generalize the
      // domain list to be a flat list of style formatted lines.
      DomainList reload_section;
      reload_section.title = l10n_util::GetStringUTF8(
          IDS_GEOLOCATION_BUBBLE_REQUIRE_RELOAD_TO_CLEAR);
      add_domain_list(reload_section);
    }
  }
  virtual void OnClearLinkClicked() {
    if (!tab_contents())
      return;
    // Reset this embedder's entry to default for each of the requesting
    // origins currently on the page.
    const GURL& embedder_url = tab_contents()->GetURL();
    TabSpecificContentSettings* content_settings =
        tab_contents()->GetTabSpecificContentSettings();
    const GeolocationSettingsState::StateMap& state_map =
        content_settings->geolocation_settings_state().state_map();
    GeolocationContentSettingsMap* settings_map =
        profile()->GetGeolocationContentSettingsMap();
    for (GeolocationSettingsState::StateMap::const_iterator it =
         state_map.begin(); it != state_map.end(); ++it) {
      settings_map->SetContentSetting(it->first, embedder_url,
                                      CONTENT_SETTING_DEFAULT);
    }
  }
};

// static
ContentSettingBubbleModel*
    ContentSettingBubbleModel::CreateContentSettingBubbleModel(
        TabContents* tab_contents,
        Profile* profile,
        ContentSettingsType content_type) {
  if (content_type == CONTENT_SETTINGS_TYPE_COOKIES) {
    return new ContentSettingTitleLinkAndInfoModel(tab_contents, profile,
                                                   content_type);
  }
  if (content_type == CONTENT_SETTINGS_TYPE_POPUPS) {
    return new ContentSettingPopupBubbleModel(tab_contents, profile,
                                              content_type);
  }
  if (content_type == CONTENT_SETTINGS_TYPE_GEOLOCATION) {
    return new ContentSettingDomainListBubbleModel(tab_contents, profile,
                                                   content_type);
  }
  if (content_type == CONTENT_SETTINGS_TYPE_PLUGINS) {
    return new ContentSettingPluginBubbleModel(tab_contents, profile,
                                               content_type);
  }
  return new ContentSettingSingleRadioGroup(tab_contents, profile,
                                            content_type);
}

ContentSettingBubbleModel::ContentSettingBubbleModel(
    TabContents* tab_contents, Profile* profile,
    ContentSettingsType content_type)
    : tab_contents_(tab_contents), profile_(profile),
      content_type_(content_type) {
  if (tab_contents) {
    TabSpecificContentSettings* settings =
        tab_contents->GetTabSpecificContentSettings();
    set_load_plugins_link_enabled(settings->load_plugins_link_enabled());
  } else {
    set_load_plugins_link_enabled(true);
  }
  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
                 Source<TabContents>(tab_contents));
}

ContentSettingBubbleModel::~ContentSettingBubbleModel() {
}

void ContentSettingBubbleModel::AddBlockedResource(
    const std::string& resource_identifier) {
  bubble_content_.resource_identifiers.insert(resource_identifier);
}

void ContentSettingBubbleModel::Observe(NotificationType type,
                                        const NotificationSource& source,
                                        const NotificationDetails& details) {
  DCHECK(type == NotificationType::TAB_CONTENTS_DESTROYED);
  DCHECK(source == Source<TabContents>(tab_contents_));
  tab_contents_ = NULL;
}