import * as React from 'react';
import {BasicConfig, BasicContainer, BasicContainerPropsInterface} from '../Container/types';
import {CallbackController, callbackItem} from './CallbackController';
import {compileExpressionString, isExpression, parseExpressionString} from '../../util/vm';
import {connect} from 'react-redux';
import {RootState} from '../../data/reducers';
import {bindActionCreators, Dispatch} from 'redux';
import {actionCreators, ITriggerAction, TRIGGER_SET_DATA_PAYLOAD} from './actions';
import {DataCustomer} from '../DataCustomer/Controller';
import {parseExpressString} from '../../parser/index';

export class TriggerEventItem {
    /**
     * 事件名称
     */
    event: string;

    /**
     * 指定的目标DataCustomer
     */
    targetCustomer: string | string[];

    /**
     * 传递给目标的参数
     */
    params: (string | number | boolean)[];

    /**
     * 调试模式
     */
    debug: boolean;

    /**
     * 事件触发的条件
     */
    condition?: string;
}

export class TriggerPropsInterface extends BasicContainerPropsInterface {
    info: BasicConfig;

    /**
     * 当前Container的数据模型对象
     */
    $data: Object;

    /**
     * 通过表格组件, 渲染之后, 获取到的每一行的数据
     */
    $item?: Object;

    /**
     * 通过表格组件, 渲染之后, 获取到的第几行
     */
    $index?: number;

    /**
     * React组件Key
     */
    key: string | number;

    /**
     * 底层组件设置数据模型值使用
     */
    $setData: (name: string, value: any) => void;

    /**
     * 父级的Container组件的model值
     */
    model: string;

    /**
     * 父级Container组件的dataCustomer
     */
    dataCustomer: DataCustomer;
}

interface TriggerProps extends TriggerPropsInterface {
    /**
     * 当前trigger的数据模型
     */
    $trigger: {};

    /**
     * 设置触发器的store缓存
     */
    triggerSetData: typeof actionCreators.triggerSetData;
}

class Trigger extends BasicContainer<TriggerProps, {}> {
    private callbackController: CallbackController;
    private taskQueue: any[];
    private isRunningTaskQueue: boolean;

    constructor(props: TriggerProps) {
        super(props);

        this.callbackController = new CallbackController();
        this.taskQueue = [];
        this.eventHandle = this.eventHandle.bind(this);
        this.isRunningTaskQueue = false;
    }

    componentWillMount() {
        let trigger = this.props.info.trigger;

        if (trigger instanceof Array) {
            trigger.forEach(tr => {
                let event = tr.event;
                let customerList: string | string[] = tr.targetCustomer;
                let params = tr.params;
                let condition = tr.condition;
                let debug = tr.debug;

                if (typeof customerList === 'string') {
                    customerList = [customerList];
                }

                if (!customerList) {
                    return;
                }

                customerList.forEach(targetCustomer => {
                    this.callbackController.registerCallback(event, targetCustomer, params, debug, condition);
                });
            });
        }
    }

    async componentWillReceiveProps(nextProps: TriggerProps) {
        if (this.props.$trigger !== nextProps.$trigger && this.taskQueue.length > 0 && !this.isRunningTaskQueue) {
            let prev = null;

            this.isRunningTaskQueue = true;
            for (let task of this.taskQueue) {
                let runTime = this.getRuntimeContext(nextProps, this.context);
                runTime.$prev = prev;
                try {
                    prev = await this.props.dataCustomer.execCustomer({
                        customer: task,
                        runTime,
                        model: this.props.model,
                        prev,
                        props: this.props,
                        context: this.context
                    });

                    if (prev === false) {
                        break;
                    }
                } catch (e) {
                    break;
                }
            }
            this.isRunningTaskQueue = false;
            this.taskQueue = [];
        }
    }

    render() {
        let info = this.props.info;

        if (!this.isUnderContainerEnv()) {
            console.error('trigger属性只能在container组件内部使用');
            return this.props.children;
        }

        let children = React.Children.map(this.props.children, (child: React.ReactElement<any>) => {
            return React.cloneElement(child, {
                info: info,
                $data: this.props.$data,
                $parent: this.props.$parent,
                $trigger: this.props.$trigger,
                $setData: this.props.$setData,
                dataCustomer: this.props.dataCustomer,
                eventHandle: this.eventHandle,
                model: this.props.model
            });
        });

        return (
            children
        );
    }

    private async eventHandle(eventName: string, args: Object) {
        let isExist = this.callbackController.hasCallback(eventName);

        if (!isExist) {
            return;
        }

        let callbackInfo = this.callbackController.getCallbackInfo(eventName);

        if (!(callbackInfo instanceof Array)) {
            callbackInfo = [callbackInfo];
        }

        await this.execCallbackInfo(callbackInfo, args);
    }

    private async execCallbackInfo(callbackInfo: callbackItem[], args: Object = {}) {
        callbackInfo = callbackInfo.filter(info => {
            if (!info.targetCustomer) {
                console.error('you should provider an targetCustomer props');
                return false;
            }

            return true;
        });

        let items: TRIGGER_SET_DATA_PAYLOAD[] = [];

        for (let info of callbackInfo) {
            let params = info.params || {};
            let debug = info.debug;
            let runTime = this.getRuntimeContext(this.props, this.context);
            let context = {
                ...runTime,
                $args: args
            };

            let output: any;
            if (typeof params === 'string' && isExpression(params)) {
                output = parseExpressionString(params, context);
            } else {
                output = compileExpressionString(params, context, [], true);
            }

            let condition: boolean = true;

            if (typeof info.condition === 'string' && isExpression(info.condition)) {
                condition = !!parseExpressionString(info.condition, context);
            }

            if (debug) {
                console.log(`---------- TRIGGER DEBUG START ---------`);
                console.log('event: ' + info.event);
                console.log('targetCustomer: ' + info.targetCustomer);
                console.log('params: ', info.params);
                console.log('condition: ', info.condition, 'result: ', condition);
                console.log('$args: ', args);
                console.log('---------- TRIGGER DEBUG END -----------');
            }

            if (!condition) {
                continue;
            }

            let customerGroups = this.props.dataCustomer.getGroups();
            let customerList: string | string[] = info.targetCustomer;

            if (typeof customerList === 'string') {
                customerList = [customerList];
            }

            customerList.forEach(targetCustomer => {
                // $this 自动指向内置的$SELF_PASS_CUSTOMER
                if (targetCustomer === '$this') {
                    targetCustomer = '$SELF_PASS_CUSTOMER';
                }

                if (isExpression(targetCustomer)) {
                    targetCustomer = parseExpressString(targetCustomer, runTime);
                }

                if (customerGroups.has(targetCustomer)) {
                    let customers = customerGroups.get(targetCustomer);
                    customers.forEach(customer => {
                        this.taskQueue.push(customer);

                        items.push({
                            model: this.props.model,
                            customer: customer,
                            data: output
                        });
                    });
                } else {
                    this.taskQueue.push(targetCustomer);
                    items.push({
                        model: this.props.model,
                        customer: targetCustomer,
                        data: output
                    });
                }
            });
        }

        this.props.triggerSetData(items);
    }
}

const mapStateToProps = (state: RootState, ownProps: TriggerPropsInterface) => {
    return {
        $trigger: state.trigger[ownProps.model] || {}
    };
};

const mapDispatchToProps = (dispatch: Dispatch<ITriggerAction>) => bindActionCreators({
    triggerSetData: actionCreators.triggerSetData,

}, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(Trigger);
