// 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.

import 'dart:ui' as ui show lerpDouble;

import 'basic_types.dart';

/// An immutable set of offsets in each of the four cardinal directions.
///
/// Typically used for an offset from each of the four sides of a box. For
/// example, the padding inside a box can be represented using this class.
class EdgeDims {
  /// Constructs an EdgeDims from offsets from the top, right, bottom and left.
  const EdgeDims.TRBL(this.top, this.right, this.bottom, this.left);

  /// Constructs an EdgeDims where all the offsets are value.
  const EdgeDims.all(double value)
      : top = value, right = value, bottom = value, left = value;

  /// Constructs an EdgeDims with only the given values non-zero.
  const EdgeDims.only({ this.top: 0.0,
                        this.right: 0.0,
                        this.bottom: 0.0,
                        this.left: 0.0 });

  /// Constructs an EdgeDims with symmetrical vertical and horizontal offsets.
  const EdgeDims.symmetric({ double vertical: 0.0,
                             double horizontal: 0.0 })
    : top = vertical, left = horizontal, bottom = vertical, right = horizontal;

  /// The offset from the top.
  final double top;

  /// The offset from the right.
  final double right;

  /// The offset from the bottom.
  final double bottom;

  /// The offset from the left.
  final double left;

  /// Whether every dimension is non-negative.
  bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;

  /// The total offset in the vertical direction.
  double get horizontal => left + right;

  /// The total offset in the horizontal direction.
  double get vertical => top + bottom;

  /// The size that this EdgeDims would occupy with an empty interior.
  Size get collapsedSize => new Size(horizontal, vertical);

  /// An EdgeDims with top and bottom as well as left and right flipped.
  EdgeDims get flipped => new EdgeDims.TRBL(bottom, left, top, right);

  Rect inflateRect(Rect rect) {
    return new Rect.fromLTRB(rect.left - left, rect.top - top, rect.right + right, rect.bottom + bottom);
  }

  EdgeDims operator -(EdgeDims other) {
    return new EdgeDims.TRBL(
      top - other.top,
      right - other.right,
      bottom - other.bottom,
      left - other.left
    );
  }

  EdgeDims operator +(EdgeDims other) {
    return new EdgeDims.TRBL(
      top + other.top,
      right + other.right,
      bottom + other.bottom,
      left + other.left
    );
  }

  EdgeDims operator *(double other) {
    return new EdgeDims.TRBL(
      top * other,
      right * other,
      bottom * other,
      left * other
    );
  }

  EdgeDims operator /(double other) {
    return new EdgeDims.TRBL(
      top / other,
      right / other,
      bottom / other,
      left / other
    );
  }

  EdgeDims operator ~/(double other) {
    return new EdgeDims.TRBL(
      (top ~/ other).toDouble(),
      (right ~/ other).toDouble(),
      (bottom ~/ other).toDouble(),
      (left ~/ other).toDouble()
    );
  }

  EdgeDims operator %(double other) {
    return new EdgeDims.TRBL(
      top % other,
      right % other,
      bottom % other,
      left % other
    );
  }

  /// Linearly interpolate between two EdgeDims.
  ///
  /// If either is null, this function interpolates from [EdgeDims.zero].
  static EdgeDims lerp(EdgeDims a, EdgeDims b, double t) {
    if (a == null && b == null)
      return null;
    if (a == null)
      return b * t;
    if (b == null)
      return a * (1.0 - t);
    return new EdgeDims.TRBL(
      ui.lerpDouble(a.top, b.top, t),
      ui.lerpDouble(a.right, b.right, t),
      ui.lerpDouble(a.bottom, b.bottom, t),
      ui.lerpDouble(a.left, b.left, t)
    );
  }

  /// An EdgeDims with zero offsets in each direction.
  static const EdgeDims zero = const EdgeDims.TRBL(0.0, 0.0, 0.0, 0.0);

  bool operator ==(dynamic other) {
    if (identical(this, other))
      return true;
    if (other is! EdgeDims)
      return false;
    final EdgeDims typedOther = other;
    return top == typedOther.top &&
           right == typedOther.right &&
           bottom == typedOther.bottom &&
           left == typedOther.left;
  }

  int get hashCode => hashValues(top, left, bottom, right);

  String toString() => "EdgeDims($top, $right, $bottom, $left)";
}
