/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */


#ifndef PAGESPEED_SYSTEM_SYSTEM_REWRITE_OPTIONS_H_
#define PAGESPEED_SYSTEM_SYSTEM_REWRITE_OPTIONS_H_

#include <set>

#include "net/instaweb/rewriter/public/rewrite_options.h"
#include "net/instaweb/rewriter/static_asset_config.pb.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/fast_wildcard_group.h"
#include "pagespeed/kernel/base/hasher.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_system.h"
#include "pagespeed/kernel/http/google_url.h"
#include "pagespeed/kernel/util/copy_on_write.h"
#include "pagespeed/system/external_server_spec.h"

namespace net_instaweb {

class MessageHandler;

// This manages configuration options specific to server implementations of
// pagespeed optimization libraries, such as mod_pagespeed and ngx_pagespeed.
class SystemRewriteOptions : public RewriteOptions {
 public:
  typedef std::set<StaticAssetEnum::StaticAsset> StaticAssetSet;

  static const char kCentralControllerPort[];
  static const char kPopularityContestMaxInFlight[];
  static const char kPopularityContestMaxQueueSize[];
  static const char kStaticAssetCDN[];
  static const char kRedisServer[];
  static const char kRedisReconnectionDelayMs[];
  static const char kRedisTimeoutUs[];
  static const char kRedisDatabaseIndex[];
  static const char kRedisTTLSec[];

  static constexpr int kMemcachedDefaultPort = 11211;
  static constexpr int kRedisDefaultPort = 6379;

  static void Initialize();
  static void Terminate();

  SystemRewriteOptions(const StringPiece& description,
                       ThreadSystem* thread_system);
  explicit SystemRewriteOptions(ThreadSystem* thread_system);
  virtual ~SystemRewriteOptions();

  virtual void Merge(const RewriteOptions& src);

  virtual OptionSettingResult ParseAndSetOptionFromName2(
      StringPiece name, StringPiece arg1, StringPiece arg2,
      GoogleString* msg, MessageHandler* handler);

  virtual GoogleString SubclassSignatureLockHeld();

  int64 file_cache_clean_interval_ms() const {
    return file_cache_clean_interval_ms_.value();
  }
  bool has_file_cache_clean_interval_ms() const {
    return file_cache_clean_interval_ms_.was_set();
  }
  void set_file_cache_clean_interval_ms(int64 x) {
    set_option(x, &file_cache_clean_interval_ms_);
  }
  int64 file_cache_clean_size_kb() const {
    return file_cache_clean_size_kb_.value();
  }
  bool has_file_cache_clean_size_kb() const {
    return file_cache_clean_size_kb_.was_set();
  }
  void set_file_cache_clean_size_kb(int64 x) {
    set_option(x, &file_cache_clean_size_kb_);
  }
  int64 file_cache_clean_inode_limit() const {
    return file_cache_clean_inode_limit_.value();
  }
  bool has_file_cache_clean_inode_limit() const {
    return file_cache_clean_inode_limit_.was_set();
  }
  void set_file_cache_clean_inode_limit(int64 x) {
    set_option(x, &file_cache_clean_inode_limit_);
  }
  int64 lru_cache_byte_limit() const {
    return lru_cache_byte_limit_.value();
  }
  void set_lru_cache_byte_limit(int64 x) {
    set_option(x, &lru_cache_byte_limit_);
  }
  int64 lru_cache_kb_per_process() const {
    return lru_cache_kb_per_process_.value();
  }
  void set_lru_cache_kb_per_process(int64 x) {
    set_option(x, &lru_cache_kb_per_process_);
  }
  bool use_shared_mem_locking() const {
    return use_shared_mem_locking_.value();
  }
  void set_use_shared_mem_locking(bool x) {
    set_option(x, &use_shared_mem_locking_);
  }
  bool compress_metadata_cache() const {
    return compress_metadata_cache_.value();
  }
  void set_compress_metadata_cache(bool x) {
    set_option(x, &compress_metadata_cache_);
  }
  bool statistics_enabled() const {
    return statistics_enabled_.value();
  }
  void set_statistics_enabled(bool x) {
    set_option(x, &statistics_enabled_);
  }
  bool statistics_logging_enabled() const {
    return statistics_logging_enabled_.value();
  }
  void set_statistics_logging_enabled(bool x) {
    set_option(x, &statistics_logging_enabled_);
  }
  int64 statistics_logging_max_file_size_kb() const {
    return statistics_logging_max_file_size_kb_.value();
  }
  const GoogleString& statistics_logging_charts_css() const {
    return statistics_logging_charts_css_.value();
  }
  const GoogleString& statistics_logging_charts_js() const {
    return statistics_logging_charts_js_.value();
  }
  int64 statistics_logging_interval_ms() const {
    return statistics_logging_interval_ms_.value();
  }
  void set_statistics_logging_interval_ms(int64 x) {
    set_option(x, &statistics_logging_interval_ms_);
  }
  const GoogleString& file_cache_path() const {
    return file_cache_path_.value();
  }
  void set_file_cache_path(const GoogleString& x) {
    set_option(x, &file_cache_path_);
  }
  const GoogleString& log_dir() const { return log_dir_.value(); }
  void set_log_dir(const GoogleString& x) { set_option(x, &log_dir_); }
  const ExternalClusterSpec& memcached_servers() const {
    return memcached_servers_.value();
  }
  void set_memcached_servers(const ExternalClusterSpec& x) {
    set_option(x, &memcached_servers_);
  }
  int memcached_threads() const {
    return memcached_threads_.value();
  }
  void set_memcached_threads(int x) {
    set_option(x, &memcached_threads_);
  }
  int memcached_timeout_us() const {
    return memcached_timeout_us_.value();
  }
  bool has_memcached_timeout_us() const {
    return memcached_timeout_us_.was_set();
  }
  void set_memcached_timeout_us(int x) {
    set_option(x, &memcached_timeout_us_);
  }
  const ExternalServerSpec& redis_server() const {
    return redis_server_.value();
  }
  void set_redis_server(const ExternalServerSpec& x) {
    set_option(x, &redis_server_);
  }
  int64 redis_reconnection_delay_ms() const {
    return redis_reconnection_delay_ms_.value();
  }
  int64 redis_timeout_us() const {
    return redis_timeout_us_.value();
  }
  int redis_database_index() const {
    return redis_database_index_.value();
  }
  bool has_redis_database_index() const {
    return redis_database_index_.was_set();
  }
  int redis_ttl_sec() const {
    return redis_ttl_sec_.value();
  }
  bool has_redis_ttl_sec() const {
    return redis_ttl_sec_.was_set();
  }
  int64 slow_file_latency_threshold_us() const {
    return slow_file_latency_threshold_us_.value();
  }
  bool has_slow_file_latency_threshold_us() const {
    return slow_file_latency_threshold_us_.was_set();
  }
  void set_slow_file_latency_threshold_us(int64 x) {
    set_option(x, &slow_file_latency_threshold_us_);
  }
  const GoogleString& fetcher_proxy() const {
    return fetcher_proxy_.value();
  }
  void set_fetcher_proxy(const GoogleString& x) {
    set_option(x, &fetcher_proxy_);
  }

  const GoogleString& controller_port() const {
    return controller_port_.value();
  }
  int popularity_contest_max_inflight_requests() const {
    return popularity_contest_max_inflight_requests_.value();
  }
  int popularity_contest_max_queue_size() const {
    return popularity_contest_max_queue_size_.value();
  }

  // Cache flushing configuration.
  void set_cache_flush_poll_interval_sec(int64 num_seconds) {
    set_option(num_seconds, &cache_flush_poll_interval_sec_);
  }
  int64 cache_flush_poll_interval_sec() const {
    return cache_flush_poll_interval_sec_.value();
  }
  void set_cache_flush_filename(const StringPiece& sp) {
    set_option(sp.as_string(), &cache_flush_filename_);
  }
  const GoogleString& cache_flush_filename() const {
    return cache_flush_filename_.value();
  }

  const GoogleString& https_options() const {
    return https_options_.value();
  }
  const GoogleString& ssl_cert_directory() const {
    return ssl_cert_directory_.value();
  }
  const GoogleString& ssl_cert_file() const {
    return ssl_cert_file_.value();
  }

  int64 slurp_flush_limit() const {
    return slurp_flush_limit_.value();
  }
  void set_slurp_flush_limit(int64 x) {
    set_option(x, &slurp_flush_limit_);
  }
  bool slurp_read_only() const {
    return slurp_read_only_.value();
  }
  void set_slurp_read_only(bool x) {
    set_option(x, &slurp_read_only_);
  }
  bool rate_limit_background_fetches() const {
    return rate_limit_background_fetches_.value();
  }
  const GoogleString& slurp_directory() const {
    return slurp_directory_.value();
  }
  void set_slurp_directory(GoogleString x) {
    set_option(x, &slurp_directory_);
  }
  bool disable_loopback_routing() const {
    return disable_loopback_routing_.value();
  }
  bool fetch_with_gzip() const {
    return fetch_with_gzip_.value();
  }
  int64 ipro_max_response_bytes() const {
    return ipro_max_response_bytes_.value();
  }
  int64 ipro_max_concurrent_recordings() const {
    return ipro_max_concurrent_recordings_.value();
  }
  int64 default_shared_memory_cache_kb() const {
    return default_shared_memory_cache_kb_.value();
  }
  void set_default_shared_memory_cache_kb(int64 x) {
    set_option(x, &default_shared_memory_cache_kb_);
  }
  int shm_metadata_cache_checkpoint_interval_sec() const {
    return shm_metadata_cache_checkpoint_interval_sec_.value();
  }
  void set_purge_method(const GoogleString& x) {
    set_option(x, &purge_method_);
  }
  const GoogleString& purge_method() const { return purge_method_.value(); }

  bool AllowDomain(const GoogleUrl& url,
                   const FastWildcardGroup& wildcard_group) const;

  // For each of these AccessAllowed() methods, the url needs to have been
  // checked to make sure it is_valid().
  bool StatisticsAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *statistics_domains_);
  }
  bool GlobalStatisticsAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *global_statistics_domains_);
  }
  bool MessagesAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *messages_domains_);
  }
  bool ConsoleAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *console_domains_);
  }
  bool AdminAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *admin_domains_);
  }
  bool GlobalAdminAccessAllowed(const GoogleUrl& url) const {
    return AllowDomain(url, *global_admin_domains_);
  }

  // If this is set to true, we'll turn on our fallback proxy-like behavior
  // on non-.pagespeed. URLs without changing the main fetcher from Serf
  // (the way the slurp options would).
  bool test_proxy() const {
    return test_proxy_.value();
  }
  void set_test_proxy(bool x) {
    set_option(x, &test_proxy_);
  }

  // Returns true if we were asked to configure StaticAssetManager to
  // serve static assets that are usually compiled in from an external
  // base URL.
  bool has_static_assets_to_cdn() const {
    return static_assets_to_cdn_.was_set();
  }

  // Particular assets to serve of an external URL.
  const StaticAssetSet& static_assets_to_cdn() const {
    return static_assets_to_cdn_.asset_set();
  }

  // Base URL to serve them from.
  const GoogleString& static_assets_cdn_base() const {
    return static_assets_to_cdn_.value();
  }

  // Fills in an options proto based on the CDN settings passed above.
  void FillInStaticAssetCDNConf(StaticAssetConfig* out_conf) const;

  // This configures the fetcher we use for fallback handling if test_proxy()
  // is on:
  //  - If this is empty, we use the usual fetcher (e.g. Serf)
  //  - If it's non-empty, the fallback URLs will be fetched from the given
  //    slurp directory.  PageSpeed resource fetches, however, will still
  //    use the usual fetcher (e.g. Serf).
  GoogleString test_proxy_slurp() const {
    return test_proxy_slurp_.value();
  }

  // Helper functions
  bool slurping_enabled() const {
    return !slurp_directory().empty();
  }

  bool slurping_enabled_read_only() const {
    return slurping_enabled() && slurp_read_only();
  }

  virtual SystemRewriteOptions* Clone() const;
  virtual SystemRewriteOptions* NewOptions() const;

  // Returns a suitably down cast version of 'instance' if it is an instance
  // of this class, NULL if not.
  static const SystemRewriteOptions* DynamicCast(
      const RewriteOptions* instance);
  static SystemRewriteOptions* DynamicCast(RewriteOptions* instance);

  // Human-readable description of what this configuration is for.  This
  // may be a directory, or a string indicating a combination of directives
  // for multiple directories.  Should only be used for debugging.
  StringPiece description() const { return description_; }
  void set_description(const StringPiece& x) { x.CopyToString(&description_); }

 private:
  // We have some special parsing error-checking requirements for
  // FetchHttps
  class HttpsOptions : public Option<GoogleString> {
   public:
    virtual bool SetFromString(StringPiece value_string,
                               GoogleString* error_detail);
  };

  class StaticAssetCDNOptions : public OptionTemplateBase<GoogleString> {
   public:
    virtual bool SetFromString(StringPiece value_string,
                               GoogleString* error_detail);

    virtual GoogleString Signature(const Hasher* hasher) const;
    virtual GoogleString ToString() const;
    virtual void Merge(const OptionBase* src);

    // value() here is just the base path.
    const StaticAssetSet& asset_set() const {
      return *static_assets_to_cdn_.get();
    }

   private:
    // The string is the base path,
    CopyOnWrite<StaticAssetSet> static_assets_to_cdn_;
  };

  template<typename Spec, int default_port>
  class ExternalServersOption : public OptionTemplateBase<Spec> {
   public:
    bool SetFromString(StringPiece value_string,
                       GoogleString* error_detail) override {
      return this->mutable_value().SetFromString(value_string, default_port,
                                                 error_detail);
    }
    GoogleString ToString() const override {
      return this->value().ToString();
    }
    GoogleString Signature(const Hasher* hasher) const override {
      return hasher->Hash(ToString());
    }
  };

  class ControllerPortOption : public Option<GoogleString> {
   public:
    bool SetFromString(StringPiece value_string,
                       GoogleString* error_detail) override;
  };

  // Keeps the properties added by this subclass.  These are merged into
  // RewriteOptions::all_properties_ during Initialize().
  static Properties* system_properties_;

  // Adds an option to system_properties_.
  //
  template<class OptionClass>
  static void AddSystemProperty(typename OptionClass::ValueType default_value,
                                OptionClass SystemRewriteOptions::*offset,
                                const char* id,
                                StringPiece option_name,
                                const char* help,
                                bool safe_to_print) {
    AddProperty(default_value, offset, id, option_name, kServerScope, help,
                safe_to_print, system_properties_);
  }

  template<class OptionClass>
  static void AddSystemProperty(typename OptionClass::ValueType default_value,
                                OptionClass SystemRewriteOptions::*offset,
                                const char* id,
                                StringPiece option_name,
                                OptionScope scope,
                                const char* help,
                                bool safe_to_print) {
    AddProperty(default_value, offset, id, option_name, scope, help,
                safe_to_print, system_properties_);
  }

  static void AddProperties();

  // Debug string useful in understanding config merges.
  GoogleString description_;

  Option<GoogleString> fetcher_proxy_;
  Option<GoogleString> file_cache_path_;
  Option<GoogleString> log_dir_;

  ExternalServersOption<ExternalClusterSpec, kMemcachedDefaultPort>
      memcached_servers_;
  ExternalServersOption<ExternalServerSpec, kRedisDefaultPort> redis_server_;
  Option<GoogleString> statistics_logging_charts_css_;
  Option<GoogleString> statistics_logging_charts_js_;
  Option<GoogleString> cache_flush_filename_;
  Option<GoogleString> ssl_cert_directory_;
  Option<GoogleString> ssl_cert_file_;
  HttpsOptions https_options_;

  Option<GoogleString> slurp_directory_;
  Option<GoogleString> test_proxy_slurp_;

  Option<bool> statistics_enabled_;
  Option<bool> statistics_logging_enabled_;
  Option<bool> use_shared_mem_locking_;
  Option<bool> compress_metadata_cache_;

  Option<bool> slurp_read_only_;
  Option<bool> test_proxy_;
  Option<bool> rate_limit_background_fetches_;

  // If false (default) we will redirect all fetches to unknown hosts to
  // localhost.
  Option<bool> disable_loopback_routing_;

  // Makes fetches from PSA to origin-server request
  // accept-encoding:gzip, even when used in a context when we want
  // cleartext.  We'll decompress as we read the content if needed.
  Option<bool> fetch_with_gzip_;

  ControllerPortOption controller_port_;
  Option<int> popularity_contest_max_inflight_requests_;
  Option<int> popularity_contest_max_queue_size_;

  Option<int> memcached_threads_;
  Option<int> memcached_timeout_us_;
  Option<int64> redis_reconnection_delay_ms_;
  Option<int64> redis_timeout_us_;
  Option<int> redis_database_index_;
  Option<int> redis_ttl_sec_;

  Option<int64> slow_file_latency_threshold_us_;
  Option<int64> file_cache_clean_inode_limit_;
  Option<int64> file_cache_clean_interval_ms_;
  Option<int64> file_cache_clean_size_kb_;
  Option<int64> lru_cache_byte_limit_;
  Option<int64> lru_cache_kb_per_process_;
  Option<int64> statistics_logging_interval_ms_;
  // If cache_flush_poll_interval_sec_<=0 then we turn off polling for
  // cache-flushes.
  Option<int64> cache_flush_poll_interval_sec_;
  Option<int64> statistics_logging_max_file_size_kb_;
  Option<int64> slurp_flush_limit_;
  Option<int64> ipro_max_response_bytes_;
  Option<int64> ipro_max_concurrent_recordings_;
  Option<int64> default_shared_memory_cache_kb_;
  Option<int> shm_metadata_cache_checkpoint_interval_sec_;
  Option<GoogleString> purge_method_;

  StaticAssetCDNOptions static_assets_to_cdn_;

  CopyOnWrite<FastWildcardGroup> statistics_domains_;
  CopyOnWrite<FastWildcardGroup> global_statistics_domains_;
  CopyOnWrite<FastWildcardGroup> messages_domains_;
  CopyOnWrite<FastWildcardGroup> console_domains_;
  CopyOnWrite<FastWildcardGroup> admin_domains_;
  CopyOnWrite<FastWildcardGroup> global_admin_domains_;

  DISALLOW_COPY_AND_ASSIGN(SystemRewriteOptions);
};

}  // namespace net_instaweb

#endif  // PAGESPEED_SYSTEM_SYSTEM_REWRITE_OPTIONS_H_
