/*
 * (C) 1999-2003 Lars Knoll (knoll@kde.org)
 * Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
 * Copyright (C) 2007 Alexey Proskuryakov <ap@webkit.org>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; see the file COPYING.LIB.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef CSSPrimitiveValue_h
#define CSSPrimitiveValue_h

#include "core/CSSPropertyNames.h"
#include "core/CSSValueKeywords.h"
#include "core/CoreExport.h"
#include "core/css/CSSValue.h"
#include "platform/wtf/BitVector.h"
#include "platform/wtf/Forward.h"
#include "platform/wtf/MathExtras.h"
#include "platform/wtf/text/StringHash.h"
#include "platform/wtf/text/StringView.h"

namespace blink {

class CSSCalcValue;
class CSSToLengthConversionData;
class Length;

// Dimension calculations are imprecise, often resulting in values of e.g.
// 44.99998. We need to go ahead and round if we're really close to the next
// integer value.
template <typename T>
inline T RoundForImpreciseConversion(double value) {
  value += (value < 0) ? -0.01 : +0.01;
  return ((value > std::numeric_limits<T>::max()) ||
          (value < std::numeric_limits<T>::min()))
             ? 0
             : static_cast<T>(value);
}

template <>
inline float RoundForImpreciseConversion(double value) {
  double ceiled_value = ceil(value);
  double proximity_to_next_int = ceiled_value - value;
  if (proximity_to_next_int <= 0.01 && value > 0)
    return static_cast<float>(ceiled_value);
  if (proximity_to_next_int >= 0.99 && value < 0)
    return static_cast<float>(floor(value));
  return static_cast<float>(value);
}

// CSSPrimitiveValue stores numeric data types (e.g. 1, 10px, 4%) and calc()
// values (e.g. calc(3px + 2em)).
class CORE_EXPORT CSSPrimitiveValue : public CSSValue {
 public:
  // These units are iterated through, so be careful when adding or changing the
  // order.
  enum class UnitType {
    kUnknown,
    kNumber,
    kPercentage,
    // Length units
    kEms,
    kExs,
    kPixels,
    kCentimeters,
    kMillimeters,
    kInches,
    kPoints,
    kPicas,
    kViewportWidth,
    kViewportHeight,
    kViewportMin,
    kViewportMax,
    kRems,
    kChs,
    kUserUnits,  // The SVG term for unitless lengths
    // Angle units
    kDegrees,
    kRadians,
    kGradians,
    kTurns,
    // Time units
    kMilliseconds,
    kSeconds,
    kHertz,
    kKilohertz,
    // Resolution
    kDotsPerPixel,
    kDotsPerInch,
    kDotsPerCentimeter,
    // Other units
    kFraction,
    kInteger,
    kCalc,
    kCalcPercentageWithNumber,
    kCalcPercentageWithLength,
    kCalcLengthWithNumber,
    kCalcPercentageWithLengthAndNumber,

    // This value is used to handle quirky margins in reflow roots (body, td,
    // and th) like WinIE. The basic idea is that a stylesheet can use the value
    // __qem (for quirky em) instead of em. When the quirky value is used, if
    // you're in quirks mode, the margin will collapse away inside a table cell.
    // This quirk is specified in the HTML spec but our impl is different.
    // TODO: Remove this. crbug.com/443952
    kQuirkyEms,
  };

  enum LengthUnitType {
    kUnitTypePixels = 0,
    kUnitTypePercentage,
    kUnitTypeFontSize,
    kUnitTypeFontXSize,
    kUnitTypeRootFontSize,
    kUnitTypeZeroCharacterWidth,
    kUnitTypeViewportWidth,
    kUnitTypeViewportHeight,
    kUnitTypeViewportMin,
    kUnitTypeViewportMax,

    // This value must come after the last length unit type to enable iteration
    // over the length unit types.
    kLengthUnitTypeCount,
  };

  struct CSSLengthArray {
    CSSLengthArray() : values(kLengthUnitTypeCount) {
      type_flags.Resize(kLengthUnitTypeCount);
    }

    Vector<double, CSSPrimitiveValue::kLengthUnitTypeCount> values;
    BitVector type_flags;
  };

  void AccumulateLengthArray(CSSLengthArray&, double multiplier = 1) const;

  enum UnitCategory {
    kUNumber,
    kUPercent,
    kULength,
    kUAngle,
    kUTime,
    kUFrequency,
    kUResolution,
    kUOther
  };
  static UnitCategory UnitTypeToUnitCategory(UnitType);
  static float ClampToCSSLengthRange(double);

  static bool IsAngle(UnitType unit) {
    return unit == UnitType::kDegrees || unit == UnitType::kRadians ||
           unit == UnitType::kGradians || unit == UnitType::kTurns;
  }
  bool IsAngle() const { return IsAngle(TypeWithCalcResolved()); }
  bool IsFontRelativeLength() const {
    return GetType() == UnitType::kQuirkyEms || GetType() == UnitType::kEms ||
           GetType() == UnitType::kExs || GetType() == UnitType::kRems ||
           GetType() == UnitType::kChs;
  }
  bool IsQuirkyEms() const { return GetType() == UnitType::kQuirkyEms; }
  bool IsViewportPercentageLength() const {
    return IsViewportPercentageLength(GetType());
  }
  static bool IsViewportPercentageLength(UnitType type) {
    return type >= UnitType::kViewportWidth && type <= UnitType::kViewportMax;
  }
  static bool IsLength(UnitType type) {
    return (type >= UnitType::kEms && type <= UnitType::kUserUnits) ||
           type == UnitType::kQuirkyEms;
  }
  static inline bool IsRelativeUnit(UnitType type) {
    return type == UnitType::kPercentage || type == UnitType::kEms ||
           type == UnitType::kExs || type == UnitType::kRems ||
           type == UnitType::kChs || IsViewportPercentageLength(type);
  }
  bool IsLength() const { return IsLength(TypeWithCalcResolved()); }
  bool IsNumber() const {
    return TypeWithCalcResolved() == UnitType::kNumber ||
           TypeWithCalcResolved() == UnitType::kInteger;
  }
  bool IsPercentage() const {
    return TypeWithCalcResolved() == UnitType::kPercentage;
  }
  bool IsPx() const { return TypeWithCalcResolved() == UnitType::kPixels; }
  static bool IsTime(UnitType unit) {
    return unit == UnitType::kSeconds || unit == UnitType::kMilliseconds;
  }
  bool IsTime() const { return IsTime(GetType()); }
  static bool IsFrequency(UnitType unit) {
    return unit == UnitType::kHertz || unit == UnitType::kKilohertz;
  }
  bool IsCalculated() const { return GetType() == UnitType::kCalc; }
  bool IsCalculatedPercentageWithNumber() const {
    return TypeWithCalcResolved() == UnitType::kCalcPercentageWithNumber;
  }
  bool IsCalculatedPercentageWithLength() const {
    return TypeWithCalcResolved() == UnitType::kCalcPercentageWithLength;
  }
  static bool IsResolution(UnitType type) {
    return type >= UnitType::kDotsPerPixel &&
           type <= UnitType::kDotsPerCentimeter;
  }
  static bool IsFlex(UnitType unit) { return unit == UnitType::kFraction; }
  bool IsFlex() const { return IsFlex(GetType()); }

  static CSSPrimitiveValue* Create(double value, UnitType);
  static CSSPrimitiveValue* Create(const Length& value, float zoom) {
    return new CSSPrimitiveValue(value, zoom);
  }

  // TODO(sashab): Remove this.
  template <typename T>
  static CSSPrimitiveValue* Create(T value) {
    return new CSSPrimitiveValue(value);
  }

  ~CSSPrimitiveValue();

  UnitType TypeWithCalcResolved() const;

  double ComputeDegrees() const;
  double ComputeSeconds() const;
  double ComputeDotsPerPixel() const;

  // Computes a length in pixels, resolving relative lengths
  template <typename T>
  T ComputeLength(const CSSToLengthConversionData&) const;

  // Converts to a Length (Fixed, Percent or Calculated)
  Length ConvertToLength(const CSSToLengthConversionData&) const;

  double GetDoubleValue() const;
  float GetFloatValue() const { return GetValue<float>(); }
  int GetIntValue() const { return GetValue<int>(); }
  template <typename T>
  inline T GetValue() const {
    return clampTo<T>(GetDoubleValue());
  }

  CSSCalcValue* CssCalcValue() const {
    DCHECK(IsCalculated());
    return value_.calc;
  }

  template <typename T>
  inline T ConvertTo() const;  // Defined in CSSPrimitiveValueMappings.h

  static const char* UnitTypeToString(UnitType);
  static UnitType StringToUnitType(StringView string) {
    if (string.Is8Bit())
      return StringToUnitType(string.Characters8(), string.length());
    return StringToUnitType(string.Characters16(), string.length());
  }

  String CustomCSSText() const;

  bool Equals(const CSSPrimitiveValue&) const;

  DECLARE_TRACE_AFTER_DISPATCH();

  static UnitType CanonicalUnitTypeForCategory(UnitCategory);
  static double ConversionToCanonicalUnitsScaleFactor(UnitType);

  // Returns true and populates lengthUnitType, if unitType is a length unit.
  // Otherwise, returns false.
  static bool UnitTypeToLengthUnitType(UnitType, LengthUnitType&);
  static UnitType LengthUnitTypeToUnitType(LengthUnitType);

 private:
  CSSPrimitiveValue(const Length&, float zoom);
  CSSPrimitiveValue(double, UnitType);

  template <typename T>
  CSSPrimitiveValue(T);  // Defined in CSSPrimitiveValueMappings.h

  template <typename T>
  CSSPrimitiveValue(T* val) : CSSValue(kPrimitiveClass) {
    Init(val);
  }

  static void Create(int);       // compile-time guard
  static void Create(unsigned);  // compile-time guard
  template <typename T>
  operator T*();  // compile-time guard

  // Code generated by CSSPrimitiveValueUnitTrie.cpp.tmpl
  static UnitType StringToUnitType(const LChar*, unsigned length);
  static UnitType StringToUnitType(const UChar*, unsigned length);

  void Init(UnitType);
  void Init(const Length&);
  void Init(CSSCalcValue*);

  double ComputeLengthDouble(const CSSToLengthConversionData&) const;

  inline UnitType GetType() const {
    return static_cast<UnitType>(primitive_unit_type_);
  }

  union {
    double num;
    // FIXME: oilpan: Should be a member, but no support for members in unions.
    // Just trace the raw ptr for now.
    CSSCalcValue* calc;
  } value_;
};

using CSSLengthArray = CSSPrimitiveValue::CSSLengthArray;

DEFINE_CSS_VALUE_TYPE_CASTS(CSSPrimitiveValue, IsPrimitiveValue());

template <>
int CSSPrimitiveValue::ComputeLength(const CSSToLengthConversionData&) const;

template <>
Length CSSPrimitiveValue::ComputeLength(const CSSToLengthConversionData&) const;

template <>
unsigned CSSPrimitiveValue::ComputeLength(
    const CSSToLengthConversionData&) const;

template <>
short CSSPrimitiveValue::ComputeLength(const CSSToLengthConversionData&) const;

template <>
float CSSPrimitiveValue::ComputeLength(const CSSToLengthConversionData&) const;

template <>
double CSSPrimitiveValue::ComputeLength(const CSSToLengthConversionData&) const;
}  // namespace blink

#endif  // CSSPrimitiveValue_h
