import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {connect} from 'react-redux';
import {ALL_PAGES, createRegExp} from './utils';

export const highlight = (str = '', filter = '') =>
    str.toString().replace(createRegExp(filter), '<strong>$1</strong>');

const _highlight = highlight;

export const Highlight = ({children, filter, highlight, ...props}) => {
    const hl = highlight || _highlight;
    return <span {...props} dangerouslySetInnerHTML={{__html: hl(children, filter)}} />;
};

Highlight.propTypes = {
    children: PropTypes.node,
    filter: PropTypes.string,
    highlight: PropTypes.func
};

Highlight.defaultProps = {
    children: null,
    filter: '',
    highlight: null
};

export const TH = ({sort: {by, reverse}, onSetSort, by: nextBy, children, className, ...props}) => {
    const isCurrent = nextBy === by;

    //TODO Add ability to customize onClick
    const onClick = e => {
        e.preventDefault();
        onSetSort(nextBy, isCurrent ? !reverse : false);
    };

    className = classnames(
        'sortable',
        {
            'order-asc': isCurrent && !reverse,
            'order-desc': isCurrent && reverse
        },
        className
    );

    const iconClassName = classnames(
        'rc-icon',
        isCurrent && reverse ? 'rc-icon-chevron-up-small' : 'rc-icon-chevron-down-small',
        'sorter-icon',
        {'list-hover-arrow': !isCurrent}
    );

    return (
        <th onClick={onClick} {...props} className={className}>
            {children}
            <span className={classnames(iconClassName)} />
        </th>
    );
};

TH.propTypes = {
    children: PropTypes.node,
    sort: PropTypes.shape({
        by: PropTypes.string.isRequired,
        reverse: PropTypes.bool.isRequired
    }),
    by: PropTypes.string.isRequired,
    className: PropTypes.string,
    onSetSort: PropTypes.func.isRequired
};

TH.defaultProps = {
    children: null,
    sort: null,
    className: null
};

export const Pagination = ({
    loading,
    filteredTotal,
    pagination: {page, perPage},
    onSetPage,
    className,
    ...props
}) => {
    const onPageClick = (p, e) => {
        e.preventDefault();
        onSetPage(p);
    };

    const totalPages = Math.ceil(filteredTotal / perPage);
    let pages = [];

    for (let i = 1; i < totalPages + 1; i++) {
        pages.push(
            <button
                className={classnames('btn btn-default', {active: page === i})}
                key={i}
                onClick={onPageClick.bind(null, i)}
                disabled={loading}>
                {i}
            </button>
        );
    }

    return (
        <span className={classnames('btn-group', className)} {...props}>
            {pages}
        </span>
    );
};

Pagination.propTypes = {
    pagination: PropTypes.shape({
        page: PropTypes.number.isRequired,
        perPage: PropTypes.number.isRequired
    }).isRequired,
    loading: PropTypes.bool,
    filteredTotal: PropTypes.number.isRequired,
    className: PropTypes.string,
    onSetPage: PropTypes.func.isRequired
};

Pagination.defaultProps = {
    loading: false,
    className: null
};

export const PerPageSelector = ({
    loading,
    pagination: {perPage, availablePerPage},
    onSetPerPage,
    ...props
}) => {
    const onChange = e => {
        onSetPerPage(parseInt(e.target.value, 10));
    };

    return (
        <select
            className="form-control"
            onChange={onChange}
            value={perPage}
            disabled={loading}
            {...props}>
            {availablePerPage.map((p, i) => (
                <option key={i} value={p}>
                    {p === ALL_PAGES ? 'All' : p}
                </option>
            ))}
        </select>
    );
};

PerPageSelector.propTypes = {
    pagination: PropTypes.shape({
        availablePerPage: PropTypes.arrayOf(PropTypes.number).isRequired,
        perPage: PropTypes.number.isRequired
    }).isRequired,
    loading: PropTypes.bool,
    onSetPerPage: PropTypes.func.isRequired
};

PerPageSelector.defaultProps = {
    loading: false
};

/**
 * @param {Setup} setup
 * @param {function} [propsMapper]
 * @param {function} [dispatchMapper]
 * @return {function(*)}
 */
export const connectSetup = (
    setup,
    propsMapper = props => props,
    dispatchMapper = props => props
) => {
    return Cmp => {
        if (!propsMapper) {
            propsMapper = props => props;
        }
        if (!dispatchMapper) {
            dispatchMapper = props => props;
        }

        return connect(
            state =>
                propsMapper(
                    {
                        filter: setup.getFilter(state),
                        fetched: setup.getFetched(state),
                        loading: setup.getLoading(state),
                        error: setup.getError(state),
                        items: setup.getItems(state),
                        total: setup.getTotal(state),
                        filteredTotal: setup.getFilteredTotal(state),
                        sort: setup.getSort(state),
                        pagination: setup.getPagination(state),
                        params: setup.getParams(state),
                        setup: setup //TODO Unnecessary?
                    },
                    setup
                ),
            dispatchMapper(
                {
                    onLoad: setup.load,
                    onSetParams: setup.setParams,
                    onSetSort: setup.setSort,
                    onSetPage: setup.setPage,
                    onSetPerPage: setup.setPerPage,
                    onSetFilter: setup.setFilter
                },
                setup
            )
        )(Cmp);
    };
};

/**
 * @param {Setup} setup
 * @param {function} [mapper]
 * @return {function(*)}
 */
export const connectUI = (setup, mapper = props => props) => {
    const UI = mapper(
        {
            TH: connectSetup(setup)(TH),
            Highlight: connectSetup(setup)(Highlight),
            PerPageSelector: connectSetup(setup)(PerPageSelector),
            Pagination: connectSetup(setup)(Pagination)
        },
        setup
    );

    return Cmp => {
        return props => <Cmp {...props} {...UI} />;
    };
};

export class SetupContext extends React.Component {
    static propTypes = {
        setup: PropTypes.any.isRequired,
        children: PropTypes.any.isRequired
    };

    static childContextTypes = {
        setup: PropTypes.any
    };

    getChildContext() {
        return {
            setup: this.props.setup //TODO Multiple named setups via this.props.setup.prefix()
        };
    }

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

export class ContextChild extends React.Component {
    static contextTypes = {
        setup: PropTypes.any
    };

    static propTypes = {
        children: PropTypes.node
    };

    static defaultProps = {
        children: null
    };

    componentWillMount() {
        this.component = this.connectComponent();
    }

    /**
     * @abstract
     * @return {React.Component}
     */
    getComponent() {
        return null;
    }

    connectComponent() {
        return connectSetup(this.context.setup)(this.getComponent());
    }

    render() {
        const Cmp = this.component;
        const {children, ...props} = this.props;
        return <Cmp {...props}>{children}</Cmp>;
    }
}

export class ContextTH extends ContextChild {
    getComponent() {
        return TH;
    }
}

export class ContextHighlight extends ContextChild {
    getComponent() {
        return Highlight;
    }
}

export class ContextPagination extends ContextChild {
    getComponent() {
        return Pagination;
    }
}

export class ContextPerPageSelector extends ContextChild {
    getComponent() {
        return PerPageSelector;
    }
}
