// Copyright (c) 2012 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.

#ifndef CONTENT_BROWSER_GPU_GPU_BLACKLIST_H_
#define CONTENT_BROWSER_GPU_GPU_BLACKLIST_H_

#include <string>
#include <vector>

#include "base/basictypes.h"
#include "base/gtest_prod_util.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/common/content_export.h"
#include "content/public/common/gpu_feature_type.h"
#include "content/public/common/gpu_switching_option.h"

namespace content {
struct GPUInfo;

class CONTENT_EXPORT GpuBlacklist {
 public:
  enum OsType {
    kOsLinux,
    kOsMacosx,
    kOsWin,
    kOsChromeOS,
    kOsAndroid,
    kOsAny,
    kOsUnknown
  };

  enum OsFilter {
    // In loading, ignore all entries that belong to other OS.
    kCurrentOsOnly,
    // In loading, keep all entries. This is for testing only.
    kAllOs
  };

  struct Decision {
    GpuFeatureType blacklisted_features;
    GpuSwitchingOption gpu_switching;

    Decision()
        : blacklisted_features(GPU_FEATURE_TYPE_UNKNOWN),
          gpu_switching(GPU_SWITCHING_OPTION_UNKNOWN) {
    }
  };

  GpuBlacklist();
  virtual ~GpuBlacklist();

  // Loads blacklist information from a json file.
  // If failed, the current GpuBlacklist is un-touched.
  bool LoadGpuBlacklist(const std::string& json_context, OsFilter os_filter);
  bool LoadGpuBlacklist(const std::string& browser_version_string,
                        const std::string& json_context,
                        OsFilter os_filter);

  // Collects system information and combines them with gpu_info and blacklist
  // information to make the blacklist decision.
  // If os is kOsAny, use the current OS; if os_version is empty, use the
  // current OS version.
  Decision MakeBlacklistDecision(
      OsType os, std::string os_version, const GPUInfo& gpu_info);

  // Collects the active entries from the last MakeBlacklistDecision() call.
  // If disabled set to true, return entries that are disabled; otherwise,
  // return enabled entries.
  void GetDecisionEntries(std::vector<uint32>* entry_ids,
                          bool disabled) const;

  // Returns the description and bugs from active entries from the last
  // MakeBlacklistDecision() call.
  //
  // Each problems has:
  // {
  //    "description": "Your GPU is too old",
  //    "crBugs": [1234],
  //    "webkitBugs": []
  // }
  void GetBlacklistReasons(base::ListValue* problem_list) const;

  // Return the largest entry id.  This is used for histogramming.
  uint32 max_entry_id() const;

  // Returns the version of the current blacklist.
  std::string GetVersion() const;

  // Check if we needs more gpu info to make the blacklisting decisions.
  // This is computed from the last MakeBlacklistDecision() call.
  // If yes, we should create a gl context and do a full gpu info collection.
  bool needs_more_info() const { return needs_more_info_; }

 private:
  friend class GpuBlacklistTest;
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, ChromeVersionEntry);
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, CurrentBlacklistValidation);
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, DualGpuModel);
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, UnknownExceptionField);
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, UnknownFeature);
  FRIEND_TEST_ALL_PREFIXES(GpuBlacklistTest, UnknownField);

  enum BrowserVersionSupport {
    kSupported,
    kUnsupported,
    kMalformed
  };

  enum NumericOp {
    kBetween,  // <= * <=
    kEQ,  // =
    kLT,  // <
    kLE,  // <=
    kGT,  // >
    kGE,  // >=
    kAny,
    kUnknown  // Indicates the data is invalid.
  };

  class VersionInfo {
   public:
    // If version_style is empty, it defaults to kNumerical.
    VersionInfo(const std::string& version_op,
                const std::string& version_style,
                const std::string& version_string,
                const std::string& version_string2);
    ~VersionInfo();

    // Determines if a given version is included in the VersionInfo range.
    // "splitter" divides version string into segments.
    bool Contains(const std::string& version, char splitter) const;
    // Same as above, using '.' as splitter.
    bool Contains(const std::string& version) const;

    // Determine if the version_style is lexical.
    bool IsLexical() const;

    // Determines if the VersionInfo contains valid information.
    bool IsValid() const;

   private:
    enum VersionStyle {
      kVersionStyleNumerical,
      kVersionStyleLexical,
      kVersionStyleUnknown
    };

    static VersionStyle StringToVersionStyle(const std::string& version_style);

    // Compare two version strings.
    // Return 1 if version > version_ref,
    //        0 if version = version_ref,
    //       -1 if version < version_ref.
    // Note that we only compare as many as segments as version_ref contains.
    // If version_ref is xxx.yyy, it's considered as xxx.yyy.*
    // For example: Compare("10.3.1", "10.3") returns 0,
    //              Compare("10.3", "10.3.1") returns -1.
    // If "version_style" is Lexical, the first segment is compared
    // numerically, all other segments are compared lexically.
    // Lexical is used for AMD Linux driver versions only.
    static int Compare(const std::vector<std::string>& version,
                       const std::vector<std::string>& version_ref,
                       VersionStyle version_style);

    NumericOp op_;
    VersionStyle version_style_;
    std::vector<std::string> version_;
    std::vector<std::string> version2_;
  };

  class OsInfo {
   public:
    OsInfo(const std::string& os,
           const std::string& version_op,
           const std::string& version_string,
           const std::string& version_string2);
    ~OsInfo();

    // Determines if a given os/version is included in the OsInfo set.
    bool Contains(OsType type, const std::string& version) const;

    // Determines if the VersionInfo contains valid information.
    bool IsValid() const;

    OsType type() const;

    // Maps string to OsType; returns kOsUnknown if it's not a valid os.
    static OsType StringToOsType(const std::string& os);

   private:
    OsType type_;
    scoped_ptr<VersionInfo> version_info_;
  };

  class StringInfo {
   public:
    StringInfo(const std::string& string_op, const std::string& string_value);

    // Determines if a given string is included in the StringInfo.
    bool Contains(const std::string& value) const;

    // Determines if the StringInfo contains valid information.
    bool IsValid() const;

   private:
    enum Op {
      kContains,
      kBeginWith,
      kEndWith,
      kEQ,  // =
      kUnknown  // Indicates StringInfo data is invalid.
    };

    // Maps string to Op; returns kUnknown if it's not a valid Op.
    static Op StringToOp(const std::string& string_op);

    Op op_;
    std::string value_;
  };

  class FloatInfo {
   public:
    FloatInfo(const std::string& float_op,
              const std::string& float_value,
              const std::string& float_value2);

    // Determines if a given float is included in the FloatInfo.
    bool Contains(float value) const;

    // Determines if the FloatInfo contains valid information.
    bool IsValid() const;

   private:
    NumericOp op_;
    float value_;
    float value2_;
  };

  class IntInfo {
   public:
    IntInfo(const std::string& int_op,
            const std::string& int_value,
            const std::string& int_value2);

    // Determines if a given int is included in the IntInfo.
    bool Contains(int value) const;

    // Determines if the IntInfo contains valid information.
    bool IsValid() const;

   private:
    NumericOp op_;
    int value_;
    int value2_;
  };

  class MachineModelInfo {
   public:
    MachineModelInfo(const std::string& name_op,
                     const std::string& name_value,
                     const std::string& version_op,
                     const std::string& version_string,
                     const std::string& version_string2);
    ~MachineModelInfo();

    // Determines if a given name/version is included in the MachineModelInfo.
    bool Contains(const std::string& name, const std::string& version) const;

    // Determines if the MachineModelInfo contains valid information.
    bool IsValid() const;

   private:
    scoped_ptr<StringInfo> name_info_;
    scoped_ptr<VersionInfo> version_info_;
  };

  class GpuBlacklistEntry;
  typedef scoped_refptr<GpuBlacklistEntry> ScopedGpuBlacklistEntry;

  class GpuBlacklistEntry : public base::RefCounted<GpuBlacklistEntry> {
   public:
    // Constructs GpuBlacklistEntry from DictionaryValue loaded from json.
    // Top-level entry must have an id number.  Others are exceptions.
    static ScopedGpuBlacklistEntry GetGpuBlacklistEntryFromValue(
        const base::DictionaryValue* value, bool top_level);

    // Determines if a given os/gc/machine_model/driver is included in the
    // Entry set.
    bool Contains(OsType os_type, const std::string& os_version,
                  const GPUInfo& gpu_info) const;

    // Determines whether we needs more gpu info to make the blacklisting
    // decision.  It should only be checked if Contains() returns true.
    bool NeedsMoreInfo(const GPUInfo& gpu_info) const;

    // Returns the OsType.
    OsType GetOsType() const;

    // Returns the entry's unique id.  0 is reserved.
    uint32 id() const;

    // Returns whether the entry is disabled.
    bool disabled() const;

    // Returns the description of the entry
    const std::string& description() const { return description_; }

    // Returns a list of Chromium and Webkit bugs applicable to this entry
    const std::vector<int>& cr_bugs() const { return cr_bugs_; }
    const std::vector<int>& webkit_bugs() const { return webkit_bugs_; }

    // Returns the GpuFeatureType.
    GpuFeatureType GetGpuFeatureType() const;

    // Returns the GpuSwitchingOption.
    GpuSwitchingOption GetGpuSwitchingOption() const;

    // Returns true if an unknown field is encountered.
    bool contains_unknown_fields() const {
      return contains_unknown_fields_;
    }
    // Returns true if an unknown blacklist feature is encountered.
    bool contains_unknown_features() const {
      return contains_unknown_features_;
    }

   private:
    friend class base::RefCounted<GpuBlacklistEntry>;

    enum MultiGpuStyle {
      kMultiGpuStyleOptimus,
      kMultiGpuStyleAMDSwitchable,
      kMultiGpuStyleNone
    };

    enum MultiGpuCategory {
      kMultiGpuCategoryPrimary,
      kMultiGpuCategorySecondary,
      kMultiGpuCategoryAny,
      kMultiGpuCategoryNone
    };

    GpuBlacklistEntry();
    ~GpuBlacklistEntry();

    bool SetId(uint32 id);

    void SetDisabled(bool disabled);

    bool SetOsInfo(const std::string& os,
                   const std::string& version_op,
                   const std::string& version_string,
                   const std::string& version_string2);

    bool SetVendorId(const std::string& vendor_id_string);

    bool AddDeviceId(const std::string& device_id_string);

    bool SetMultiGpuStyle(const std::string& multi_gpu_style_string);

    bool SetMultiGpuCategory(const std::string& multi_gpu_category_string);

    bool SetDriverVendorInfo(const std::string& vendor_op,
                             const std::string& vendor_value);

    bool SetDriverVersionInfo(const std::string& version_op,
                              const std::string& version_style,
                              const std::string& version_string,
                              const std::string& version_string2);

    bool SetDriverDateInfo(const std::string& date_op,
                           const std::string& date_string,
                           const std::string& date_string2);

    bool SetGLVendorInfo(const std::string& vendor_op,
                         const std::string& vendor_value);

    bool SetGLRendererInfo(const std::string& renderer_op,
                           const std::string& renderer_value);

    bool SetCpuBrand(const std::string& cpu_op,
                     const std::string& cpu_value);

    bool SetPerfGraphicsInfo(const std::string& op,
                             const std::string& float_string,
                             const std::string& float_string2);

    bool SetPerfGamingInfo(const std::string& op,
                           const std::string& float_string,
                           const std::string& float_string2);

    bool SetPerfOverallInfo(const std::string& op,
                            const std::string& float_string,
                            const std::string& float_string2);

    bool SetMachineModelInfo(const std::string& name_op,
                             const std::string& name_value,
                             const std::string& version_op,
                             const std::string& version_string,
                             const std::string& version_string2);

    bool SetGpuCountInfo(const std::string& op,
                         const std::string& int_string,
                         const std::string& int_string2);

    bool SetBlacklistedFeatures(
        const std::vector<std::string>& blacklisted_features);

    bool SetGpuSwitchingOption(const std::string& switching_string);

    void AddException(ScopedGpuBlacklistEntry exception);

    static MultiGpuStyle StringToMultiGpuStyle(const std::string& style);

    static MultiGpuCategory StringToMultiGpuCategory(
        const std::string& category);

    uint32 id_;
    bool disabled_;
    std::string description_;
    std::vector<int> cr_bugs_;
    std::vector<int> webkit_bugs_;
    scoped_ptr<OsInfo> os_info_;
    uint32 vendor_id_;
    std::vector<uint32> device_id_list_;
    MultiGpuStyle multi_gpu_style_;
    MultiGpuCategory multi_gpu_category_;
    scoped_ptr<StringInfo> driver_vendor_info_;
    scoped_ptr<VersionInfo> driver_version_info_;
    scoped_ptr<VersionInfo> driver_date_info_;
    scoped_ptr<StringInfo> gl_vendor_info_;
    scoped_ptr<StringInfo> gl_renderer_info_;
    scoped_ptr<StringInfo> cpu_brand_;
    scoped_ptr<FloatInfo> perf_graphics_info_;
    scoped_ptr<FloatInfo> perf_gaming_info_;
    scoped_ptr<FloatInfo> perf_overall_info_;
    scoped_ptr<MachineModelInfo> machine_model_info_;
    scoped_ptr<IntInfo> gpu_count_info_;
    Decision decision_;
    std::vector<ScopedGpuBlacklistEntry> exceptions_;
    bool contains_unknown_fields_;
    bool contains_unknown_features_;
  };

  // Gets the current OS type.
  static OsType GetOsType();

  bool LoadGpuBlacklist(const base::DictionaryValue& parsed_json,
                        OsFilter os_filter);

  void Clear();

  // Check if the entry is supported by the current version of browser.
  // By default, if there is no browser version information in the entry,
  // return kSupported;
  BrowserVersionSupport IsEntrySupportedByCurrentBrowserVersion(
      const base::DictionaryValue* value);

  // Returns the number of entries.  This is only for tests.
  size_t num_entries() const;

  // Check if any entries contain unknown fields.  This is only for tests.
  bool contains_unknown_fields() const { return contains_unknown_fields_; }

  static NumericOp StringToNumericOp(const std::string& op);

  std::string version_;
  std::vector<ScopedGpuBlacklistEntry> blacklist_;

  std::string browser_version_;

  // This records all the blacklist entries that are appliable to the current
  // user machine.  It is updated everytime MakeBlacklistDecision() is
  // called and is used later by GetDecisionEntries().
  std::vector<ScopedGpuBlacklistEntry> active_entries_;

  uint32 max_entry_id_;

  bool contains_unknown_fields_;

  bool needs_more_info_;

  DISALLOW_COPY_AND_ASSIGN(GpuBlacklist);
};

}  // namespace content

#endif  // CONTENT_BROWSER_GPU_GPU_BLACKLIST_H_