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

import 'package:vector_math/vector_math_64.dart';

import 'basic_types.dart';

export 'basic_types.dart';

/// A composited layer
///
/// During painting, the render tree generates a tree of composited layers that
/// are uploaded into the engine and displayed by the compositor. This class is
/// the base class for all composited layers.
abstract class Layer {
  Layer({ this.offset: Offset.zero });

  /// Offset from parent in the parent's coordinate system.
  Offset offset;

  /// This layer's parent in the layer tree
  ContainerLayer get parent => _parent;
  ContainerLayer _parent;

  /// This layer's next sibling in the parent layer's child list
  Layer get nextSibling => _nextSibling;
  Layer _nextSibling;

  /// This layer's previous sibling in the parent layer's child list
  Layer get previousSibling => _previousSibling;
  Layer _previousSibling;

  /// Removes this layer from its parent layer's child list
  void detach() {
    if (_parent != null)
      _parent._remove(this);
  }

  /// Replaces this layer with the given layer in the parent layer's child list
  void replaceWith(Layer newLayer) {
    assert(_parent != null);
    assert(newLayer._parent == null);
    assert(newLayer._nextSibling == null);
    assert(newLayer._previousSibling == null);
    newLayer._nextSibling = _nextSibling;
    if (_nextSibling != null)
      newLayer._nextSibling._previousSibling = newLayer;
    newLayer._previousSibling = _previousSibling;
    if (_previousSibling != null)
      newLayer._previousSibling._nextSibling = newLayer;
    newLayer._parent = _parent;
    if (_parent._firstChild == this)
      _parent._firstChild = newLayer;
    if (_parent._lastChild == this)
      _parent._lastChild = newLayer;
    _nextSibling = null;
    _previousSibling = null;
    _parent = null;
  }

  /// Override this function to upload this layer to the engine
  ///
  /// The layerOffset is the accumulated offset of this layer's parent from the
  /// origin of the builder's coordinate system.
  void addToScene(ui.SceneBuilder builder, Offset layerOffset);
}

/// A composited layer containing a [Picture]
class PictureLayer extends Layer {
  PictureLayer({ Offset offset: Offset.zero, this.paintBounds })
    : super(offset: offset);

  /// The rectangle in this layer's coodinate system that bounds the recording
  ///
  /// The paint bounds are used to decide how much graphics memory to allocate
  /// when rasterizing this layer.
  Rect paintBounds;

  /// The picture recorded for this layer
  ///
  /// The picture's coodinate system matches this layer's coodinate system
  ui.Picture picture;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.addPicture(offset + layerOffset, picture, paintBounds);
  }

}

/// A layer that indicates to the compositor that it should display
/// certain statistics within it
class StatisticsLayer extends Layer {
  StatisticsLayer({
    Offset offset: Offset.zero,
    this.paintBounds,
    this.optionsMask,
    this.rasterizerThreshold
  }) : super(offset: offset);

  /// The rectangle in this layer's coodinate system that bounds the recording
  Rect paintBounds;

  /// A mask specifying the statistics to display
  final int optionsMask;

  final int rasterizerThreshold;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    assert(optionsMask != null);
    builder.addStatistics(optionsMask, paintBounds.shift(layerOffset));
    builder.setRasterizerTracingThreshold(rasterizerThreshold);
  }

}


/// A composited layer that has a list of children
class ContainerLayer extends Layer {
  ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset);

  /// The first composited layer in this layer's child list
  Layer get firstChild => _firstChild;
  Layer _firstChild;

  /// The last composited layer in this layer's child list
  Layer get lastChild => _lastChild;
  Layer _lastChild;

  bool _debugUltimatePreviousSiblingOf(Layer child, { Layer equals }) {
    while (child._previousSibling != null) {
      assert(child._previousSibling != child);
      child = child._previousSibling;
    }
    return child == equals;
  }

  bool _debugUltimateNextSiblingOf(Layer child, { Layer equals }) {
    while (child._nextSibling != null) {
      assert(child._nextSibling != child);
      child = child._nextSibling;
    }
    return child == equals;
  }

  /// Adds the given layer to the end of this layer's child list
  void append(Layer child) {
    assert(child != this);
    assert(child != _firstChild);
    assert(child != _lastChild);
    assert(child._parent == null);
    assert(child._nextSibling == null);
    assert(child._previousSibling == null);
    child._parent = this;
    child._previousSibling = _lastChild;
    if (_lastChild != null)
      _lastChild._nextSibling = child;
    _lastChild = child;
    if (_firstChild == null)
      _firstChild = child;
  }

  void _remove(Layer child) {
    assert(child._parent == this);
    assert(_debugUltimatePreviousSiblingOf(child, equals: _firstChild));
    assert(_debugUltimateNextSiblingOf(child, equals: _lastChild));
    if (child._previousSibling == null) {
      assert(_firstChild == child);
      _firstChild = child._nextSibling;
    } else {
      child._previousSibling._nextSibling = child._nextSibling;
    }
    if (child._nextSibling == null) {
      assert(_lastChild == child);
      _lastChild = child._previousSibling;
    } else {
      child._nextSibling._previousSibling = child._previousSibling;
    }
    child._previousSibling = null;
    child._nextSibling = null;
    child._parent = null;
  }

  /// Removes all of this layer's children from its child list
  void removeAllChildren() {
    Layer child = _firstChild;
    while (child != null) {
      Layer next = child.nextSibling;
      child._previousSibling = null;
      child._nextSibling = null;
      child._parent = null;
      child = next;
    }
    _firstChild = null;
    _lastChild = null;
  }

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    addChildrenToScene(builder, offset + layerOffset);
  }

  /// Uploads all of this layer's children to the engine
  void addChildrenToScene(ui.SceneBuilder builder, Offset layerOffset) {
    Layer child = _firstChild;
    while (child != null) {
      child.addToScene(builder, layerOffset);
      child = child.nextSibling;
    }
  }

}

/// A composite layer that clips its children using a rectangle
class ClipRectLayer extends ContainerLayer {
  ClipRectLayer({ Offset offset: Offset.zero, this.clipRect }) : super(offset: offset);

  /// The rectangle to clip in the parent's coordinate system
  Rect clipRect;
  // TODO(abarth): Why is the rectangle in the parent's coordinate system
  // instead of in the coordinate system of this layer?

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushClipRect(clipRect.shift(layerOffset));
    addChildrenToScene(builder, offset + layerOffset);
    builder.pop();
  }

}

/// A composite layer that clips its children using a rounded rectangle
class ClipRRectLayer extends ContainerLayer {
  ClipRRectLayer({ Offset offset: Offset.zero, this.bounds, this.clipRRect }) : super(offset: offset);

  /// Unused
  Rect bounds;
  // TODO(abarth): Remove.

  /// The rounded-rect to clip in the parent's coordinate system
  ui.RRect clipRRect;
  // TODO(abarth): Why is the rounded-rect in the parent's coordinate system
  // instead of in the coordinate system of this layer?

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushClipRRect(clipRRect.shift(layerOffset), bounds.shift(layerOffset));
    addChildrenToScene(builder, offset + layerOffset);
    builder.pop();
  }

}

/// A composite layer that clips its children using a path
class ClipPathLayer extends ContainerLayer {
  ClipPathLayer({ Offset offset: Offset.zero, this.bounds, this.clipPath }) : super(offset: offset);

  /// Unused
  Rect bounds;
  // TODO(abarth): Remove.

  /// The path to clip in the parent's coordinate system
  Path clipPath;
  // TODO(abarth): Why is the path in the parent's coordinate system instead of
  // in the coordinate system of this layer?

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushClipPath(clipPath.shift(layerOffset), bounds.shift(layerOffset));
    addChildrenToScene(builder, offset + layerOffset);
    builder.pop();
  }

}

/// A composited layer that applies a transformation matrix to its children
class TransformLayer extends ContainerLayer {
  TransformLayer({ Offset offset: Offset.zero, this.transform }) : super(offset: offset);

  /// The matrix to apply
  Matrix4 transform;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    Matrix4 offsetTransform = new Matrix4.identity();
    offsetTransform.translate(offset.dx + layerOffset.dx, offset.dy + layerOffset.dy);
    builder.pushTransform((offsetTransform * transform).storage);
    addChildrenToScene(builder, Offset.zero);
    builder.pop();
  }
}

/// A composited layer that makes its children partially transparent
class OpacityLayer extends ContainerLayer {
  OpacityLayer({ Offset offset: Offset.zero, this.bounds, this.alpha }) : super(offset: offset);

  /// Unused
  Rect bounds;
  // TODO(abarth): Remove.

  /// The amount to multiply into the alpha channel
  ///
  /// The opacity is expressed as an integer from 0 to 255, where 0 is fully
  /// transparent and 255 is fully opaque.
  int alpha;

  void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
    builder.pushOpacity(alpha, bounds?.shift(layerOffset));
    addChildrenToScene(builder, offset + layerOffset);
    builder.pop();
  }
}