// Copyright (c) 2013 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.
//
// DeclarativeRule<>, DeclarativeConditionSet<>, and DeclarativeActionSet<>
// templates usable with multiple different declarativeFoo systems.  These are
// templated on the Condition and Action types that define the behavior of a
// particular declarative event.

#ifndef EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
#define EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__

#include <limits>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/callback.h"
#include "base/macros.h"
#include "base/memory/linked_ptr.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "components/url_matcher/url_matcher.h"
#include "extensions/common/api/events.h"
#include "extensions/common/extension.h"

namespace base {
class Time;
class Value;
}

namespace content {
class BrowserContext;
}

namespace extensions {

// This class stores a set of conditions that may be part of a DeclarativeRule.
// If any condition is fulfilled, the Actions of the DeclarativeRule can be
// triggered.
//
// ConditionT should be immutable after creation.  It must define the following
// members:
//
//   // Arguments passed through from DeclarativeConditionSet::Create.
//   static std::unique_ptr<ConditionT> Create(
//       const Extension* extension,
//       URLMatcherConditionFactory* url_matcher_condition_factory,
//       // Except this argument gets elements of the Values array.
//       const base::Value& definition,
//       std::string* error);
//   // If the Condition needs to be filtered by some URLMatcherConditionSets,
//   // append them to |condition_sets|.
//   // DeclarativeConditionSet::GetURLMatcherConditionSets forwards here.
//   void GetURLMatcherConditionSets(
//       URLMatcherConditionSet::Vector* condition_sets);
//   // |match_data| passed through from DeclarativeConditionSet::IsFulfilled.
//   bool IsFulfilled(const ConditionT::MatchData& match_data);
template<typename ConditionT>
class DeclarativeConditionSet {
 public:
  typedef std::vector<std::unique_ptr<base::Value>> Values;
  typedef std::vector<linked_ptr<const ConditionT> > Conditions;
  typedef typename Conditions::const_iterator const_iterator;

  // Factory method that creates a DeclarativeConditionSet for |extension|
  // according to the JSON array |conditions| passed by the extension API. Sets
  // |error| and returns NULL in case of an error.
  static std::unique_ptr<DeclarativeConditionSet> Create(
      const Extension* extension,
      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
      const Values& condition_values,
      std::string* error);

  const Conditions& conditions() const {
    return conditions_;
  }

  const_iterator begin() const { return conditions_.begin(); }
  const_iterator end() const { return conditions_.end(); }

  // If |url_match_trigger| is not -1, this function looks for a condition
  // with this URLMatcherConditionSet, and forwards to that condition's
  // IsFulfilled(|match_data|). If there is no such condition, then false is
  // returned. If |url_match_trigger| is -1, this function returns whether any
  // of the conditions without URL attributes is satisfied.
  bool IsFulfilled(url_matcher::URLMatcherConditionSet::ID url_match_trigger,
                   const typename ConditionT::MatchData& match_data) const;

  // Appends the URLMatcherConditionSet from all conditions to |condition_sets|.
  void GetURLMatcherConditionSets(
      url_matcher::URLMatcherConditionSet::Vector* condition_sets) const;

  // Returns whether there are some conditions without UrlFilter attributes.
  bool HasConditionsWithoutUrls() const {
    return !conditions_without_urls_.empty();
  }

 private:
  typedef std::map<url_matcher::URLMatcherConditionSet::ID, const ConditionT*>
      URLMatcherIdToCondition;

  DeclarativeConditionSet(
      const Conditions& conditions,
      const URLMatcherIdToCondition& match_id_to_condition,
      const std::vector<const ConditionT*>& conditions_without_urls);

  const URLMatcherIdToCondition match_id_to_condition_;
  const Conditions conditions_;
  const std::vector<const ConditionT*> conditions_without_urls_;

  DISALLOW_COPY_AND_ASSIGN(DeclarativeConditionSet);
};

// Immutable container for multiple actions.
//
// ActionT should be immutable after creation.  It must define the following
// members:
//
//   // Arguments passed through from ActionSet::Create.
//   static std::unique_ptr<ActionT> Create(
//       const Extension* extension,
//       // Except this argument gets elements of the Values array.
//       const base::Value& definition,
//       std::string* error, bool* bad_message);
//   void Apply(const std::string& extension_id,
//              const base::Time& extension_install_time,
//              // Contains action-type-specific in/out parameters.
//              typename ActionT::ApplyInfo* apply_info) const;
//   // Only needed if the RulesRegistry calls DeclarativeActionSet::Revert().
//   void Revert(const std::string& extension_id,
//               const base::Time& extension_install_time,
//               // Contains action-type-specific in/out parameters.
//               typename ActionT::ApplyInfo* apply_info) const;
//   // Return the minimum priority of rules that can be evaluated after this
//   // action runs.  A suitable default value is MIN_INT.
//   int minimum_priority() const;
//
// TODO(battre): As DeclarativeActionSet can become the single owner of all
// actions, we can optimize here by making some of them singletons (e.g. Cancel
// actions).
template<typename ActionT>
class DeclarativeActionSet {
 public:
  typedef std::vector<std::unique_ptr<base::Value>> Values;
  typedef std::vector<scoped_refptr<const ActionT> > Actions;

  explicit DeclarativeActionSet(const Actions& actions);

  // Factory method that instantiates a DeclarativeActionSet for |extension|
  // according to |actions| which represents the array of actions received from
  // the extension API.
  static std::unique_ptr<DeclarativeActionSet> Create(
      content::BrowserContext* browser_context,
      const Extension* extension,
      const Values& action_values,
      std::string* error,
      bool* bad_message);

  // Rules call this method when their conditions are fulfilled.
  void Apply(const std::string& extension_id,
             const base::Time& extension_install_time,
             typename ActionT::ApplyInfo* apply_info) const;

  // Rules call this method when their conditions are fulfilled, but Apply has
  // already been called.
  void Reapply(const std::string& extension_id,
               const base::Time& extension_install_time,
               typename ActionT::ApplyInfo* apply_info) const;

  // Rules call this method when they have stateful conditions, and those
  // conditions stop being fulfilled.  Rules with event-based conditions (e.g. a
  // network request happened) will never Revert() an action.
  void Revert(const std::string& extension_id,
              const base::Time& extension_install_time,
              typename ActionT::ApplyInfo* apply_info) const;

  // Returns the minimum priority of rules that may be evaluated after
  // this rule. Defaults to MIN_INT.
  int GetMinimumPriority() const;

  const Actions& actions() const { return actions_; }

 private:
  const Actions actions_;

  DISALLOW_COPY_AND_ASSIGN(DeclarativeActionSet);
};

// Representation of a rule of a declarative API:
// https://developer.chrome.com/beta/extensions/events.html#declarative.
// Generally a RulesRegistry will hold a collection of Rules for a given
// declarative API and contain the logic for matching and applying them.
//
// See DeclarativeConditionSet and DeclarativeActionSet for the requirements on
// ConditionT and ActionT.
template<typename ConditionT, typename ActionT>
class DeclarativeRule {
 public:
  typedef std::string ExtensionId;
  typedef std::string RuleId;
  typedef std::pair<ExtensionId, RuleId> GlobalRuleId;
  typedef int Priority;
  typedef DeclarativeConditionSet<ConditionT> ConditionSet;
  typedef DeclarativeActionSet<ActionT> ActionSet;
  typedef extensions::api::events::Rule JsonRule;
  typedef std::vector<std::string> Tags;

  // Checks whether the set of |conditions| and |actions| are consistent.
  // Returns true in case of consistency and MUST set |error| otherwise.
  typedef base::Callback<bool(const ConditionSet* conditions,
                              const ActionSet* actions,
                              std::string* error)> ConsistencyChecker;

  DeclarativeRule(const GlobalRuleId& id,
                  const Tags& tags,
                  base::Time extension_installation_time,
                  std::unique_ptr<ConditionSet> conditions,
                  std::unique_ptr<ActionSet> actions,
                  Priority priority);

  // Creates a DeclarativeRule for |extension| given a json definition.  The
  // format of each condition and action's json is up to the specific ConditionT
  // and ActionT.  |extension| may be NULL in tests.
  //
  // Before constructing the final rule, calls check_consistency(conditions,
  // actions, error) and returns NULL if it fails.  Pass NULL if no consistency
  // check is needed.  If |error| is empty, the translation was successful and
  // the returned rule is internally consistent.
  static std::unique_ptr<DeclarativeRule> Create(
      url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
      content::BrowserContext* browser_context,
      const Extension* extension,
      base::Time extension_installation_time,
      linked_ptr<JsonRule> rule,
      ConsistencyChecker check_consistency,
      std::string* error);

  const GlobalRuleId& id() const { return id_; }
  const Tags& tags() const { return tags_; }
  const std::string& extension_id() const { return id_.first; }
  const ConditionSet& conditions() const { return *conditions_; }
  const ActionSet& actions() const { return *actions_; }
  Priority priority() const { return priority_; }

  // Calls actions().Apply(extension_id(), extension_installation_time_,
  // apply_info). This function should only be called when the conditions_ are
  // fulfilled (from a semantic point of view; no harm is done if this function
  // is called at other times for testing purposes).
  void Apply(typename ActionT::ApplyInfo* apply_info) const;

  // Returns the minimum priority of rules that may be evaluated after
  // this rule. Defaults to MIN_INT. Only valid if the conditions of this rule
  // are fulfilled.
  Priority GetMinimumPriority() const;

 private:
  GlobalRuleId id_;
  Tags tags_;
  base::Time extension_installation_time_;  // For precedences of rules.
  std::unique_ptr<ConditionSet> conditions_;
  std::unique_ptr<ActionSet> actions_;
  Priority priority_;

  DISALLOW_COPY_AND_ASSIGN(DeclarativeRule);
};

// Implementation details below here.

//
// DeclarativeConditionSet
//

template<typename ConditionT>
bool DeclarativeConditionSet<ConditionT>::IsFulfilled(
    url_matcher::URLMatcherConditionSet::ID url_match_trigger,
    const typename ConditionT::MatchData& match_data) const {
  if (url_match_trigger == -1) {
    // Invalid trigger -- indication that we should only check conditions
    // without URL attributes.
    for (const ConditionT* condition : conditions_without_urls_) {
      if (condition->IsFulfilled(match_data))
        return true;
    }
    return false;
  }

  typename URLMatcherIdToCondition::const_iterator triggered =
      match_id_to_condition_.find(url_match_trigger);
  return (triggered != match_id_to_condition_.end() &&
          triggered->second->IsFulfilled(match_data));
}

template<typename ConditionT>
void DeclarativeConditionSet<ConditionT>::GetURLMatcherConditionSets(
    url_matcher::URLMatcherConditionSet::Vector* condition_sets) const {
  for (const linked_ptr<const ConditionT>& condition : conditions_)
    condition->GetURLMatcherConditionSets(condition_sets);
}

// static
template <typename ConditionT>
std::unique_ptr<DeclarativeConditionSet<ConditionT>>
DeclarativeConditionSet<ConditionT>::Create(
    const Extension* extension,
    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
    const Values& condition_values,
    std::string* error) {
  Conditions result;

  for (const std::unique_ptr<base::Value>& value : condition_values) {
    CHECK(value.get());
    std::unique_ptr<ConditionT> condition = ConditionT::Create(
        extension, url_matcher_condition_factory, *value, error);
    if (!error->empty())
      return std::unique_ptr<DeclarativeConditionSet>();
    result.push_back(make_linked_ptr(condition.release()));
  }

  URLMatcherIdToCondition match_id_to_condition;
  std::vector<const ConditionT*> conditions_without_urls;
  url_matcher::URLMatcherConditionSet::Vector condition_sets;

  for (const linked_ptr<const ConditionT>& condition : result) {
    condition_sets.clear();
    condition->GetURLMatcherConditionSets(&condition_sets);
    if (condition_sets.empty()) {
      conditions_without_urls.push_back(condition.get());
    } else {
      for (const scoped_refptr<url_matcher::URLMatcherConditionSet>& match_set :
           condition_sets)
        match_id_to_condition[match_set->id()] = condition.get();
    }
  }

  return base::WrapUnique(new DeclarativeConditionSet(
      result, match_id_to_condition, conditions_without_urls));
}

template<typename ConditionT>
DeclarativeConditionSet<ConditionT>::DeclarativeConditionSet(
    const Conditions& conditions,
    const URLMatcherIdToCondition& match_id_to_condition,
    const std::vector<const ConditionT*>& conditions_without_urls)
    : match_id_to_condition_(match_id_to_condition),
      conditions_(conditions),
      conditions_without_urls_(conditions_without_urls) {}

//
// DeclarativeActionSet
//

template<typename ActionT>
DeclarativeActionSet<ActionT>::DeclarativeActionSet(const Actions& actions)
    : actions_(actions) {}

// static
template <typename ActionT>
std::unique_ptr<DeclarativeActionSet<ActionT>>
DeclarativeActionSet<ActionT>::Create(content::BrowserContext* browser_context,
                                      const Extension* extension,
                                      const Values& action_values,
                                      std::string* error,
                                      bool* bad_message) {
  *error = "";
  *bad_message = false;
  Actions result;

  for (const std::unique_ptr<base::Value>& value : action_values) {
    CHECK(value.get());
    scoped_refptr<const ActionT> action =
        ActionT::Create(browser_context, extension, *value, error, bad_message);
    if (!error->empty() || *bad_message)
      return std::unique_ptr<DeclarativeActionSet>();
    result.push_back(action);
  }

  return std::unique_ptr<DeclarativeActionSet>(
      new DeclarativeActionSet(result));
}

template<typename ActionT>
void DeclarativeActionSet<ActionT>::Apply(
    const std::string& extension_id,
    const base::Time& extension_install_time,
    typename ActionT::ApplyInfo* apply_info) const {
  for (const scoped_refptr<const ActionT>& action : actions_)
    action->Apply(extension_id, extension_install_time, apply_info);
}

template<typename ActionT>
void DeclarativeActionSet<ActionT>::Reapply(
    const std::string& extension_id,
    const base::Time& extension_install_time,
    typename ActionT::ApplyInfo* apply_info) const {
  for (const scoped_refptr<const ActionT>& action : actions_)
    action->Reapply(extension_id, extension_install_time, apply_info);
}

template<typename ActionT>
void DeclarativeActionSet<ActionT>::Revert(
    const std::string& extension_id,
    const base::Time& extension_install_time,
    typename ActionT::ApplyInfo* apply_info) const {
  for (const scoped_refptr<const ActionT>& action : actions_)
    action->Revert(extension_id, extension_install_time, apply_info);
}

template<typename ActionT>
int DeclarativeActionSet<ActionT>::GetMinimumPriority() const {
  int minimum_priority = std::numeric_limits<int>::min();
  for (typename Actions::const_iterator i = actions_.begin();
       i != actions_.end(); ++i) {
    minimum_priority = std::max(minimum_priority, (*i)->minimum_priority());
  }
  return minimum_priority;
}

//
// DeclarativeRule
//

template <typename ConditionT, typename ActionT>
DeclarativeRule<ConditionT, ActionT>::DeclarativeRule(
    const GlobalRuleId& id,
    const Tags& tags,
    base::Time extension_installation_time,
    std::unique_ptr<ConditionSet> conditions,
    std::unique_ptr<ActionSet> actions,
    Priority priority)
    : id_(id),
      tags_(tags),
      extension_installation_time_(extension_installation_time),
      conditions_(conditions.release()),
      actions_(actions.release()),
      priority_(priority) {
  CHECK(conditions_.get());
  CHECK(actions_.get());
}

// static
template <typename ConditionT, typename ActionT>
std::unique_ptr<DeclarativeRule<ConditionT, ActionT>>
DeclarativeRule<ConditionT, ActionT>::Create(
    url_matcher::URLMatcherConditionFactory* url_matcher_condition_factory,
    content::BrowserContext* browser_context,
    const Extension* extension,
    base::Time extension_installation_time,
    linked_ptr<JsonRule> rule,
    ConsistencyChecker check_consistency,
    std::string* error) {
  std::unique_ptr<DeclarativeRule> error_result;

  std::unique_ptr<ConditionSet> conditions = ConditionSet::Create(
      extension, url_matcher_condition_factory, rule->conditions, error);
  if (!error->empty())
    return std::move(error_result);
  CHECK(conditions.get());

  bool bad_message = false;
  std::unique_ptr<ActionSet> actions = ActionSet::Create(
      browser_context, extension, rule->actions, error, &bad_message);
  if (bad_message) {
    // TODO(battre) Export concept of bad_message to caller, the extension
    // should be killed in case it is true.
    *error = "An action of a rule set had an invalid "
        "structure that should have been caught by the JSON validator.";
    return std::move(error_result);
  }
  if (!error->empty() || bad_message)
    return std::move(error_result);
  CHECK(actions.get());

  if (!check_consistency.is_null() &&
      !check_consistency.Run(conditions.get(), actions.get(), error)) {
    DCHECK(!error->empty());
    return std::move(error_result);
  }

  CHECK(rule->priority.get());
  int priority = *(rule->priority);

  GlobalRuleId rule_id(extension->id(), *(rule->id));
  Tags tags = rule->tags ? *rule->tags : Tags();
  return std::unique_ptr<DeclarativeRule>(
      new DeclarativeRule(rule_id, tags, extension_installation_time,
                          std::move(conditions), std::move(actions), priority));
}

template<typename ConditionT, typename ActionT>
void DeclarativeRule<ConditionT, ActionT>::Apply(
    typename ActionT::ApplyInfo* apply_info) const {
  return actions_->Apply(extension_id(),
                         extension_installation_time_,
                         apply_info);
}

template<typename ConditionT, typename ActionT>
int DeclarativeRule<ConditionT, ActionT>::GetMinimumPriority() const {
  return actions_->GetMinimumPriority();
}

}  // namespace extensions

#endif  // EXTENSIONS_BROWSER_API_DECLARATIVE_DECLARATIVE_RULE_H__
