import $ from 'jquery';
import React, { PropTypes } from 'react';

const docEl = window.document.documentElement;

export default class ScrollHandler extends React.Component {

    static get propTypes() {
        return {
            bufferPx: PropTypes.number,
            children: PropTypes.element.isRequired,
            onScrollToBottom: PropTypes.func.isRequired,
            scrollElement: PropTypes.oneOfType([PropTypes.node, PropTypes.oneOf([window])]),
            suspend: PropTypes.bool,
        };
    }

    static get defaultProps() {
        return {
            scrollElement: window,
        };
    }

    componentDidMount() {
        this.updateListeners(null, this.props.scrollElement);
    }

    componentDidUpdate(previousProps) {
        this.updateListeners(previousProps.scrollElement, this.props.scrollElement);
    }

    componentWillUnmount() {
        this.updateListeners(this.props.scrollElement, null);
    }

    scrollCallback(scrollElement) {
        if (this.props.suspend) {
            return;
        }

        const $scrollElement = $(scrollElement);

        if (!this._scrollingWindow && $scrollElement.is(':hidden')) {
            return;
        }

        const scrollTop = $scrollElement.scrollTop();
        const paneHeight = this._getPaneHeight();
        const contentHeight = this._getContentHeight();
        const scrollBottom = paneHeight + scrollTop;

        // In Chrome (not checked since like v12) on Windows at some font sizes (Ctrl +), the scrollPaneHeight is rounded down, but contentHeight is
        // rounded up (I think). This means there is a 1px difference between them and the event won't fire.
        const chromeWindowsFontChangeBuffer = 1;

        const scrolledToBottom = scrollBottom + chromeWindowsFontChangeBuffer >= contentHeight - (this.props.bufferPx | 0);

        if (scrolledToBottom) {
            this.props.onScrollToBottom();
        }

    }

    updateListeners(previousScrollElement, currentScrollElement) {
        previousScrollElement = $(previousScrollElement)[0];
        currentScrollElement = $(currentScrollElement)[0];

        if (previousScrollElement === currentScrollElement) {
            return;
        }

        if (previousScrollElement) {
            $(previousScrollElement).off('scroll', this._scrollCallback);
            $(window).off('resize', this._scrollCallback);
        }

        if (currentScrollElement) {
            this._scrollCallback = this.scrollCallback.bind(this, currentScrollElement);
            this._scrollingWindow = $.isWindow(currentScrollElement);
            this._getPaneHeight = this._scrollingWindow ? () => docEl.clientHeight : () => currentScrollElement.clientHeight;
            this._getContentHeight = this._scrollingWindow ? () => docEl.scrollHeight : () => currentScrollElement.scrollHeight;
            $(currentScrollElement).on('scroll', this._scrollCallback);
            $(window).on('resize', this._scrollCallback);
        }
    }

    render() {
        return this.props.children;
    }
}
