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

#include <map>

#include "base/json/json_writer.h"
#include "base/memory/singleton.h"
#include "base/stl_util-inl.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/browser/extensions/extension_event_router.h"
#include "chrome/browser/extensions/extension_prefs.h"
#include "chrome/browser/extensions/extension_proxy_api.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "content/common/notification_type.h"
#include "content/common/notification_service.h"

namespace {

struct PrefMappingEntry {
  const char* extension_pref;
  const char* browser_pref;
  const char* permission;
};

const char kNotControllable[] = "NotControllable";
const char kControlledByOtherExtensions[] = "ControlledByOtherExtensions";
const char kControllableByThisExtension[] = "ControllableByThisExtension";
const char kControlledByThisExtension[] = "ControlledByThisExtension";

const char kIncognito[] = "incognito";
const char kIncognitoSpecific[] = "incognitoSpecific";
const char kLevelOfControl[] = "levelOfControl";
const char kValue[] = "value";

const char kOnPrefChangeFormat[] = "experimental.preferences.%s.onChange";

const char kIncognitoErrorMessage[] =
    "You do not have permission to access incognito preferences.";

const char kPermissionErrorMessage[] =
    "You do not have permission to access the preference '%s'. "
    "Be sure to declare in your manifest what permissions you need.";

PrefMappingEntry kPrefMapping[] = {
  { "blockThirdPartyCookies",
    prefs::kBlockThirdPartyCookies,
    Extension::kContentSettingsPermission
  },
  { "enableReferrers",
    prefs::kEnableReferrers,
    Extension::kContentSettingsPermission
  },
  { "enableHyperlinkAuditing",
    prefs::kEnableHyperlinkAuditing,
    Extension::kContentSettingsPermission
  },
  { "proxy",
    prefs::kProxy,
    Extension::kProxyPermission
  },
};

class IdentityPrefTransformer : public PrefTransformerInterface {
 public:
  IdentityPrefTransformer() { }
  virtual ~IdentityPrefTransformer() { }

  virtual Value* ExtensionToBrowserPref(const Value* extension_pref,
                                        std::string* error) {
    return extension_pref->DeepCopy();
  }

  virtual Value* BrowserToExtensionPref(const Value* browser_pref) {
    return browser_pref->DeepCopy();
  }
};

// Returns a string constant (defined in the API) indicating the level of
// control this extension has over the specified preference.
const char* GetLevelOfControl(
    Profile* profile,
    const std::string& extension_id,
    const std::string& browser_pref,
    bool incognito) {
  PrefService* prefs = incognito ? profile->GetOffTheRecordPrefs()
                                 : profile->GetPrefs();
  const PrefService::Preference* pref =
      prefs->FindPreference(browser_pref.c_str());
  CHECK(pref);
  ExtensionPrefs* ep = profile->GetExtensionService()->extension_prefs();

  if (!pref->IsExtensionModifiable())
    return kNotControllable;

  if (ep->DoesExtensionControlPref(extension_id, browser_pref, incognito))
    return kControlledByThisExtension;

  if (ep->CanExtensionControlPref(extension_id, browser_pref, incognito))
    return kControllableByThisExtension;

  return kControlledByOtherExtensions;
}

class PrefMapping {
 public:
  static PrefMapping* GetInstance() {
    return Singleton<PrefMapping>::get();
  }

  bool FindBrowserPrefForExtensionPref(const std::string& extension_pref,
                                       std::string* browser_pref,
                                       std::string* permission) {
    std::map<std::string, std::pair<std::string, std::string> >::iterator it =
        mapping_.find(extension_pref);
    if (it != mapping_.end()) {
      *browser_pref = it->second.first;
      *permission = it->second.second;
      return true;
    }
    return false;
  }

  bool FindEventForBrowserPref(const std::string& browser_pref,
                               std::string* event_name,
                               std::string* permission) {
    std::map<std::string, std::pair<std::string, std::string> >::iterator it =
        event_mapping_.find(browser_pref);
    if (it != event_mapping_.end()) {
      *event_name = it->second.first;
      *permission = it->second.second;
      return true;
    }
    return false;
  }

  PrefTransformerInterface* FindTransformerForBrowserPref(
      const std::string& browser_pref) {
    std::map<std::string, PrefTransformerInterface*>::iterator it =
        transformers_.find(browser_pref);
    if (it != transformers_.end())
      return it->second;
    else
      return identity_transformer_.get();
  }

 private:
  friend struct DefaultSingletonTraits<PrefMapping>;

  PrefMapping() {
    identity_transformer_.reset(new IdentityPrefTransformer());
    for (size_t i = 0; i < arraysize(kPrefMapping); ++i) {
      mapping_[kPrefMapping[i].extension_pref] =
          std::make_pair(kPrefMapping[i].browser_pref,
                         kPrefMapping[i].permission);
      std::string event_name =
          base::StringPrintf(kOnPrefChangeFormat,
                             kPrefMapping[i].extension_pref);
      event_mapping_[kPrefMapping[i].browser_pref] =
          std::make_pair(event_name, kPrefMapping[i].permission);
    }
    DCHECK_EQ(arraysize(kPrefMapping), mapping_.size());
    DCHECK_EQ(arraysize(kPrefMapping), event_mapping_.size());
    RegisterPrefTransformer(prefs::kProxy, new ProxyPrefTransformer());
  }

  ~PrefMapping() {
    STLDeleteContainerPairSecondPointers(transformers_.begin(),
                                         transformers_.end());
  }

  void RegisterPrefTransformer(const std::string& browser_pref,
                               PrefTransformerInterface* transformer) {
    DCHECK_EQ(0u, transformers_.count(browser_pref)) <<
        "Trying to register pref transformer for " << browser_pref << " twice";
    transformers_[browser_pref] = transformer;
  }

  // Mapping from extension pref keys to browser pref keys and permissions.
  std::map<std::string, std::pair<std::string, std::string> > mapping_;

  // Mapping from browser pref keys to extension event names and permissions.
  std::map<std::string, std::pair<std::string, std::string> > event_mapping_;

  // Mapping from browser pref keys to transformers.
  std::map<std::string, PrefTransformerInterface*> transformers_;

  scoped_ptr<PrefTransformerInterface> identity_transformer_;

  DISALLOW_COPY_AND_ASSIGN(PrefMapping);
};

}  // namespace

ExtensionPreferenceEventRouter::ExtensionPreferenceEventRouter(
    Profile* profile) : profile_(profile) {
  registrar_.Init(profile_->GetPrefs());
  incognito_registrar_.Init(profile_->GetOffTheRecordPrefs());
  for (size_t i = 0; i < arraysize(kPrefMapping); ++i) {
    registrar_.Add(kPrefMapping[i].browser_pref, this);
    incognito_registrar_.Add(kPrefMapping[i].browser_pref, this);
  }
}

ExtensionPreferenceEventRouter::~ExtensionPreferenceEventRouter() { }

void ExtensionPreferenceEventRouter::Observe(
    NotificationType type,
    const NotificationSource& source,
    const NotificationDetails& details) {
  if (type == NotificationType::PREF_CHANGED) {
    const std::string* pref_key =
        Details<const std::string>(details).ptr();
    OnPrefChanged(Source<PrefService>(source).ptr(), *pref_key);
  } else {
    NOTREACHED();
  }
}

void ExtensionPreferenceEventRouter::OnPrefChanged(
    PrefService* pref_service,
    const std::string& browser_pref) {
  bool incognito = (pref_service != profile_->GetPrefs());

  std::string event_name;
  std::string permission;
  bool rv = PrefMapping::GetInstance()->FindEventForBrowserPref(
      browser_pref, &event_name, &permission);
  DCHECK(rv);

  ListValue args;
  DictionaryValue* dict = new DictionaryValue();
  args.Append(dict);
  const PrefService::Preference* pref =
      pref_service->FindPreference(browser_pref.c_str());
  CHECK(pref);
  ExtensionService* extension_service = profile_->GetExtensionService();
  PrefTransformerInterface* transformer =
      PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
  dict->Set(kValue, transformer->BrowserToExtensionPref(pref->GetValue()));
  if (incognito) {
    ExtensionPrefs* ep = extension_service->extension_prefs();
    dict->Set(
        kIncognitoSpecific,
        Value::CreateBooleanValue(ep->HasIncognitoPrefValue(browser_pref)));
  }

  ExtensionEventRouter* router = profile_->GetExtensionEventRouter();
  if (!router || !router->HasEventListener(event_name))
    return;
  const ExtensionList* extensions = extension_service->extensions();
  for (ExtensionList::const_iterator it = extensions->begin();
       it != extensions->end(); ++it) {
    std::string extension_id = (*it)->id();
    // TODO(bauerb): Only iterate over registered event listeners.
    if (router->ExtensionHasEventListener(extension_id, event_name) &&
        (*it)->HasApiPermission(permission) &&
        (!incognito || extension_service->CanCrossIncognito(*it))) {
      std::string level_of_control =
          GetLevelOfControl(profile_, extension_id, browser_pref, incognito);
      dict->Set(kLevelOfControl, Value::CreateStringValue(level_of_control));

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

      DispatchEvent(extension_id, event_name, json_args);
    }
  }
}

void ExtensionPreferenceEventRouter::DispatchEvent(
    const std::string& extension_id,
    const std::string& event_name,
    const std::string& json_args) {
  profile_->GetExtensionEventRouter()->DispatchEventToExtension(
      extension_id, event_name, json_args, NULL, GURL());
}

// TODO(battre): Factor out common parts once this is stable.

GetPreferenceFunction::~GetPreferenceFunction() { }

bool GetPreferenceFunction::RunImpl() {
  std::string pref_key;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &pref_key));
  DictionaryValue* details = NULL;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &details));

  bool incognito = false;
  if (details->HasKey(kIncognito))
    EXTENSION_FUNCTION_VALIDATE(details->GetBoolean(kIncognito, &incognito));

  if (incognito && !include_incognito()) {
    error_ = kIncognitoErrorMessage;
    return false;
  }

  PrefService* prefs = incognito ? profile_->GetOffTheRecordPrefs()
                                 : profile_->GetPrefs();
  std::string browser_pref;
  std::string permission;
  EXTENSION_FUNCTION_VALIDATE(
      PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
          pref_key, &browser_pref, &permission));
  if (!GetExtension()->HasApiPermission(permission)) {
    error_ = base::StringPrintf(kPermissionErrorMessage, pref_key.c_str());
    return false;
  }

  const PrefService::Preference* pref =
      prefs->FindPreference(browser_pref.c_str());
  CHECK(pref);
  std::string level_of_control =
      GetLevelOfControl(profile_, extension_id(), browser_pref, incognito);

  scoped_ptr<DictionaryValue> result(new DictionaryValue);
  PrefTransformerInterface* transformer =
      PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
  result->Set(kValue, transformer->BrowserToExtensionPref(pref->GetValue()));
  result->Set(kLevelOfControl, Value::CreateStringValue(level_of_control));
  if (incognito) {
    ExtensionPrefs* ep = profile_->GetExtensionService()->extension_prefs();
    result->Set(
        kIncognitoSpecific,
        Value::CreateBooleanValue(ep->HasIncognitoPrefValue(browser_pref)));
  }
  result_.reset(result.release());
  return true;
}

SetPreferenceFunction::~SetPreferenceFunction() { }

bool SetPreferenceFunction::RunImpl() {
  std::string pref_key;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &pref_key));
  DictionaryValue* details = NULL;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &details));

  Value* value = NULL;
  EXTENSION_FUNCTION_VALIDATE(details->Get(kValue, &value));

  bool incognito = false;
  if (details->HasKey(kIncognito))
    EXTENSION_FUNCTION_VALIDATE(details->GetBoolean(kIncognito, &incognito));

  if (incognito && !include_incognito()) {
    error_ = kIncognitoErrorMessage;
    return false;
  }

  std::string browser_pref;
  std::string permission;
  EXTENSION_FUNCTION_VALIDATE(
      PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
          pref_key, &browser_pref, &permission));
  if (!GetExtension()->HasApiPermission(permission)) {
    error_ = base::StringPrintf(kPermissionErrorMessage, pref_key.c_str());
    return false;
  }
  ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
  const PrefService::Preference* pref =
      prefs->pref_service()->FindPreference(browser_pref.c_str());
  CHECK(pref);
  EXTENSION_FUNCTION_VALIDATE(value->GetType() == pref->GetType());
  PrefTransformerInterface* transformer =
      PrefMapping::GetInstance()->FindTransformerForBrowserPref(browser_pref);
  std::string error;
  Value* browserPrefValue = transformer->ExtensionToBrowserPref(value, &error);
  if (!browserPrefValue) {
    error_ = error;
    return false;
  }
  prefs->SetExtensionControlledPref(extension_id(),
                                    browser_pref,
                                    incognito,
                                    browserPrefValue);
  return true;
}

ClearPreferenceFunction::~ClearPreferenceFunction() { }

bool ClearPreferenceFunction::RunImpl() {
  std::string pref_key;
  EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &pref_key));
  DictionaryValue* details = NULL;
  EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &details));

  bool incognito = false;
  if (details->HasKey(kIncognito))
    EXTENSION_FUNCTION_VALIDATE(details->GetBoolean(kIncognito, &incognito));

  // We don't check incognito permissions here, as an extension should be always
  // allowed to clear its own settings.

  std::string browser_pref;
  std::string permission;
  EXTENSION_FUNCTION_VALIDATE(
      PrefMapping::GetInstance()->FindBrowserPrefForExtensionPref(
          pref_key, &browser_pref, &permission));
  if (!GetExtension()->HasApiPermission(permission)) {
    error_ = base::StringPrintf(kPermissionErrorMessage, pref_key.c_str());
    return false;
  }
  ExtensionPrefs* prefs = profile_->GetExtensionService()->extension_prefs();
  prefs->RemoveExtensionControlledPref(extension_id(), browser_pref, incognito);
  return true;
}