/**
 * Created by rburson on 4/29/16.
 */
import * as React from 'react'
import {
    CvState,
    CvProps,
    CvResultCallback,
    CvEvent,
    CvEventType,
    CvEventRegistry,
    CvStateChangeResult,
    CvStateChangeType,
    CvContext,
    CvMessage,
    CvMessageType,
    CvValueProvider,
    CvActionHandlerParams,
    CvActionFiredResult,
    CvActionBase
} from './catreact-core'
import {FormContext, EntityRec, Try, QueryContext, QueryMarkerOption, Log, ObjUtil, DialogException} from 'catavolt-sdk'

export interface CvQueryPaneState extends CvState {
}

export enum CvQueryMode {
    NORMAL, DEFERRED, MANUAL
}

export interface CvQueryPaneProps extends CvProps {
    paneRef?:number;
    formContext?:FormContext;
    queryContext?:QueryContext;
    stateChangeListeners?:Array<(event:CvEvent<CvStateChangeResult>)=>void>;
    actionListeners?:Array<(event:CvEvent<CvActionFiredResult>)=>void>
    queryRenderer?:(cvContext:CvContext, callback:CvQueryPaneCallback)=>any;
    errorRenderer?:(cvContext:CvContext, dialogException?:DialogException)=>{};
    actionProvider?:CvValueProvider<CvActionHandlerParams>;
    recordPageSize?:number;
    queryMode?:CvQueryMode;
}

export interface CvQueryPaneCallback {
    hasMoreBackward():boolean;
    hasMoreForward():boolean;
    isInProgress():boolean;
    pageBackward(resultCallback:CvResultCallback<number>, replaceBuffer?:boolean):void;
    pageForward(resultCallback:CvResultCallback<number>, replaceBuffer?:boolean):void;
    pageSize():number;
    refresh():void;
    suppressEvents(suppressEvents:boolean):void;
}

export var CvQueryBase = {

    formContext: function (nextProps, nextContext) {
        if(nextProps && nextProps.formContext) {
            return nextProps.formContext;
        } else if (nextContext) {
            return this.firstInScope(FormContext, nextContext.cvContext.scopeCtx);
        }  else {
            return this.props.formContext || this.firstInScope(FormContext);
        }
    },

    getChildContext: function () {
        const ctx = this.getDefaultChildContext();
        ctx.cvContext.scopeCtx.scopeObj = this.queryContext();
        return ctx;
    },

    reload: function (pageSize:number, nextProps, nextContext) {

        //we allow multiple refresh calls - the sdk chains Futures so they are safe
        this.refreshInProgress = this.refreshInProgress ? ++this.refreshInProgress : 1;
        const queryContext = this.queryContext(nextProps, nextContext);
        queryContext.setScroller(pageSize, null, [QueryMarkerOption.None]);
        CvActionBase._publishActionStarted('#refresh', queryContext, false, this.props.actionListeners, this.eventRegistry());
        queryContext.scroller.refresh().onComplete(entityRecTry=> {
            this.refreshInProgress--;
            if (entityRecTry.isFailure) {
                const event:CvEvent<CvMessage> = {
                    type: CvEventType.MESSAGE,
                    eventObj: {
                        message: 'Failed to load query buffer',
                        messageObj: entityRecTry.failure,
                        type: CvMessageType.ERROR
                    }
                }
                this.eventRegistry().publish(event, false);
            } else {
                //Log.debug(JSON.stringify(queryContext.scroller.buffer));
                if (this.isMounted()) {
                    this.forceUpdate();
                }
            }
            CvActionBase._publishActionFinished('#refresh', queryContext, this.props.actionListeners, this.eventRegistry());
        });
    },

    queryContext: function (nextProps, nextContext) {
        if(nextProps && nextProps.queryContext) {
           return nextProps.queryContext; 
        } else if (this.props.queryContext) {
            return this.props.queryContext;
        } else {
            const paneRef = nextProps ? nextProps.paneRef : this.props.paneRef;
            const formContext:FormContext = this.formContext(nextProps, nextContext);
            let queryContext = null;
            formContext.childrenContexts.some((childContext)=> {
                if (childContext instanceof QueryContext &&
                    childContext.paneRef == paneRef) {
                    queryContext = childContext;
                    return true;
                } else {
                    return false;
                }
            });
            return queryContext;
        }
    },

    isInProgress: function():boolean {
        return !!this.refreshInProgress;
    },

    checkReloadNeeded: function (nextProps, nextContext) {

        const queryContext = this.queryContext(nextProps, nextContext);
        //update page size if it has changed - note: this resets the buffer
        //attempt to detect changes to the queryContext

       //is this an update to recordPageSize?
        if(nextProps && nextProps.recordPageSize && nextProps.recordPageSize != this.props.recordPageSize) {
            this.reload(nextProps.recordPageSize, nextProps, nextContext);
        //is this a 'new' queryContext object
        } else if (queryContext && this.props.queryContext && (queryContext != this.props.queryContext)) {
            this.reload(this.props.recordPageSize, nextProps, nextContext);
        // is the queryContext's buffer empty? @TODO - removing this for now, not sure it's necessary
        //} else if (queryContext.scroller.buffer && queryContext.scroller.buffer.length === 0) {
            //this.reload(this.props.recordPageSize, nextProps, nextContext);
        //is the pane out of date?
        }else if(queryContext.isRefreshNeeded) {
            this.reload(this.props.recordPageSize, nextProps, nextContext);
        }
    },

    _checkDestroyed: function () {
        const queryContext = this.queryContext();
        if (queryContext && queryContext.isDestroyed) {
            const event:CvEvent<CvStateChangeResult> =
            {
                type: CvEventType.STATE_CHANGE,
                resourceId: null,
                eventObj: {source: queryContext, type: CvStateChangeType.DESTROYED}
            };
            this.props.stateChangeListeners.forEach(listener=> {
                listener(event)
            });
        }
    },

    _componentWillMount: function () {
        if(this.props.queryMode === CvQueryMode.NORMAL) {
            this.reload(this.props.recordPageSize);
        }
        if (this.props.actionProvider) {
            this.props.actionProvider.subscribe(this._handleAction);
        }
        (this.eventRegistry() as CvEventRegistry)
            .subscribe<CvStateChangeResult>(this._dataChangeListener, CvEventType.STATE_CHANGE);
    },

    _componentWillReceiveProps: function (nextProps, nextContext) {
        if (nextProps.actionProvider && nextProps.actionProvider !== this.props.actionProvider) {
            nextProps.actionProvider.subscribe(this._handleAction);
        }
        if(this.props.queryMode === CvQueryMode.NORMAL) {
            this.checkReloadNeeded(nextProps, nextContext);
        }
    },

    _componentWillUnmount: function () {
        (this.eventRegistry() as CvEventRegistry).unsubscribe(this._dataChangeListener);
    },

    _dataChangeListener: function (dataChangeResult:CvEvent<CvStateChangeResult>) {
        
        //reload if someone else ('source' !== me) published a state change
        if (dataChangeResult.eventObj.type === CvStateChangeType.DATA_CHANGE &&
            dataChangeResult.eventObj.source !== this.queryContext()) {
            if(this.props.queryMode === CvQueryMode.NORMAL ||
                this.props.queryMode === CvQueryMode.DEFERRED) {
                this.reload(this.props.recordPageSize);
            }
        }
    },

    _getCallbackObj: function ():CvQueryPaneCallback {

        let inProgress:boolean = false;
        let suppressEvents:boolean = false;

        return {

            hasMoreBackward: ():boolean => {
                return this.queryContext().scroller.hasMoreBackward;
            },
            hasMoreForward: ():boolean => {
                return this.queryContext().scroller.hasMoreForward;
            },

            isInProgress: ():boolean => {
                return inProgress || this.isInProgress();
            },

            pageBackward: (resultCallback:CvResultCallback<number>, replaceBuffer:boolean=false):void => {
                if (!inProgress) {
                    const queryContext = this.queryContext();
                    const scroller = queryContext.scroller;
                    if(scroller.hasMoreBackward) {
                        inProgress = true;
                        if(!suppressEvents) CvActionBase._publishActionStarted('#pageBackward', queryContext, false, this.props.actionListeners, this.eventRegistry());
                        scroller.pageBackward().onComplete((entityRecTry:Try<Array<EntityRec>>) => {
                            inProgress = false;
                            if (entityRecTry.isFailure) {
                                resultCallback(null, Error(entityRecTry.failure));
                            } else {
                                if(replaceBuffer) {
                                    scroller.trimLast(scroller.buffer.length - entityRecTry.success.length);
                                }
                                if(!suppressEvents) this.forceUpdate();
                                resultCallback(entityRecTry.success.length);
                            }
                            if(!suppressEvents) CvActionBase._publishActionFinished('#pageBackward', queryContext, this.props.actionListeners, this.eventRegistry());
                        });
                    } else {
                        resultCallback(null, Error('There are no more previous records'));
                    }
                } else {
                    resultCallback(null, Error('paging already in progress'));
                }
            },
            pageForward: (resultCallback:CvResultCallback<number>, replaceBuffer:boolean=false):void => {
                if (!inProgress) {
                    const queryContext = this.queryContext();
                    const scroller = queryContext.scroller;
                    if(scroller.hasMoreForward) {
                        inProgress = true;
                        if(!suppressEvents) CvActionBase._publishActionStarted('#pageForward', queryContext, false, this.props.actionListeners, this.eventRegistry());
                        scroller.pageForward().onComplete((entityRecTry:Try<Array<EntityRec>>) => {
                            inProgress = false;
                            if (entityRecTry.isFailure) {
                                resultCallback(null, Error(entityRecTry.failure));
                            } else {
                                if(replaceBuffer) {
                                    scroller.trimFirst(scroller.buffer.length - entityRecTry.success.length);
                                }
                                if(!suppressEvents) this.forceUpdate();
                                resultCallback(entityRecTry.success.length);
                            }
                            if(!suppressEvents) CvActionBase._publishActionFinished('#pageForward', queryContext, this.props.actionListeners, this.eventRegistry());
                        });
                    } else {
                        resultCallback(null, Error('There are no more records'));
                    }
                } else {
                    resultCallback(null, Error('paging already in progress'));
                }

            },
            pageSize: ():number => {
                return this.queryContext().scroller.pageSize;
            },
            
            refresh: ()=> {
                this.reload(this.props.recordPageSize);
            },
            
            suppressEvents: (val:boolean)=>{ suppressEvents = val;}

        }
    },

    _getDefaultProps: function () {
        return {
            paneRef: null,
            formContext: null,
            queryContext: null,
            queryRenderer: null,
            errorRenderer: null,
            stateChangeListeners: [],
            actionListeners: [],
            actionProvider: null,
            recordPageSize:50,
            queryMode: CvQueryMode.NORMAL
        }
    },

    _handleAction: function (params:CvActionHandlerParams) {

        const event:CvEvent<CvActionFiredResult> = params.event;
        const callback:CvResultCallback<any> = params.callback;
        if (event.eventObj.clientAction) {
            const actionId = event.eventObj.actionId;
            if (actionId === '#refresh') {
                this.reload(this.queryContext().scroller.buffer.length);
                callback(true);
            } else {
                callback(null, `Action ${actionId} not yet implemented.`);
            }
        }
    },

    _render: function () {

        const queryContext:QueryContext = this.queryContext();
        if (queryContext && !queryContext.isDestroyed) {
            if(queryContext.hasError) {
                return this.props.errorRenderer ? this.props.errorRenderer(this.getChildContext().cvContext, queryContext.error) : null;
            }else if (this.props.renderer) {
                return this.props.renderer(this.getChildContext().cvContext);
            } else if (this.props.queryRenderer) {
                return this.props.queryRenderer(this.getChildContext().cvContext, this._getCallbackObj())
            } else if (React.Children.count(this.props.children) > 0) {
                return this.props.children
            } else {
                return null;
            }
        } else {
            return null;
        }
    }


}