// 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:sky' as sky;

import 'package:sky/rendering/box.dart';
import 'package:sky/rendering/object.dart';
import 'package:sky/theme/view_configuration.dart';
import 'package:sky/widgets/framework.dart';

// Slots are painted in this order and hit tested in reverse of this order
enum ScaffoldSlots {
  body,
  statusBar,
  toolbar,
  snackBar,
  floatingActionButton,
  drawer
}

class RenderScaffold extends RenderBox {

  RenderScaffold({
    RenderBox body,
    RenderBox statusBar,
    RenderBox toolbar,
    RenderBox snackBar,
    RenderBox floatingActionButton,
    RenderBox drawer
  }) {
    this[ScaffoldSlots.body] = body;
    this[ScaffoldSlots.statusBar] = statusBar;
    this[ScaffoldSlots.toolbar] = toolbar;
    this[ScaffoldSlots.snackBar] = snackBar;
    this[ScaffoldSlots.floatingActionButton] = floatingActionButton;
    this[ScaffoldSlots.drawer] = drawer;
  }

  Map<ScaffoldSlots, RenderBox> _slots = new Map<ScaffoldSlots, RenderBox>();
  RenderBox operator[] (ScaffoldSlots slot) => _slots[slot];
  void operator[]= (ScaffoldSlots slot, RenderBox value) {
    RenderBox old = _slots[slot];
    if (old == value)
      return;
    if (old != null)
      dropChild(old);
    if (value == null) {
      _slots.remove(slot);
    } else {
      _slots[slot] = value;
      adoptChild(value);
    }
    markNeedsLayout();
  }

  void attachChildren() {
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      RenderBox box = _slots[slot];
      if (box != null)
        box.attach();
    }
  }

  void detachChildren() {
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      RenderBox box = _slots[slot];
      if (box != null)
        box.detach();
    }
  }

  void visitChildren(RenderObjectVisitor visitor) {
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      RenderBox box = _slots[slot];
      if (box != null)
        visitor(box);
    }
  }

  ScaffoldSlots remove(RenderBox child) {
    assert(child != null);
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      if (_slots[slot] == child) {
        this[slot] = null;
        return slot;
      }
    }
    return null;
  }

  bool get sizedByParent => true;
  void performResize() {
    size = constraints.biggest;
    assert(!size.isInfinite);
  }

  // TODO(eseidel): These change based on device size!
  // http://www.google.com/design/spec/layout/metrics-keylines.html#metrics-keylines-keylines-spacing
  static const kButtonX = 16.0; // left from right edge of body
  static const kButtonY = 16.0; // up from bottom edge of body

  void performLayout() {
    double bodyHeight = size.height;
    double bodyPosition = 0.0;
    if (_slots[ScaffoldSlots.statusBar] != null) {
      RenderBox statusBar = _slots[ScaffoldSlots.statusBar];
      statusBar.layout(new BoxConstraints.tight(new Size(size.width, kStatusBarHeight)));
      assert(statusBar.parentData is BoxParentData);
      statusBar.parentData.position = new Point(0.0, size.height - kStatusBarHeight);
      bodyHeight -= kStatusBarHeight;
    }
    if (_slots[ScaffoldSlots.toolbar] != null) {
      RenderBox toolbar = _slots[ScaffoldSlots.toolbar];
      double toolbarHeight = kToolBarHeight + sky.view.paddingTop;
      toolbar.layout(new BoxConstraints.tight(new Size(size.width, toolbarHeight)));
      assert(toolbar.parentData is BoxParentData);
      toolbar.parentData.position = Point.origin;
      bodyPosition += toolbarHeight;
      bodyHeight -= toolbarHeight;
    }
    if (_slots[ScaffoldSlots.body] != null) {
      RenderBox body = _slots[ScaffoldSlots.body];
      body.layout(new BoxConstraints.tight(new Size(size.width, bodyHeight)));
      assert(body.parentData is BoxParentData);
      body.parentData.position = new Point(0.0, bodyPosition);
    }
    if (_slots[ScaffoldSlots.snackBar] != null) {
      RenderBox snackBar = _slots[ScaffoldSlots.snackBar];
      // TODO(jackson): On tablet/desktop, minWidth = 288, maxWidth = 568
      snackBar.layout(new BoxConstraints(minWidth: size.width, maxWidth: size.width, minHeight: 0.0, maxHeight: size.height),
                      parentUsesSize: true);
      assert(snackBar.parentData is BoxParentData);
      // Position it off-screen. SnackBar slides in with an animation.
      snackBar.parentData.position = new Point(0.0, size.height);
    }
    if (_slots[ScaffoldSlots.floatingActionButton] != null) {
      RenderBox floatingActionButton = _slots[ScaffoldSlots.floatingActionButton];
      Size area = new Size(size.width - kButtonX, size.height - kButtonY);
      floatingActionButton.layout(new BoxConstraints.loose(area), parentUsesSize: true);
      assert(floatingActionButton.parentData is BoxParentData);
      floatingActionButton.parentData.position = (area - floatingActionButton.size).toPoint();
    }
    if (_slots[ScaffoldSlots.drawer] != null) {
      RenderBox drawer = _slots[ScaffoldSlots.drawer];
      drawer.layout(new BoxConstraints(minWidth: 0.0, maxWidth: size.width, minHeight: size.height, maxHeight: size.height));
      assert(drawer.parentData is BoxParentData);
      drawer.parentData.position = Point.origin;
    }
  }

  void paint(PaintingContext context, Offset offset) {
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      RenderBox box = _slots[slot];
      if (box != null) {
        assert(box.parentData is BoxParentData);
        context.paintChild(box, box.parentData.position + offset);
      }
    }
  }

  void hitTestChildren(HitTestResult result, { Point position }) {
    for (ScaffoldSlots slot in ScaffoldSlots.values.reversed) {
      RenderBox box = _slots[slot];
      if (box != null) {
        assert(box.parentData is BoxParentData);
        if (box.hitTest(result, position: (position - box.parentData.position).toPoint()))
          return;
      }
    }
  }

  String debugDescribeChildren(String prefix) {
    return _slots.keys.map((slot) => '${prefix}${slot}: ${_slots[slot].toString(prefix)}').join();
  }
}

class Scaffold extends RenderObjectWrapper {

  Scaffold({
    Key key,
    Widget body,
    Widget statusBar,
    Widget toolbar,
    Widget snackBar,
    Widget floatingActionButton,
    Widget drawer
  }) : super(key: key) {
    _slots[ScaffoldSlots.body] = body;
    _slots[ScaffoldSlots.statusBar] = statusBar;
    _slots[ScaffoldSlots.toolbar] = toolbar;
    _slots[ScaffoldSlots.snackBar] = snackBar;
    _slots[ScaffoldSlots.floatingActionButton] = floatingActionButton;
    _slots[ScaffoldSlots.drawer] = drawer;
  }

  Map<ScaffoldSlots, Widget> _slots = new Map<ScaffoldSlots, Widget>();

  RenderScaffold get renderObject => super.renderObject;
  RenderScaffold createNode() => new RenderScaffold();

  void walkChildren(WidgetTreeWalker walker) {
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      Widget widget = _slots[slot];
      if (widget != null)
        walker(widget);
    }
  }

  void insertChildRenderObject(RenderObjectWrapper child, ScaffoldSlots slot) {
    renderObject[slot] = child != null ? child.renderObject : null;
  }

  void detachChildRenderObject(RenderObjectWrapper child) {
    final renderObject = this.renderObject; // TODO(ianh): Remove this once the analyzer is cleverer
    assert(renderObject is RenderScaffold);
    assert(renderObject == child.renderObject.parent);
    renderObject.remove(child.renderObject);
    assert(renderObject == this.renderObject); // TODO(ianh): Remove this once the analyzer is cleverer
  }

  void syncRenderObject(Widget old) {
    super.syncRenderObject(old);
    for (ScaffoldSlots slot in ScaffoldSlots.values) {
      Widget widget = _slots[slot];
      _slots[slot] = syncChild(widget, old is Scaffold ? old._slots[slot] : null, slot);
      assert((_slots[slot] == null) == (widget == null));
      assert(_slots[slot] == null || _slots[slot].parent == this);
    }
  }

}
