// 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 'package:sky/painting/paragraph_painter.dart';
import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/object.dart';

export 'package:sky/painting/paragraph_painter.dart';

// Unfortunately, using full precision floating point here causes bad layouts
// because floating point math isn't associative. If we add and subtract
// padding, for example, we'll get different values when we estimate sizes and
// when we actually compute layout because the operations will end up associated
// differently. To work around this problem for now, we round fractional pixel
// values up to the nearest whole pixel value. The right long-term fix is to do
// layout using fixed precision arithmetic.
double _applyFloatingPointHack(double layoutValue) {
  return layoutValue.ceilToDouble();
}

class RenderParagraph extends RenderBox {

  RenderParagraph(TextSpan text)
   : _paragraphPainter = new ParagraphPainter(text) {
    assert(text != null);
  }

  ParagraphPainter _paragraphPainter;

  BoxConstraints _constraintsForCurrentLayout; // when null, we don't have a current layout

  TextSpan get text => _paragraphPainter.text;
  void set text(TextSpan value) {
    if (_paragraphPainter.text == value)
      return;
    _paragraphPainter.text = value;
    _constraintsForCurrentLayout = null;
    markNeedsLayout();
  }

  void _layout(BoxConstraints constraints) {
    assert(constraints != null);
    if (_constraintsForCurrentLayout == constraints)
      return; // already cached this layout
    _paragraphPainter.maxWidth = constraints.maxWidth;
    _paragraphPainter.minWidth = constraints.minWidth;
    _paragraphPainter.minHeight = constraints.minHeight;
    _paragraphPainter.maxHeight = constraints.maxHeight;
    _paragraphPainter.layout();
    _constraintsForCurrentLayout = constraints;
  }

  double getMinIntrinsicWidth(BoxConstraints constraints) {
    _layout(constraints);
    return constraints.constrainWidth(
        _applyFloatingPointHack(_paragraphPainter.minContentWidth));
  }

  double getMaxIntrinsicWidth(BoxConstraints constraints) {
    _layout(constraints);
    return constraints.constrainWidth(
        _applyFloatingPointHack(_paragraphPainter.maxContentWidth));
  }

  double _getIntrinsicHeight(BoxConstraints constraints) {
    _layout(constraints);
    return constraints.constrainHeight(
        _applyFloatingPointHack(_paragraphPainter.height));
  }

  double getMinIntrinsicHeight(BoxConstraints constraints) {
    return _getIntrinsicHeight(constraints);
  }

  double getMaxIntrinsicHeight(BoxConstraints constraints) {
    return _getIntrinsicHeight(constraints);
  }

  double computeDistanceToActualBaseline(TextBaseline baseline) {
    assert(!needsLayout);
    _layout(constraints);
    return _paragraphPainter.computeDistanceToActualBaseline(baseline);
  }

  void performLayout() {
    _layout(constraints);
    // _paragraphPainter.width always expands to fill, use maxContentWidth instead.
    size = constraints.constrain(new Size(_applyFloatingPointHack(_paragraphPainter.maxContentWidth),
                                          _applyFloatingPointHack(_paragraphPainter.height)));
  }

  void paint(PaintingContext context, Offset offset) {
    // Ideally we could compute the min/max intrinsic width/height with a
    // non-destructive operation. However, currently, computing these values
    // will destroy state inside the painter. If that happens, we need to
    // get back the correct state by calling _layout again.
    //
    // TODO(abarth): Make computing the min/max intrinsic width/height
    // a non-destructive operation.
    _layout(constraints);
    _paragraphPainter.paint(context.canvas, offset);
  }

  // we should probably expose a way to do precise (inter-glpyh) hit testing

  String debugDescribeSettings(String prefix) {
    String result = '${super.debugDescribeSettings(prefix)}';
    result += '${prefix}text:\n${text.toString("$prefix  ")}\n';
    return result;
  }
}