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

import 'framework.dart';
import 'scroll_behavior.dart';
import 'scrollable.dart';
import 'virtual_viewport.dart';

import 'package:flutter/rendering.dart';

class ScrollableList extends Scrollable {
  ScrollableList({
    Key key,
    double initialScrollOffset,
    Axis scrollDirection: Axis.vertical,
    ScrollListener onScroll,
    SnapOffsetCallback snapOffsetCallback,
    double snapAlignmentOffset: 0.0,
    this.itemExtent,
    this.itemsWrap: false,
    this.padding,
    this.scrollableListPainter,
    this.children
  }) : super(
    key: key,
    initialScrollOffset: initialScrollOffset,
    scrollDirection: scrollDirection,
    onScroll: onScroll,
    snapOffsetCallback: snapOffsetCallback,
    snapAlignmentOffset: snapAlignmentOffset
  ) {
    assert(itemExtent != null);
  }

  final double itemExtent;
  final bool itemsWrap;
  final EdgeDims padding;
  final ScrollableListPainter scrollableListPainter;
  final Iterable<Widget> children;

  ScrollableState createState() => new _ScrollableListState();
}

class _ScrollableListState extends ScrollableState<ScrollableList> {
  ScrollBehavior createScrollBehavior() => new OverscrollBehavior();
  ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;

  void _handleExtentsChanged(double contentExtent, double containerExtent) {
    config.scrollableListPainter?.contentExtent = contentExtent;
    setState(() {
      scrollTo(scrollBehavior.updateExtents(
        contentExtent: config.itemsWrap ? double.INFINITY : contentExtent,
        containerExtent: containerExtent,
        scrollOffset: scrollOffset
      ));
    });
  }

  void dispatchOnScrollStart() {
    super.dispatchOnScrollStart();
    config.scrollableListPainter?.scrollStarted();
  }

  void dispatchOnScroll() {
    super.dispatchOnScroll();
    config.scrollableListPainter?.scrollOffset = scrollOffset;
  }

  void dispatchOnScrollEnd() {
    super.dispatchOnScrollEnd();
    config.scrollableListPainter?.scrollEnded();
  }

  Widget buildContent(BuildContext context) {
    return new ListViewport(
      onExtentsChanged: _handleExtentsChanged,
      startOffset: scrollOffset,
      scrollDirection: config.scrollDirection,
      itemExtent: config.itemExtent,
      itemsWrap: config.itemsWrap,
      padding: config.padding,
      overlayPainter: config.scrollableListPainter,
      children: config.children
    );
  }
}

class ListViewport extends VirtualViewport {
  ListViewport({
    Key key,
    this.onExtentsChanged,
    this.startOffset: 0.0,
    this.scrollDirection: Axis.vertical,
    this.itemExtent,
    this.itemsWrap: false,
    this.padding,
    this.overlayPainter,
    this.children
  }) {
    assert(scrollDirection != null);
    assert(itemExtent != null);
  }

  final ExtentsChangedCallback onExtentsChanged;
  final double startOffset;
  final Axis scrollDirection;
  final double itemExtent;
  final bool itemsWrap;
  final EdgeDims padding;
  final Painter overlayPainter;
  final Iterable<Widget> children;

  RenderList createRenderObject() => new RenderList(itemExtent: itemExtent);

  _ListViewportElement createElement() => new _ListViewportElement(this);
}

class _ListViewportElement extends VirtualViewportElement<ListViewport> {
  _ListViewportElement(ListViewport widget) : super(widget);

  RenderList get renderObject => super.renderObject;

  int get materializedChildBase => _materializedChildBase;
  int _materializedChildBase;

  int get materializedChildCount => _materializedChildCount;
  int _materializedChildCount;

  double get startOffsetBase => _startOffsetBase;
  double _startOffsetBase;

  double get startOffsetLimit =>_startOffsetLimit;
  double _startOffsetLimit;

  void updateRenderObject(ListViewport oldWidget) {
    renderObject.scrollDirection = widget.scrollDirection;
    renderObject.itemExtent = widget.itemExtent;
    renderObject.padding = widget.padding;
    renderObject.overlayPainter = widget.overlayPainter;
    super.updateRenderObject(oldWidget);
  }

  double _contentExtent;
  double _containerExtent;

  double _getContainerExtentFromRenderObject() {
    switch (widget.scrollDirection) {
      case Axis.vertical:
        return renderObject.size.height;
      case Axis.horizontal:
        return renderObject.size.width;
    }
  }

  void layout(BoxConstraints constraints) {
    final int length = renderObject.virtualChildCount;
    final double itemExtent = widget.itemExtent;

    double contentExtent = widget.itemExtent * length;
    double containerExtent = _getContainerExtentFromRenderObject();

    _materializedChildBase = math.max(0, widget.startOffset ~/ itemExtent);
    int materializedChildLimit = math.max(0, ((widget.startOffset + containerExtent) / itemExtent).ceil());

    if (!widget.itemsWrap) {
      _materializedChildBase = math.min(length, _materializedChildBase);
      materializedChildLimit = math.min(length, materializedChildLimit);
    } else if (length == 0) {
      materializedChildLimit = _materializedChildBase;
    }

    _materializedChildCount = materializedChildLimit - _materializedChildBase;
    _startOffsetBase = _materializedChildBase * itemExtent;
    _startOffsetLimit = materializedChildLimit * itemExtent - containerExtent;

    super.layout(constraints);

    if (contentExtent != _contentExtent || containerExtent != _containerExtent) {
      _contentExtent = contentExtent;
      _containerExtent = containerExtent;
      widget.onExtentsChanged(_contentExtent, _containerExtent);
    }
  }
}