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

#include "base/command_line.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/blocked_content_container.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
#include "chrome/browser/geolocation/geolocation_content_settings_map.h"
#include "chrome/browser/metrics/user_metrics.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/tab_contents/tab_specific_content_settings.h"
#include "chrome/browser/ui/collected_cookies_infobar_delegate.h"
#include "chrome/common/pref_names.h"
#include "content/browser/renderer_host/render_view_host.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/browser/tab_contents/tab_contents_delegate.h"
#include "content/common/notification_service.h"
#include "grit/generated_resources.h"
#include "net/base/net_util.h"
#include "ui/base/l10n/l10n_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();
  }

  virtual ~ContentSettingTitleAndLinkModel() {}

 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.
      0,  // Prerender does not have a bubble.
    };
    // Fields as for kBlockedTitleIDs, above.
    static const int kResourceSpecificBlockedTitleIDs[] = {
      0,
      0,
      0,
      IDS_BLOCKED_PLUGINS_TITLE,
      0,
      0,
      0,
      0,
    };
    static const int kAccessedTitleIDs[] = {
      IDS_ACCESSED_COOKIES_TITLE,
      0,
      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())) {
      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.
      0,  // Prerender does 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()->ShowContentSettingsPage(content_type());
  }
};

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

  virtual ~ContentSettingTitleLinkAndCustomModel() {}

 private:
  void SetCustomLink() {
    static const int kCustomIDs[] = {
      IDS_BLOCKED_COOKIES_INFO,
      0,  // Images do not have a custom link.
      0,  // Javascript doesn't have a custom link.
      IDS_BLOCKED_PLUGINS_LOAD_ALL,
      0,  // Popups do not have a custom link.
      0,  // Geolocation custom links are set within that class.
      0,  // Notifications do not have a bubble.
      0,  // Prerender does not have a bubble.
    };
    COMPILE_ASSERT(arraysize(kCustomIDs) == CONTENT_SETTINGS_NUM_TYPES,
                   Need_a_setting_for_every_content_settings_type);
    if (kCustomIDs[content_type()])
      set_custom_link(l10n_util::GetStringUTF8(kCustomIDs[content_type()]));
  }

  virtual void OnCustomLinkClicked() {}
};


class ContentSettingSingleRadioGroup
    : public ContentSettingTitleLinkAndCustomModel {
 public:
  ContentSettingSingleRadioGroup(TabContents* tab_contents,
                                 Profile* profile,
                                 ContentSettingsType content_type)
      : ContentSettingTitleLinkAndCustomModel(tab_contents, profile,
                                              content_type),
        block_setting_(CONTENT_SETTING_BLOCK),
        selected_item_(0) {
    SetRadioGroup();
  }

  virtual ~ContentSettingSingleRadioGroup() {
    if (settings_changed()) {
      ContentSetting setting =
          selected_item_ == 0 ? CONTENT_SETTING_ALLOW : block_setting_;
      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);
        }
      }
    }
  }

 protected:
  bool settings_changed() const {
    return selected_item_ != bubble_content().radio_group.default_item;
  }

 private:
  ContentSetting block_setting_;
  int selected_item_;

  // Initialize the radio group by setting the appropriate labels for the
  // content type and setting the default value based on the content setting.
  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));

    if (display_host.empty())
      display_host = url.spec();

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

    RadioGroup radio_group;
    radio_group.url = url;

    static const int kAllowIDs[] = {
      IDS_BLOCKED_COOKIES_UNBLOCK,
      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.
      0,  // Prerender does 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,
      0,  // Prerender does not have a bubble.
    };
    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[] = {
      IDS_BLOCKED_COOKIES_NO_ACTION,
      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.
      0,  // Prerender does 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::GetStringUTF8(kBlockIDs[content_type()]);

    radio_group.radio_items.push_back(radio_allow_label);
    radio_group.radio_items.push_back(radio_block_label);
    HostContentSettingsMap* map = profile()->GetHostContentSettingsMap();
    ContentSetting mostRestrictiveSetting;
    if (resources.empty()) {
      mostRestrictiveSetting =
          map->GetContentSetting(url, content_type(), std::string());
    } else {
      mostRestrictiveSetting = CONTENT_SETTING_ALLOW;
      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) {
          mostRestrictiveSetting = CONTENT_SETTING_BLOCK;
          break;
        }
        if (setting == CONTENT_SETTING_ASK)
          mostRestrictiveSetting = CONTENT_SETTING_ASK;
      }
    }
    if (mostRestrictiveSetting == CONTENT_SETTING_ALLOW) {
      radio_group.default_item = 0;
      // |block_setting_| is already set to |CONTENT_SETTING_BLOCK|.
    } else {
      radio_group.default_item = 1;
      block_setting_ = mostRestrictiveSetting;
    }
    selected_item_ = radio_group.default_item;
    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) {
    selected_item_ = radio_index;
  }
};

class ContentSettingCookiesBubbleModel : public ContentSettingSingleRadioGroup {
 public:
  ContentSettingCookiesBubbleModel(TabContents* tab_contents,
                                   Profile* profile,
                                   ContentSettingsType content_type)
      : ContentSettingSingleRadioGroup(tab_contents, profile, content_type) {
    DCHECK_EQ(CONTENT_SETTINGS_TYPE_COOKIES, content_type);
    set_custom_link_enabled(true);
  }

  virtual ~ContentSettingCookiesBubbleModel() {
    if (settings_changed()) {
      tab_contents()->AddInfoBar(
          new CollectedCookiesInfoBarDelegate(tab_contents()));
    }
  }

 private:
  virtual void OnCustomLinkClicked() OVERRIDE {
    if (tab_contents()) {
      NotificationService::current()->Notify(
          NotificationType::COLLECTED_COOKIES_SHOWN,
          Source<TabSpecificContentSettings>(
              tab_contents()->GetTabSpecificContentSettings()),
          NotificationService::NoDetails());
      tab_contents()->delegate()->ShowCollectedCookiesDialog(tab_contents());
    }
  }
};

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);
    set_custom_link_enabled(tab_contents && tab_contents->
        GetTabSpecificContentSettings()->load_plugins_link_enabled());
  }

  virtual ~ContentSettingPluginBubbleModel() {}

 private:
  virtual void OnCustomLinkClicked() OVERRIDE {
    UserMetrics::RecordAction(UserMetricsAction("ClickToPlay_LoadAll_Bubble"));
    DCHECK(tab_contents());
    tab_contents()->render_view_host()->LoadBlockedPlugins();
    set_custom_link_enabled(false);
    tab_contents()->GetTabSpecificContentSettings()->
        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();
  }

  virtual ~ContentSettingPopupBubbleModel() {}

 private:
  void SetPopups() {
    // check for crbug.com/53176
    if (!tab_contents()->blocked_content_container())
      return;
    std::vector<TabContents*> blocked_contents;
    tab_contents()->blocked_content_container()->GetBlockedContents(
        &blocked_contents);
    for (std::vector<TabContents*>::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_content_container()) {
      tab_contents()->blocked_content_container()->LaunchForContents(
          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";
    SetDomainsAndCustomLink();
  }

  virtual ~ContentSettingDomainListBubbleModel() {}

 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 SetDomainsAndCustomLink() {
    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_custom_link(l10n_util::GetStringUTF8(
                      IDS_GEOLOCATION_BUBBLE_CLEAR_LINK));
      set_custom_link_enabled(true);
    } else if (tab_state_flags &
               GeolocationSettingsState::TABSTATE_HAS_CHANGED) {
      set_custom_link(l10n_util::GetStringUTF8(
                      IDS_GEOLOCATION_BUBBLE_REQUIRE_RELOAD_TO_CLEAR));
    }
  }
  virtual void OnCustomLinkClicked() OVERRIDE {
    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 ContentSettingCookiesBubbleModel(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) {
  registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
                 Source<TabContents>(tab_contents));
}

ContentSettingBubbleModel::~ContentSettingBubbleModel() {
}

ContentSettingBubbleModel::RadioGroup::RadioGroup() : default_item(0) {}

ContentSettingBubbleModel::RadioGroup::~RadioGroup() {}

ContentSettingBubbleModel::DomainList::DomainList() {}

ContentSettingBubbleModel::DomainList::~DomainList() {}

ContentSettingBubbleModel::BubbleContent::BubbleContent()
    : custom_link_enabled(false) {
}

ContentSettingBubbleModel::BubbleContent::~BubbleContent() {}


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;
}