// Copyright 2015 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 CC_RESOURCES_RESOURCE_UTIL_H_
#define CC_RESOURCES_RESOURCE_UTIL_H_

#include <stddef.h>

#include <limits>

#include "base/macros.h"
#include "base/numerics/safe_math.h"
#include "cc/base/cc_export.h"
#include "cc/base/math_util.h"
#include "cc/resources/resource_format.h"
#include "ui/gfx/geometry/size.h"

namespace cc {

class CC_EXPORT ResourceUtil {
 public:
  // Returns true if the width is valid and fits in bytes, false otherwise.
  template <typename T>
  static bool VerifyWidthInBytes(int width, ResourceFormat format);
  // Returns true if the size is valid and fits in bytes, false otherwise.
  template <typename T>
  static bool VerifySizeInBytes(const gfx::Size& size, ResourceFormat format);

  // Dies with a CRASH() if the width can not be represented as a positive
  // number of bytes.
  template <typename T>
  static T CheckedWidthInBytes(int width, ResourceFormat format);
  // Dies with a CRASH() if the size can not be represented as a positive
  // number of bytes.
  template <typename T>
  static T CheckedSizeInBytes(const gfx::Size& size, ResourceFormat format);

  // Returns the width in bytes but may overflow or return 0. Only do this for
  // computing widths for sizes that have already been checked.
  template <typename T>
  static T UncheckedWidthInBytes(int width, ResourceFormat format);
  // Returns the size in bytes but may overflow or return 0. Only do this for
  // sizes that have already been checked.
  template <typename T>
  static T UncheckedSizeInBytes(const gfx::Size& size, ResourceFormat format);
  // Returns the width in bytes aligned but may overflow or return 0. Only do
  // this for computing widths for sizes that have already been checked.
  template <typename T>
  static T UncheckedWidthInBytesAligned(int width, ResourceFormat format);
  // Returns the size in bytes aligned but may overflow or return 0. Only do
  // this for sizes that have already been checked.
  template <typename T>
  static T UncheckedSizeInBytesAligned(const gfx::Size& size,
                                       ResourceFormat format);

 private:
  template <typename T>
  static inline void VerifyType();

  template <typename T>
  static bool VerifyFitsInBytesInternal(int width,
                                        int height,
                                        ResourceFormat format,
                                        bool verify_size,
                                        bool aligned);

  template <typename T>
  static T BytesInternal(int width,
                         int height,
                         ResourceFormat format,
                         bool verify_size,
                         bool aligned);

  DISALLOW_COPY_AND_ASSIGN(ResourceUtil);
};

template <typename T>
bool ResourceUtil::VerifyWidthInBytes(int width, ResourceFormat format) {
  VerifyType<T>();
  return VerifyFitsInBytesInternal<T>(width, 0, format, false, false);
}

template <typename T>
bool ResourceUtil::VerifySizeInBytes(const gfx::Size& size,
                                     ResourceFormat format) {
  VerifyType<T>();
  return VerifyFitsInBytesInternal<T>(size.width(), size.height(), format, true,
                                      false);
}

template <typename T>
T ResourceUtil::CheckedWidthInBytes(int width, ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(width, 0, format, false, false));
  base::CheckedNumeric<T> checked_value = BitsPerPixel(format);
  checked_value *= width;
  checked_value = MathUtil::CheckedRoundUp<T>(checked_value.ValueOrDie(), 8);
  checked_value /= 8;
  return checked_value.ValueOrDie();
}

template <typename T>
T ResourceUtil::CheckedSizeInBytes(const gfx::Size& size,
                                   ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(size.width(), size.height(), format, true,
                                      false));
  base::CheckedNumeric<T> checked_value = BitsPerPixel(format);
  checked_value *= size.width();
  checked_value = MathUtil::CheckedRoundUp<T>(checked_value.ValueOrDie(), 8);
  checked_value /= 8;
  checked_value *= size.height();
  return checked_value.ValueOrDie();
}

template <typename T>
T ResourceUtil::UncheckedWidthInBytes(int width, ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(width, 0, format, false, false));
  return BytesInternal<T>(width, 0, format, false, false);
}

template <typename T>
T ResourceUtil::UncheckedSizeInBytes(const gfx::Size& size,
                                     ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(size.width(), size.height(), format, true,
                                      false));
  return BytesInternal<T>(size.width(), size.height(), format, true, false);
}

template <typename T>
T ResourceUtil::UncheckedWidthInBytesAligned(int width, ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(width, 0, format, false, true));
  return BytesInternal<T>(width, 0, format, false, true);
}

template <typename T>
T ResourceUtil::UncheckedSizeInBytesAligned(const gfx::Size& size,
                                            ResourceFormat format) {
  VerifyType<T>();
  DCHECK(VerifyFitsInBytesInternal<T>(size.width(), size.height(), format, true,
                                      true));
  return BytesInternal<T>(size.width(), size.height(), format, true, true);
}

template <typename T>
void ResourceUtil::VerifyType() {
  static_assert(
      std::numeric_limits<T>::is_integer && !std::is_same<T, bool>::value,
      "T must be non-bool integer type. Preferred type is size_t.");
}

template <typename T>
bool ResourceUtil::VerifyFitsInBytesInternal(int width,
                                             int height,
                                             ResourceFormat format,
                                             bool verify_size,
                                             bool aligned) {
  base::CheckedNumeric<T> checked_value = BitsPerPixel(format);
  checked_value *= width;
  if (!checked_value.IsValid())
    return false;

  // Roundup bits to byte (8 bits) boundary. If width is 3 and BitsPerPixel is
  // 4, then it should return 16, so that row pixels do not get truncated.
  checked_value = MathUtil::UncheckedRoundUp<T>(checked_value.ValueOrDie(), 8);

  // If aligned is true, bytes are aligned on 4-bytes boundaries for upload
  // performance, assuming that GL_PACK_ALIGNMENT or GL_UNPACK_ALIGNMENT have
  // not changed from default.
  if (aligned) {
    checked_value /= 8;
    if (!checked_value.IsValid())
      return false;
    checked_value =
        MathUtil::UncheckedRoundUp<T>(checked_value.ValueOrDie(), 4);
    checked_value *= 8;
  }

  if (verify_size)
    checked_value *= height;
  if (!checked_value.IsValid())
    return false;
  T value = checked_value.ValueOrDie();
  if ((value % 8) != 0)
    return false;
  return true;
}

template <typename T>
T ResourceUtil::BytesInternal(int width,
                              int height,
                              ResourceFormat format,
                              bool verify_size,
                              bool aligned) {
  T bytes = BitsPerPixel(format);
  bytes *= width;
  bytes = MathUtil::UncheckedRoundUp<T>(bytes, 8);
  bytes /= 8;
  if (aligned)
    bytes = MathUtil::UncheckedRoundUp<T>(bytes, 4);
  if (verify_size)
    bytes *= height;

  return bytes;
}

}  // namespace cc

#endif  // CC_RESOURCES_RESOURCE_UTIL_H_
