/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef _mozilla_widget_GfxDriverInfo_h_
#define _mozilla_widget_GfxDriverInfo_h_

#include "nsString.h"
#include "nsTArray.h"

// Macros for adding a blocklist item to the static list. _EXT variants
// allow one to specify all available parameters, including those available
// only on specific platforms (e.g. desktop environment and driver vendor
// for Linux.)

#define APPEND_TO_DRIVER_BLOCKLIST_EXT(                                       \
    os, screen, battery, windowProtocol, driverVendor, devices, feature,      \
    featureStatus, driverComparator, driverVersion, ruleId, suggestedVersion) \
  static_assert(!GfxInfoBase::IsFeatureRetired(feature),                      \
                "Feature is retired, see GfxInfoFeatureDefs.inc!");           \
  static_assert(GfxInfoBase::IsFeatureStatusAllowed(feature, featureStatus),  \
                "Feature status not allowed, see GfxInfoFeatureDefs.inc!");   \
  sDriverInfo->AppendElement(MakeAndAddRef<GfxDriverInfo>(                    \
      os, screen, battery,                                                    \
      (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol),           \
      (nsAString&)GfxDriverInfo::GetDeviceVendor(devices),                    \
      (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor),               \
      GfxDriverInfo::GetDeviceFamily(devices), feature, featureStatus,        \
      driverComparator, driverVersion, ruleId, suggestedVersion))

#define APPEND_TO_DRIVER_BLOCKLIST(os, devices, feature, featureStatus,     \
                                   driverComparator, driverVersion, ruleId, \
                                   suggestedVersion)                        \
  APPEND_TO_DRIVER_BLOCKLIST_EXT(                                           \
      os, ScreenSizeStatus::All, BatteryStatus::All, WindowProtocol::All,   \
      DriverVendor::All, devices, feature, featureStatus, driverComparator, \
      driverVersion, ruleId, suggestedVersion)

#define APPEND_TO_DRIVER_BLOCKLIST2_EXT(                                     \
    os, screen, battery, windowProtocol, driverVendor, devices, feature,     \
    featureStatus, driverComparator, driverVersion, ruleId)                  \
  static_assert(!GfxInfoBase::IsFeatureRetired(feature),                     \
                "Feature is retired, see GfxInfoFeatureDefs.inc!");          \
  static_assert(GfxInfoBase::IsFeatureStatusAllowed(feature, featureStatus), \
                "Feature status not allowed, see GfxInfoFeatureDefs.inc!");  \
  sDriverInfo->AppendElement(MakeAndAddRef<GfxDriverInfo>(                   \
      os, screen, battery,                                                   \
      (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol),          \
      (nsAString&)GfxDriverInfo::GetDeviceVendor(devices),                   \
      (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor),              \
      GfxDriverInfo::GetDeviceFamily(devices), feature, featureStatus,       \
      driverComparator, driverVersion, ruleId))

#define APPEND_TO_DRIVER_BLOCKLIST2(os, devices, feature, featureStatus,     \
                                    driverComparator, driverVersion, ruleId) \
  APPEND_TO_DRIVER_BLOCKLIST2_EXT(                                           \
      os, ScreenSizeStatus::All, BatteryStatus::All, WindowProtocol::All,    \
      DriverVendor::All, devices, feature, featureStatus, driverComparator,  \
      driverVersion, ruleId)

#define APPEND_TO_DRIVER_BLOCKLIST_REFRESH_RATE(                             \
    os, devices, feature, featureStatus, refreshRateStatus,                  \
    minRefreshRateComparator, minRefreshRate, minRefreshRateMax,             \
    maxRefreshRateComparator, maxRefreshRate, maxRefreshRateMax, ruleId,     \
    suggestedVersion)                                                        \
  static_assert(!GfxInfoBase::IsFeatureRetired(feature),                     \
                "Feature is retired, see GfxInfoFeatureDefs.inc!");          \
  static_assert(GfxInfoBase::IsFeatureStatusAllowed(feature, featureStatus), \
                "Feature status not allowed, see GfxInfoFeatureDefs.inc!");  \
  sDriverInfo->AppendElement(MakeAndAddRef<GfxDriverInfo>(                   \
      os, (nsAString&)GfxDriverInfo::GetDeviceVendor(devices),               \
      GfxDriverInfo::GetDeviceFamily(devices), feature, featureStatus,       \
      refreshRateStatus, minRefreshRateComparator, minRefreshRate,           \
      minRefreshRateMax, maxRefreshRateComparator, maxRefreshRate,           \
      maxRefreshRateMax, ruleId, suggestedVersion))

#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_EXT(                                 \
    os, screen, battery, windowProtocol, driverVendor, devices, feature,      \
    featureStatus, driverComparator, driverVersion, driverVersionMax, ruleId, \
    suggestedVersion)                                                         \
  static_assert(!GfxInfoBase::IsFeatureRetired(feature),                      \
                "Feature is retired, see GfxInfoFeatureDefs.inc!");           \
  static_assert(GfxInfoBase::IsFeatureStatusAllowed(feature, featureStatus),  \
                "Feature status not allowed, see GfxInfoFeatureDefs.inc!");   \
  static_assert((driverComparator) == DRIVER_BETWEEN_EXCLUSIVE ||             \
                (driverComparator) == DRIVER_BETWEEN_INCLUSIVE ||             \
                (driverComparator) == DRIVER_BETWEEN_INCLUSIVE_START);        \
  do {                                                                        \
    auto info = MakeRefPtr<GfxDriverInfo>(                                    \
        os, screen, battery,                                                  \
        (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol),         \
        (nsAString&)GfxDriverInfo::GetDeviceVendor(devices),                  \
        (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor),             \
        GfxDriverInfo::GetDeviceFamily(devices), feature, featureStatus,      \
        driverComparator, driverVersion, ruleId, suggestedVersion);           \
    info->mDriverVersionMax = driverVersionMax;                               \
    sDriverInfo->AppendElement(info.forget());                                \
  } while (false)

#define APPEND_TO_DRIVER_BLOCKLIST_RANGE(                                   \
    os, devices, feature, featureStatus, driverComparator, driverVersion,   \
    driverVersionMax, ruleId, suggestedVersion)                             \
  APPEND_TO_DRIVER_BLOCKLIST_RANGE_EXT(                                     \
      os, ScreenSizeStatus::All, BatteryStatus::All, WindowProtocol::All,   \
      DriverVendor::All, devices, feature, featureStatus, driverComparator, \
      driverVersion, driverVersionMax, ruleId, suggestedVersion)

#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2_EXT(                            \
    os, screen, battery, windowProtocol, driverVendor, devices, feature,      \
    featureStatus, driverComparator, driverVersion, driverVersionMax, ruleId, \
    suggestedVersion)                                                         \
  static_assert(!GfxInfoBase::IsFeatureRetired(feature),                      \
                "Feature is retired, see GfxInfoFeatureDefs.inc!");           \
  static_assert(GfxInfoBase::IsFeatureStatusAllowed(feature, featureStatus),  \
                "Feature status not allowed, see GfxInfoFeatureDefs.inc!");   \
  static_assert((driverComparator) == DRIVER_BETWEEN_EXCLUSIVE ||             \
                (driverComparator) == DRIVER_BETWEEN_INCLUSIVE ||             \
                (driverComparator) == DRIVER_BETWEEN_INCLUSIVE_START);        \
  do {                                                                        \
    auto info = MakeRefPtr<GfxDriverInfo>(                                    \
        os, screen, battery,                                                  \
        (nsAString&)GfxDriverInfo::GetWindowProtocol(windowProtocol),         \
        (nsAString&)GfxDriverInfo::GetDeviceVendor(devices),                  \
        (nsAString&)GfxDriverInfo::GetDriverVendor(driverVendor),             \
        GfxDriverInfo::GetDeviceFamily(devices), feature, featureStatus,      \
        driverComparator, driverVersion, ruleId, suggestedVersion, false,     \
        true);                                                                \
    info->mDriverVersionMax = driverVersionMax;                               \
    sDriverInfo->AppendElement(info.forget());                                \
  } while (false)

#define APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2(                              \
    os, devices, feature, featureStatus, driverComparator, driverVersion,   \
    driverVersionMax, ruleId, suggestedVersion)                             \
  APPEND_TO_DRIVER_BLOCKLIST_RANGE_GPU2_EXT(                                \
      os, ScreenSizeStatus::All, BatteryStatus::All, WindowProtocol::All,   \
      DriverVendor::All, devices, feature, featureStatus, driverComparator, \
      driverVersion, driverVersionMax, ruleId, suggestedVersion)

namespace mozilla {
namespace widget {

enum class OperatingSystem : uint8_t {
  Unknown,
#define GFXINFO_OS(id, name) id,
#include "mozilla/widget/GfxInfoOperatingSystemDefs.inc"
#undef GFXINFO_OS
  Count
};

enum VersionComparisonOp {
#define GFXINFO_DRIVER_VERSION_CMP(id) DRIVER_##id,
#include "mozilla/widget/GfxInfoDriverVersionCmpDefs.inc"
#undef GFXINFO_DRIVER_VERSION_CMP
  DRIVER_COUNT
};

enum class DeviceFamily : uint8_t {
  All,
  IntelAll,
  NvidiaAll,
  AtiAll,
  MicrosoftAll,
  ParallelsAll,
  QualcommAll,
  AppleAll,
  AmazonAll,
  IntelGMA500,
  IntelGMA900,
  IntelGMA950,
  IntelGMA3150,
  IntelGMAX3000,
  IntelGMAX4500HD,
  IntelHDGraphicsToIvyBridge,
  IntelHDGraphicsToSandyBridge,
  IntelHaswell,
  IntelSandyBridge,
  IntelGen7Baytrail,
  IntelSkylake,
  IntelKabyLake,
  IntelHD520,
  IntelMobileHDGraphics,
  IntelMeteorLake,
  IntelArrowlake,
  IntelGen12,
  NvidiaBlockD3D9Layers,
  RadeonX1000,
  RadeonCaicos,
  RadeonBlockZeroVideoCopy,
  Geforce7300GT,
  Nvidia310M,
  Nvidia8800GTS,
  NvidiaPascal,
  Bug1137716,
  Bug1116812,
  Bug1155608,
  Bug1207665,
  Bug1447141,
  AmdR600,
  IntelWebRenderBlocked,
  NvidiaWebRenderBlocked,

  Max
};

enum class DeviceVendor : uint8_t {
#define GFXINFO_DEVICE_VENDOR(id, name) id,
#include "mozilla/widget/GfxInfoDeviceVendorDefs.inc"
#undef GFXINFO_DEVICE_VENDOR
  Max
};

enum DriverVendor : uint8_t {
#define GFXINFO_DRIVER_VENDOR(id, name) id,
#include "mozilla/widget/GfxInfoDriverVendorDefs.inc"
#undef GFXINFO_DRIVER_VENDOR
  Max
};

enum class WindowProtocol : uint8_t {
#define GFXINFO_WINDOW_PROTOCOL(id, name) id,
#include "mozilla/widget/GfxInfoWindowProtocolDefs.inc"
#undef GFXINFO_WINDOW_PROTOCOL
  Max
};

enum class RefreshRateStatus {
#define GFXINFO_REFRESH_RATE_STATUS(id, name) id,
#include "mozilla/widget/GfxInfoRefreshRateStatusDefs.inc"
#undef GFXINFO_REFRESH_RATE_STATUS
  Unknown,
  Count
};

enum class BatteryStatus : uint8_t { All, Present, None };

enum class ScreenSizeStatus : uint8_t {
  All,
  Small,           // <= 1900x1200
  SmallAndMedium,  // <= 3440x1440
  Medium,          // <= 3440x1440 && > 1900x1200
  MediumAndLarge,  // >1900x1200
  Large            // > 3440x1440
};

class GfxVersionEx final {
  static constexpr size_t MAX_PARTS = 4;

 public:
  GfxVersionEx() = default;
  GfxVersionEx(const GfxVersionEx& aOther) = default;
  GfxVersionEx(GfxVersionEx&& aOther) = default;
  GfxVersionEx& operator=(const GfxVersionEx& aOther) = default;
  GfxVersionEx& operator=(GfxVersionEx&& aOther) = default;

  GfxVersionEx(uint32_t aMajor, uint32_t aMinor, uint32_t aBuild)
      : mParts{aMajor, aMinor, aBuild} {}

  GfxVersionEx(uint32_t aMajor, uint32_t aMinor, uint32_t aBuild,
               uint32_t aRevision)
      : mParts{aMajor, aMinor, aBuild, aRevision} {}

  bool Parse(const nsACString& aVersion) {
    size_t i = 0;
    for (const auto& part : aVersion.Split('.')) {
      nsresult rv;
      mParts[i] = part.ToUnsignedInteger(&rv);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return false;
      }

      if (++i == MAX_PARTS) {
        break;
      }
    }

    while (i < MAX_PARTS) {
      mParts[i++] = 0;
    }

    return true;
  }

  int32_t Compare(const GfxVersionEx& aOther) const {
    for (size_t i = 0; i < MAX_PARTS; ++i) {
      if (mParts[i] < aOther.mParts[i]) {
        return -1;
      }
      if (mParts[i] > aOther.mParts[i]) {
        return 1;
      }
    }
    return 0;
  }

  bool Compare(const GfxVersionEx& aOther, const GfxVersionEx& aOtherMax,
               VersionComparisonOp aCmp) const {
    if (aCmp == DRIVER_COMPARISON_IGNORED) {
      return true;
    }

    switch (aCmp) {
      case DRIVER_LESS_THAN:
        return Compare(aOther) < 0;
      case DRIVER_LESS_THAN_OR_EQUAL:
        return Compare(aOther) <= 0;
      case DRIVER_GREATER_THAN:
        return Compare(aOther) > 0;
      case DRIVER_GREATER_THAN_OR_EQUAL:
        return Compare(aOther) >= 0;
      case DRIVER_EQUAL:
        return Compare(aOther) == 0;
      case DRIVER_NOT_EQUAL:
        return Compare(aOther) != 0;
      case DRIVER_BETWEEN_EXCLUSIVE:
        return Compare(aOther) > 0 && Compare(aOtherMax) < 0;
      case DRIVER_BETWEEN_INCLUSIVE:
        return Compare(aOther) >= 0 && Compare(aOtherMax) <= 0;
      case DRIVER_BETWEEN_INCLUSIVE_START:
        return Compare(aOther) >= 0 && Compare(aOtherMax) < 0;
      default:
        NS_WARNING("Unsupported op in GfxDriverInfo");
        break;
    }

    return false;
  }

 private:
  uint32_t mParts[MAX_PARTS]{};
};

/* Array of devices to match, or an empty array for all devices */
class GfxDeviceFamily final {
 public:
  NS_INLINE_DECL_REFCOUNTING(GfxDeviceFamily);

  GfxDeviceFamily() = default;

  void Append(const nsAString& aDeviceId);
  void AppendRange(int32_t aBeginDeviceId, int32_t aEndDeviceId);

  bool IsEmpty() const { return mIds.IsEmpty() && mRanges.IsEmpty(); }

  nsresult Contains(nsAString& aDeviceId) const;

 private:
  ~GfxDeviceFamily() = default;

  struct DeviceRange {
    int32_t mBegin;
    int32_t mEnd;
  };

  nsTArray<nsString> mIds;
  nsTArray<DeviceRange> mRanges;
};

class GfxDriverInfo final {
 public:
  NS_INLINE_DECL_REFCOUNTING(GfxDriverInfo);

  // If |ownDevices| is true, you are transferring ownership of the devices
  // array, and it will be deleted when this GfxDriverInfo is destroyed.
  GfxDriverInfo(OperatingSystem os, ScreenSizeStatus aScreen,
                BatteryStatus aBattery, const nsAString& windowProtocol,
                const nsAString& vendor, const nsAString& driverVendor,
                already_AddRefed<const GfxDeviceFamily> devices,
                int32_t feature, int32_t featureStatus, VersionComparisonOp op,
                uint64_t driverVersion, const char* ruleId,
                const char* suggestedVersion = nullptr, bool ownDevices = false,
                bool gpu2 = false);

  // For blocking on refresh rates rather than driver versions.
  GfxDriverInfo(OperatingSystem os, const nsAString& vendor,
                already_AddRefed<const GfxDeviceFamily> devices,
                int32_t feature, int32_t featureStatus,
                RefreshRateStatus refreshRateStatus,
                VersionComparisonOp minRefreshRateOp, uint32_t minRefreshRate,
                uint32_t minRefreshRateMax,
                VersionComparisonOp maxRefreshRateOp, uint32_t maxRefreshRate,
                uint32_t maxRefreshRateMax, const char* ruleId,
                const char* suggestedVersion = nullptr);

  GfxDriverInfo();

  OperatingSystem mOperatingSystem = OperatingSystem::Unknown;
  uint32_t mOperatingSystemVersion = 0;

  GfxVersionEx mOperatingSystemVersionEx;
  GfxVersionEx mOperatingSystemVersionExMax;
  VersionComparisonOp mOperatingSystemVersionExComparisonOp =
      DRIVER_COMPARISON_IGNORED;

  uint32_t mMinRefreshRate = 0;
  uint32_t mMinRefreshRateMax = 0;
  VersionComparisonOp mMinRefreshRateComparisonOp = DRIVER_COMPARISON_IGNORED;

  uint32_t mMaxRefreshRate = 0;
  uint32_t mMaxRefreshRateMax = 0;
  VersionComparisonOp mMaxRefreshRateComparisonOp = DRIVER_COMPARISON_IGNORED;

  RefreshRateStatus mRefreshRateStatus = RefreshRateStatus::Any;

  ScreenSizeStatus mScreen = ScreenSizeStatus::All;
  BatteryStatus mBattery = BatteryStatus::All;
  nsString mWindowProtocol;

  nsString mAdapterVendor;
  nsString mDriverVendor;

  RefPtr<const GfxDeviceFamily> mDevices;

  /* Block all features */
  static constexpr int32_t allFeatures = -1;
  /* Block all features not permitted by OnlyAllowFeatureOnKnownConfig */
  static constexpr int32_t optionalFeatures = -2;
  /* A feature from nsIGfxInfo, or a wildcard set of features */
  int32_t mFeature = optionalFeatures;

  /* A feature status from nsIGfxInfo */
  int32_t mFeatureStatus;

  VersionComparisonOp mComparisonOp = DRIVER_COMPARISON_IGNORED;

  /* versions are assumed to be A.B.C.D packed as 0xAAAABBBBCCCCDDDD */
  uint64_t mDriverVersion = 0;
  uint64_t mDriverVersionMax = 0;
  static constexpr uint64_t allDriverVersions = ~(uint64_t(0));

  const char* mSuggestedVersion = nullptr;
  nsCString mRuleId;

  static already_AddRefed<const GfxDeviceFamily> GetDeviceFamily(
      DeviceFamily id);
  static RefPtr<GfxDeviceFamily>
      sDeviceFamilies[static_cast<size_t>(DeviceFamily::Max)];

  static const nsAString& GetWindowProtocol(WindowProtocol id);
  static nsString* sWindowProtocol[static_cast<size_t>(WindowProtocol::Max)];

  static const nsAString& GetDeviceVendor(DeviceVendor id);
  static const nsAString& GetDeviceVendor(DeviceFamily id);
  static nsString* sDeviceVendors[static_cast<size_t>(DeviceVendor::Max)];

  static const nsAString& GetDriverVendor(DriverVendor id);
  static nsString* sDriverVendors[static_cast<size_t>(DriverVendor::Max)];

  nsString mModel, mHardware, mProduct, mManufacturer;

  bool mGpu2 = false;

 private:
  ~GfxDriverInfo() = default;
};

inline uint64_t DriverVersion(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
  return (uint64_t(a) << 48) | (uint64_t(b) << 32) | (uint64_t(c) << 16) |
         uint64_t(d);
}

inline uint64_t V(uint32_t a, uint32_t b, uint32_t c, uint32_t d) {
#ifdef XP_WIN
  // We make sure every driver number is padded by 0s, this will allow us the
  // easiest 'compare as if decimals' approach. See ParseDriverVersion for a
  // more extensive explanation of this approach.
  while (b > 0 && b < 1000) {
    b *= 10;
  }
  while (c > 0 && c < 1000) {
    c *= 10;
  }
  while (d > 0 && d < 1000) {
    d *= 10;
  }
#endif
  return DriverVersion(a, b, c, d);
}

// All destination string storage needs to have at least 5 bytes available.
inline bool SplitDriverVersion(const char* aSource, char* aAStr, char* aBStr,
                               char* aCStr, char* aDStr) {
  // sscanf doesn't do what we want here to we parse this manually.
  int len = strlen(aSource);

  // This "4" is hardcoded in a few places, including once as a 3.
  char* dest[4] = {aAStr, aBStr, aCStr, aDStr};
  unsigned destIdx = 0;
  unsigned destPos = 0;

  for (int i = 0; i < len; i++) {
    if (destIdx >= 4) {
      // Invalid format found. Ensure we don't access dest beyond bounds.
      return false;
    }

    if (aSource[i] == '.') {
      MOZ_ASSERT(destIdx < 4 && destPos <= 4);
      dest[destIdx++][destPos] = 0;
      destPos = 0;
      continue;
    }

    if (destPos > 3) {
      // Ignore more than 4 chars. Ensure we never access dest[destIdx]
      // beyond its bounds.
      continue;
    }

    MOZ_ASSERT(destIdx < 4 && destPos < 4);
    dest[destIdx][destPos++] = aSource[i];
  }

  // Take care of the trailing period
  if (destIdx >= 4) {
    return false;
  }

  // Add last terminator.
  MOZ_ASSERT(destIdx < 4 && destPos <= 4);
  dest[destIdx][destPos] = 0;
  for (int unusedDestIdx = destIdx + 1; unusedDestIdx < 4; unusedDestIdx++) {
    dest[unusedDestIdx][0] = 0;
  }

  if (destIdx != 3) {
    return false;
  }
  return true;
}

// This allows us to pad driver version 'substrings' with 0s, this
// effectively allows us to treat the version numbers as 'decimals'. This is
// a little strange but this method seems to do the right thing for all
// different vendor's driver strings. i.e. .98 will become 9800, which is
// larger than .978 which would become 9780.
inline void PadDriverDecimal(char* aString) {
  for (int i = 0; i < 4; i++) {
    if (!aString[i]) {
      for (int c = i; c < 4; c++) {
        aString[c] = '0';
      }
      break;
    }
  }
  aString[4] = 0;
}

inline bool ParseDriverVersion(const nsAString& aVersion,
                               uint64_t* aNumericVersion) {
  *aNumericVersion = 0;

#ifndef ANDROID
  int a, b, c, d;
  char aStr[8], bStr[8], cStr[8], dStr[8];
  /* honestly, why do I even bother */
  if (!SplitDriverVersion(NS_LossyConvertUTF16toASCII(aVersion).get(), aStr,
                          bStr, cStr, dStr))
    return false;

#  ifdef XP_WIN
  PadDriverDecimal(bStr);
  PadDriverDecimal(cStr);
  PadDriverDecimal(dStr);
#  endif

  a = atoi(aStr);
  b = atoi(bStr);
  c = atoi(cStr);
  d = atoi(dStr);

  if (a < 0 || a > 0xffff) return false;
  if (b < 0 || b > 0xffff) return false;
  if (c < 0 || c > 0xffff) return false;
  if (d < 0 || d > 0xffff) return false;

  *aNumericVersion = DriverVersion(a, b, c, d);
#else
  // Can't use aVersion.ToInteger() because that's not compiled into our code
  // unless we have XPCOM_GLUE_AVOID_NSPR disabled.
  *aNumericVersion = atoi(NS_LossyConvertUTF16toASCII(aVersion).get());
#endif
  MOZ_ASSERT(*aNumericVersion != GfxDriverInfo::allDriverVersions);
  return true;
}

}  // namespace widget
}  // namespace mozilla

#endif /*_mozilla_widget_GfxDriverInfo_h_ */
