import React from 'react';
import { Swipeable } from 'react-swipeable';
import isEqual from 'lodash/isEqual';

import './Styles.scss';
import PropTypes from 'prop-types';
import clsx from 'clsx';
import { withStyles } from '@material-ui/core/styles';

const styles = theme => ({
    carousel: {
        tapHighlightColor: theme.tapHighlightColor,
    },
});

class Carousel extends React.Component {
    static propTypes = {
        className: PropTypes.string,
        vertical: PropTypes.bool,
        children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]),
    };

    constructor(props) {
        super(props);
        this.state = {
            slidesToShow: 0,
            containerWidth: 0,
            containerHeight: 0,
            firstIndex: 0,
            offsetLeft: 0,
            offsetTop: 0,
            prevActive: false,
            nextActive: false,
            showArrows: false,
        };
        this.children = [];
    }

    componentDidMount() {
        this.init();
        const { carousel } = this.refs;
        const wheelEventName = 'onwheel' in carousel ? 'wheel' : 'mousewheel';
        carousel.addEventListener(wheelEventName, this.scrolled);
    }

    componentDidUpdate() {
        // waiting for the browser to apply css changes..
        setTimeout(() => this.init(), 100);
    }

    init = () => {
        if (!this.slider || !this.slider.children) {
            return;
        }
        this.children = this.slider.children;
        const vertical = this.props.vertical;
        const container = this.refs.container,
            carousel = this.refs.carousel,
            cc = getComputedStyle(container),
            ph = parseInt(cc.paddingLeft) + parseInt(cc.paddingRight),
            pv = parseInt(cc.paddingTop) + parseInt(cc.paddingBottom),
            cw = (container.clientWidth > 0 ? container.clientWidth : carousel.clientWidth) - ph,
            ch = (container.clientHeight > 0 ? container.clientHeight : carousel.clientHeight) - pv;
        let width = 0,
            height = 0,
            containerWidth = 0,
            containerHeight = 0,
            slidesToShow = 0,
            idx = 0,
            offsetLeft = 0,
            offsetTop = 0;
        for (let c = 0; c < this.children.length; c++) {
            // IE 11 needs this..
            if (!this.children[c]) {
                continue;
            }
            const item = this.children[c];
            if (vertical) {
                height += item.offsetHeight;
                if (ch >= height) {
                    slidesToShow++;
                    containerHeight = height;
                }
                containerWidth = Math.max(containerWidth, item.offsetWidth);
                if (idx < this.state.firstIndex) {
                    offsetTop = -height;
                }
            } else {
                width += item.offsetWidth;
                if (cw >= width) {
                    slidesToShow++;
                    containerWidth = width;
                }

                containerHeight = Math.max(containerHeight, item.offsetHeight);

                if (idx < this.state.firstIndex) {
                    offsetLeft = -width;
                }
            }
            idx++;
        }
        if (this.state.firstIndex + slidesToShow > this.children.length) {
            this.setState({ firstIndex: this.children.length - slidesToShow });
            return;
        }
        const nextActive = this.state.firstIndex + slidesToShow < this.children.length;
        const prevActive = this.state.firstIndex > 0;
        const showArrows = this.children.length !== slidesToShow;
        if (!showArrows) {
            offsetTop = offsetLeft = 0;
        }
        const state = {
            ...this.state,
            slidesToShow,
            containerWidth,
            containerHeight,
            offsetLeft,
            offsetTop,
            prevActive,
            nextActive,
            showArrows,
        };
        if (!isEqual(state, this.state)) {
            this.setState(state);
        }
    };

    next = () => {
        let { firstIndex, slidesToShow } = this.state;
        if (firstIndex + slidesToShow < this.children.length) {
            this.setState({ firstIndex: this.state.firstIndex + 1 });
        }
    };
    prev = () => {
        let { firstIndex } = this.state;
        if (firstIndex > 0) {
            this.setState({ firstIndex: firstIndex - 1 });
        }
    };

    swiped = e => {
        // if it's vertical deltaY should be the bigger
        if (this.props.vertical && Math.abs(e.deltaY) >= Math.abs(e.deltaX)) {
            if (e.deltaY > 0) {
                this.next();
            } else {
                this.prev();
            }
        }
        // if it's not vertical deltaX should be the bigger
        else if (!this.props.vertical && Math.abs(e.deltaX) >= Math.abs(e.deltaY)) {
            if (e.deltaX > 0) {
                this.next();
            } else {
                this.prev();
            }
        }
    };

    scrolled = e => {
        e.preventDefault();
        if (e.deltaX > 0 || e.deltaY < 0 || e.wheelDelta > 0) {
            this.prev();
        } else {
            this.next();
        }
    };

    render() {
        const {
            containerWidth,
            containerHeight,
            offsetLeft,
            offsetTop,
            prevActive,
            nextActive,
            showArrows,
        } = this.state;
        const { classes } = this.props;
        const className = [
            'carousel-wrapper',
            this.props.className,
            this.props.vertical ? 'vertical' : 'horizontal',
            showArrows ? 'content-overflow' : 'content-fit',
        ].join(' ');
        const prevClassName = ['carousel-nav', 'prev', prevActive ? 'active' : ''].join(' ');
        const nextClassName = ['carousel-nav', 'next', nextActive ? 'active' : ''].join(' ');
        const style = { height: containerHeight, width: containerWidth };
        return (
            <div className={className} ref="container">
                <div className={'carousel'} ref="carousel" style={style}>
                    {showArrows && (
                        <div
                            className={clsx(classes.carousel, prevClassName)}
                            ref="prev"
                            onClick={this.prev}
                        />
                    )}
                    <Swipeable
                        className={'carousel-frame'}
                        trackMouse={true}
                        onSwiping={this.swiping}
                        onSwiped={this.swiped}
                    >
                        <div
                            className="carousel-slider"
                            style={{ left: offsetLeft, top: offsetTop }}
                            ref={slider => (this.slider = slider)}
                        >
                            {React.Children.map(this.props.children, element => {
                                return <div style={{ display: 'inline-block' }}>{element}</div>;
                            })}
                        </div>
                    </Swipeable>
                    {showArrows && (
                        <div
                            className={clsx(classes.carousel, nextClassName)}
                            ref="next"
                            onClick={this.next}
                        />
                    )}
                </div>
            </div>
        );
    }
}

export default withStyles(styles, { name: 'Carousel' })(Carousel);
