const preact = require('preact');
const { connect } = require('preact-redux');
const range = require('lodash/range');
const countBy = require('lodash/countBy');
const without = require('lodash/without');
const forEach = require('lodash/forEach');

const { removeTier } = require('../utils');
const shapes = require('./shapes');
const actions = require('../actions');
const buttons = require('./buttons');

const addState = connect(
    function receiveState(state) {
        const {
            ws,
            instance,
            player,
            reclaiming,
            vboxSelected,
            itemInfo,
            itemUnequip,
            tutorial,
        } = state;

        function sendVboxDiscard() {
            return ws.sendVboxDiscard(instance.id);
        }

        function sendVboxAccept(group, index) {
            document.activeElement.blur();
            return ws.sendVboxAccept(instance.id, group, index);
        }

        function sendVboxCombine() {
            return ws.sendVboxCombine(instance.id, vboxSelected.stashSelect, vboxSelected.shopSelect);
        }

        function sendVboxReclaim(i) {
            return ws.sendVboxReclaim(instance.id, i);
        }

        function sendItemUnequip([constructId, item]) {
            return ws.sendVboxUnequip(instance.id, constructId, item);
        }

        return {
            instance,
            player,
            reclaiming,
            sendVboxAccept,
            sendVboxCombine,
            sendVboxDiscard,
            sendVboxReclaim,
            vboxSelected,
            itemInfo,
            itemUnequip,
            sendItemUnequip,
            tutorial,
        };
    },

    function receiveDispatch(dispatch) {
        function setReclaiming(v) {
            dispatch(actions.setItemUnequip([]));
            dispatch(actions.setVboxSelected({ shopSelect: [], stashSelect: [] }));
            return dispatch(actions.setReclaiming(v));
        }

        function setInfo(item) {
            return dispatch(actions.setInfo(item));
        }

        function setVboxSelected(v) {
            dispatch(actions.setItemUnequip([]));
            dispatch(actions.setVboxSelected(v));
            return dispatch(actions.setVboxSelected(v));
        }

        return {
            setReclaiming,
            setInfo,
            setVboxSelected,
        };
    }
);

function validVboxSelect(vbox, itemInfo, shopSelect, stashSelect) {
    if (shopSelect.length === 0 && stashSelect.length === 0) return false;

    const validSelects = [];

    const stashItems = stashSelect.map(j => vbox.bound[j]);
    const shopItems = shopSelect.map(j => vbox.free[j[0]][j[1]]);

    const selectedItems = stashItems.concat(shopItems);
    const itemCount = countBy(selectedItems, co => co);

    itemInfo.combos.forEach(combo => {
        const comboCount = countBy(combo.components, co => co);
        const buyCount = countBy(combo.components, co => co);
        const valid = selectedItems.every(c => {
            if (!combo.components.includes(c)) return false;
            if (itemCount[c] > comboCount[c]) return false;
            buyCount[c] -= itemCount[c];
            return true;
        });
        if (valid) {
            forEach(buyCount, (value, key) => {
                if (value > 0 && !validSelects.includes(key)) validSelects.push(key);
            });
        }
    });

    return validSelects;
}

class Vbox extends preact.Component {
    shouldComponentUpdate(newProps) {
        // Single variable props
        if (newProps.itemUnequip !== this.props.itemUnequip) return true;
        if (newProps.reclaiming !== this.props.reclaiming) return true;
        if (newProps.tutorial !== this.props.tutorial) return true;
        if (newProps.vboxSelected !== this.props.vboxSelected) return true;
        if (newProps.player !== this.props.player) return true;
        if (newProps.instance !== this.props.instance) return true;
        return false;
    }

    render(args) {
        const {
            // Changing state variables
            itemUnequip,
            player,
            reclaiming,
            tutorial,
            vboxSelected,
            instance,

            // Static
            itemInfo,
            // Function Calls
            sendItemUnequip,
            sendVboxAccept,
            sendVboxCombine,
            sendVboxDiscard,
            sendVboxReclaim,
            setVboxSelected,
            setInfo,
            setReclaiming,
        } = args;

        if (!player) return false;
        const { vbox } = player;
        const { shopSelect, stashSelect } = vboxSelected;
        const vboxSelecting = shopSelect.length === 1 && stashSelect.length === 0;

        function combinerChange(newStashSelect) {
            return setVboxSelected({ shopSelect, stashSelect: newStashSelect });
        }

        const vboxHighlight = validVboxSelect(vbox, itemInfo, shopSelect, stashSelect);
        //
        // VBOX
        //
        function vboxHover(e, v) {
            if (v) {
                e.stopPropagation();
                if (shopSelect.find(c => c[0])) return true; // There is a base skill or spec selected in the vbox
                if (stashSelect.length !== 0) {
                    const base = stashSelect.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c]));
                    if (base || base === 0) return true;
                }
                setInfo(v);
            }
            return true;
        }

        function clearVboxSelected() {
            setVboxSelected({ shopSelect: [], stashSelect: [] });
        }

        function vboxBuySelected() {
            if (!vboxSelecting) return false;
            document.activeElement.blur();
            sendVboxAccept(shopSelect[0][0], shopSelect[0][1]);
            return true;
        }

        function availableBtn(v, group, index) {
            if (!v) return <button disabled class='empty' key={(group * 10) + index} >&nbsp;</button>;
            const selected = shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index);

            const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';

            function onClick(e) {
                e.stopPropagation();
                if (!comboHighlight) setInfo(vbox.free[group][index]);
                if (shopSelect.length && shopSelect.some(vs => vs[0] === group && vs[1] === index)) {
                    return setVboxSelected({ shopSelect: shopSelect.filter(vs => !(vs[0] === group && vs[1] === index)), stashSelect });
                }

                if (!shopSelect.length && !stashSelect.length) return setVboxSelected({ shopSelect: [[group, index]], stashSelect });
                if (comboHighlight !== 'combo-border') {
                    return setVboxSelected({ shopSelect: [[group, index]], stashSelect: [] });
                }
                return setVboxSelected({ shopSelect: [...shopSelect, [group, index]], stashSelect });
            }


            const classes = `${v.toLowerCase()} ${selected ? 'highlight' : ''} ${comboHighlight}`;

            const vboxObject = shapes[v] ? shapes[v]() : v;
            const disabled = vbox.bits <= group;
            return (
                <label draggable='true'
                    onDragStart={ev => ev.dataTransfer.setData('text', '')}
                    key={group * 10 + index}
                    onDragEnd={clearVboxSelected}>
                    <button
                        class={classes}
                        disabled={disabled}
                        onMouseOver={e => vboxHover(e, v)}
                        onMouseDown={onClick}
                        onClick={e => e.stopPropagation()}
                    > {vboxObject}
                    </button>
                </label>
            );
        }


        function vboxElement() {
            return (
                <div class='shop'
                    onClick={e => e.stopPropagation()}>
                    <div class="vbox-colours">
                        {range(0, 6).map(i => availableBtn(vbox.free[0][i], 0, i))}
                    </div>
                    <div class="vbox-items">
                        {range(0, 3).map(i => availableBtn(vbox.free[1][i], 1, i))}
                        {range(0, 3).map(i => availableBtn(vbox.free[2][i], 2, i))}
                    </div>

                </div>
            );
        }

        function vboxHdr() {
            return (
                <div class="shop-hdr">
                    <h3
                        onTouchStart={e => e.target.scrollIntoView(true)}
                        onMouseOver={e => hoverInfo(e, 'vbox')}> VBOX
                    </h3>
                    <div class={`bits ${vbox.bits < 3 ? 'red' : false}`} onMouseOver={e => hoverInfo(e, 'bits')}>
                        <h1> {vbox.bits}b </h1>
                    </div>
                    <button
                        class='vbox-btn'
                        onMouseOver={e => hoverInfo(e, 'refill')}
                        disabled={vbox.bits < 2
                            || (tutorial && tutorial < 7 && instance.time_control === 'Practice' && instance.rounds.length === 1)
                        }
                        onClick={e => e.stopPropagation()}
                        onMouseDown={() => sendVboxDiscard()}>
                        refill <br />
                        2b
                    </button>
                </div>
            );
        }
        //
        //  INVENTORY
        //

        function inventoryBtn(v, i) {
            const inventoryHighlight = vboxSelecting || itemUnequip.length;

            if (!v && v !== 0) {
                const emptyInvClick = () => {
                    if (vboxSelecting) return vboxBuySelected();
                    return false;
                };
                return <button key={i} onClick={emptyInvClick} disabled={!inventoryHighlight} class={inventoryHighlight ? 'receiving' : 'empty'} >&nbsp;</button>;
            }

            const comboHighlight = vboxHighlight && vboxHighlight.includes(v) ? 'combo-border' : '';

            function onClick(type) {
                if (reclaiming) return sendVboxReclaim(i);

                const combinerContainsIndex = stashSelect.indexOf(i) > -1;
                // removing
                if (combinerContainsIndex) {
                    if (type === 'click') {
                        return combinerChange(without(stashSelect, i));
                    }
                    return true;
                }

                if (!comboHighlight) {
                    setInfo(vbox.bound[i]);
                    return setVboxSelected({ shopSelect: [], stashSelect: [i] });
                }

                stashSelect.push(i);
                // if (stashSelect.length === 3) setInfo(comboItem.item);
                return combinerChange(stashSelect);
            }

            const highlighted = stashSelect.indexOf(i) > -1;
            const border = buttons[removeTier(v)] ? buttons[removeTier(v)]() : '';
            const classes = `${highlighted ? 'highlight' : border} ${comboHighlight}`;

            const invObject = shapes[v] ? shapes[v]() : v;

            return (
                <label
                    key={i}
                    draggable='true'
                    onDragStart={ev => {
                        onClick('drag');
                        ev.dataTransfer.setData('text', '');
                    }}>
                    <button
                        class={classes}
                        onMouseOver={e => vboxHover(e, v)}
                        onClick={e => e.stopPropagation()}
                        onMouseDown={() => onClick('click')}>
                        {invObject}
                    </button>
                </label>
            );
        }

        function combinerBtn() {
            let text = '';
            let mouseEvent = false;
            const combineLength = stashSelect.length + shopSelect.length;
            if (vboxHighlight && vboxHighlight.length === 0) {
                // The selected items can't be combined with additional items therefore valid combo
                const stashItems = stashSelect.map(j => vbox.bound[j]);
                const shopItems = shopSelect.map(j => vbox.free[j[0]][j[1]]);
                const selectedItems = stashItems.concat(shopItems);
                const combinerCount = countBy(selectedItems, co => co);

                const comboItemObj = itemInfo.combos.find(combo => selectedItems.every(c => {
                    if (!combo.components.includes(c)) return false;
                    const comboCount = countBy(combo.components, co => co);
                    if (combinerCount[c] > comboCount[c]) return false;
                    return true;
                }));
                let comboItem = comboItemObj ? comboItemObj.item : 'refine';
                setInfo(comboItem);
                comboItem = comboItem.replace('Plus', '+');
                let bits = 0;
                shopSelect.forEach(item => bits += item[0] + 1);
                text = bits
                    ? <p>Buy<br /> {comboItem} <br/> {bits}b</p>
                    : <p>Combine<br />{comboItem}</p>;
                if (vbox.bits >= bits) mouseEvent = sendVboxCombine;
            } else if (stashSelect.length === 0 && shopSelect.length === 1) {
                const item = shopSelect[0];
                text = <p>Buy <br/>{vbox.free[item[0]][item[1]]}<br/>{item[0] + 1}b</p>;
                mouseEvent = vboxBuySelected;
            } else {
                for (let i = 0; i < 3; i++) {
                    if (combineLength > i) {
                        text += '■ ';
                    } else {
                        text += '▫ ';
                    }
                }
            }
            return (
                <div class='combiner'>
                    <button
                        class='vbox-btn'
                        disabled={!mouseEvent}
                        onClick={e => e.stopPropagation()}
                        onMouseDown={() => mouseEvent()}>
                        {text}
                    </button>
                </div>
            );
        }

        function inventoryElement() {
            function inventoryClick(e) {
                e.stopPropagation();
                if (itemUnequip.length) return sendItemUnequip(itemUnequip);
                return true;
            }

            return (
                <div
                    class='stash'
                    onMouseDown={inventoryClick}
                    onClick={e => e.stopPropagation()}
                    onDragOver={ev => ev.preventDefault()}
                    onDrop={inventoryClick}
                >
                    <div class={`vbox-items ${reclaiming ? 'reclaiming' : ''}`}>
                        {range(0, 6).map(i => inventoryBtn(vbox.bound[i], i))}
                    </div>
                </div>
            );
        }

        function inventoryHdr() {
            return (
                <div class={`stash-hdr ${reclaiming ? 'reclaiming' : ''}`}>
                    <h3
                        onTouchStart={e => e.target.scrollIntoView(true)}
                        onMouseOver={e => hoverInfo(e, 'inventory')}> INVENTORY
                    </h3>
                    <button
                        disabled={tutorial && tutorial < 8 && instance.time_control === 'Practice' && instance.rounds.length === 1}
                        class='vbox-btn reclaim'
                        onMouseOver={e => hoverInfo(e, 'reclaim')}
                        onClick={e => e.stopPropagation()}
                        onMouseDown={e => {
                            e.stopPropagation()
                            setReclaiming(!reclaiming)
                        }}>
                        refund
                    </button>
                </div>
            );
        }


        //
        // EVERYTHING
        //
        function hoverInfo(e, newInfo) {
            if (shopSelect.find(c => c[0])) return true;
            if (stashSelect.length !== 0) {
                const base = stashSelect.find(c => !['Red', 'Blue', 'Green'].includes(vbox.bound[c]));
                if (base || base === 0) return true;
            }
            return setInfo(newInfo);
        }

        return (
            <div class='vbox'>
                {vboxHdr()}
                {vboxElement()}
                {inventoryHdr()}
                {inventoryElement()}
                {combinerBtn()}
            </div>
        );
    }
}

module.exports = addState(Vbox);
