/**
 * Created by rburson on 12/23/15.
 */

import * as React from 'react'
import {CvState, CvProps, CvBaseMixin, CvContext, CvEvent, CvEventType, CvEventRegistry, CvStateChangeResult, CvStateChangeType, CvResourceManager} from './catreact-core'
import {
    ListContext, DetailsContext, FormContext, MapContext, PrintMarkupContext, GraphContext, CalendarContext, ImagePickerContext,
    GeoFixContext, GeoLocationContext, BarcodeScanContext, Log, PaneContext, ActionSource, ContextAction
} from 'catavolt-sdk'
import ReactElement = React.ReactElement;

export interface CvFormState extends CvState {
}

export interface CvFormProps extends CvProps {
    /**
     * The sdk {FormContext} to use for this form panel. If not provided, the
     * {@link CvScopeContext} will be upwardly traversed to attempt to find a matching instance.
     */
    formContext?:FormContext;
    /**
     * A function responsible for rendering the 'layout' of the entire form. It should accept the following params:
     * @param cvContext The current CvContext. The cvContext.scopeCtx.scopeObj will be the sdk {FormContext}
     * @param childComponents The array of child components to rendered by the Form
     */
    formRenderer?:(cvContext:CvContext, childComponents:Array<ReactElement<any>>)=>{};
    /**
     * A function responsible for rendering a child component of sdk type {FormContext}. It should accept the following params:
     * @param formContext The sdk {FormContext} for this child Form
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    childFormComponentRenderer?:(formContext:FormContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {DetailsContext}. It should accept the following params:
     * @param detailsContext The sdk {DetailsContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    detailsComponentRenderer?:(detailsContext:DetailsContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {GraphContext}. It should accept the following params:
     * @param graphContext The sdk {GraphContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    graphComponentRenderer?:(graphContext:GraphContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {ListContext}. It should accept the following params:
     * @param listContext The sdk {ListContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    listComponentRenderer?:(listContext:ListContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {MapContext}. It should accept the following params:
     * @param mapContext The sdk {MapContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    mapComponentRenderer?:(mapContext:MapContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {PrintMarkupContext}. It should accept the following params:
     * @param printMarkupContext The sdk {PrintMarkupContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    printMarkupComponentRenderer?:(printMarkupContext:PrintMarkupContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {CalendarContext}. It should accept the following params:
     * @param calendarContext The sdk {CalendarContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    calendarComponentRenderer?:(calendarContext:CalendarContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {ImagePickerContext}. It should accept the following params:
     * @param imagePickerContext The sdk {ImagePickerContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    imagePickerComponentRenderer?:(imagePickerContext:ImagePickerContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {GeoFixContext}. It should accept the following params:
     * @param geoFixContext The sdk {GeoFixContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    geoFixComponentRenderer?:(geoFixContext:GeoFixContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {GeoLocationContext}. It should accept the following params:
     * @param geoLocationContext The sdk {GeoLocationContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    geoLocationComponentRenderer?:(geoLocationContext:GeoLocationContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}
    /**
     * A function responsible for rendering a child component of sdk type {BarcodeScanContext}. It should accept the following params:
     * @param barcodeScanContext The sdk {BarcodeScanContext} for this child pane
     * @param stateChangeListener Any state change events should be broadcast (or passed through) to this listener
     */
    barcodeScanComponentRenderer?:(barcodeScanContext:BarcodeScanContext, stateChangeListener:(event:CvEvent<CvStateChangeResult>)=>void, index:number)=>{}

    /*
      A note about state change listeners - notice that in 'render' there is single, 'delegate' stateChangeListener passed to each child panel
      The reason for this is that multiple children may report destroyed at the same time, and the form itself needs to report a single event
      back to the 'stateChangeListeners'
      This form isolates the stateChangeListeners from the 'lower levels' of panels that may report state changes
      */
    /**
     * An array of {@link CvListener}s
     * Any state change events will broadcast to these listeners
     */
    stateChangeListeners?:Array<(event:CvEvent<CvStateChangeResult>)=>void>;
}

/*
 ***************************************************
 * Render a FormContext
 ***************************************************
 */
export var CvForm = React.createClass<CvFormProps, CvFormState>({

    mixins: [CvBaseMixin],

    componentDidMount: function () {
    },

    componentWillUnmount: function() {
        const formContext = this.formContext();
        if (formContext && formContext.isDestroyed) {
            (this.eventRegistry() as CvEventRegistry).removeFromCache(CvResourceManager.resourceIdForObject(formContext, this.catavolt()));
        }
    },
    
    getChildContext: function () {
        const ctx = this.getDefaultChildContext();
        ctx.cvContext.scopeCtx.scopeObj = this.formContext();
        return ctx;
    },
    
    getDefaultProps: function() {
        return {
            formContext: null,
            formRenderer: null,
            childFormComponentRenderer:null,
            listComponentRenderer:null,
            detailsComponentRenderer:null,
            mapComponentRenderer:null,
            printMarkupComponentRenderer:null,
            graphComponentRenderer:null,
            stateChangeListeners:[]
        }
    },
    
    render: function () {

        const formContext = this.formContext();
        if (formContext && !formContext.isDestroyed) {
            if (this.props.renderer) {
                return this.props.renderer(this.getChildContext().cvContext);
            } else if (React.Children.count(this.props.children) > 0) {
                return this.props.children
            } else {
                const childComponents:Array<ReactElement<any>> = formContext.childrenContexts.map((context, i) => {
                    const childComponent = this._getChildComponent(context, i);
                    if(childComponent && childComponent.key != null) {
                        return childComponent;
                    } else {
                        return childComponent ? React.cloneElement(childComponent, {key: i}) : null;
                    }
                });
                return this.props.formRenderer ? this.props.formRenderer(this.getChildContext().cvContext, childComponents) : null;
            }
        } else {
            return null;   
        }

    },
    
    refresh: function() {
        this.forceUpdate();    
    },
    
    formContext: function (nextProps) {
        return (nextProps && nextProps.formContext) || this.props.formContext || this.firstInScope(FormContext);
    },
    
    _checkDestroyed: function() {
        const formContext = this.formContext();
        if (formContext && formContext.isDestroyed) {
            const event:CvEvent<CvStateChangeResult> =
            {
                type: CvEventType.STATE_CHANGE,
                resourceId: CvResourceManager.resourceIdForObject(formContext, this.catavolt()),
                eventObj: {source: formContext, type: CvStateChangeType.DESTROYED}
            };
            this.props.stateChangeListeners.forEach(listener=> {
                listener(event)
            });
        }
    },
    
    _getChildComponent: function(context:PaneContext, index:number) {

        let childComponent = null;
        if(context.hasError) {
            Log.error("Failed to load pane with: " + JSON.stringify(context.error));
        } else if(context instanceof FormContext) {
            childComponent = this.props.childFormComponentRenderer ?
                this.props.childFormComponentRenderer(context, this._stateChangeListener, index) : null;
        }else if (context instanceof ListContext) {
            childComponent = this.props.listComponentRenderer ?
                this.props.listComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof DetailsContext) {
            childComponent = this.props.detailsComponentRenderer ?
                this.props.detailsComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof MapContext) {
            childComponent = this.props.mapComponentRenderer ?
                this.props.mapComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof PrintMarkupContext) {
            childComponent = this.props.printMarkupComponentRenderer ?
                this.props.printMarkupComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof GraphContext) {
            childComponent = this.props.graphComponentRenderer ?
                this.props.graphComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof ImagePickerContext) {
            childComponent = this.props.imagePickerComponentRenderer ?
                this.props.imagePickerComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof CalendarContext) {
            childComponent = this.props.calendarComponentRenderer ?
                this.props.calendarComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof BarcodeScanContext) {
            childComponent = this.props.barcodeScanComponentRenderer ?
                this.props.barcodeScanComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof GeoFixContext) {
            childComponent = this.props.geoFixComponentRenderer ?
                this.props.geoFixComponentRenderer(context, this._stateChangeListener, index) : null;
        } else if (context instanceof GeoLocationContext) {
            childComponent = this.props.geoLocationComponentRenderer ?
                this.props.geoLocationComponentRenderer(context, this._stateChangeListener, index) : null;
        } else {
            Log.info('Not yet handling display for ' + context.constructor['name']);
            childComponent = <div>{'Not yet handling display for ' + context.constructor['name']}</div>
        }

        return childComponent;

    },
    
    _stateChangeListener: function(event:CvEvent<CvStateChangeResult>) {
        if(event.eventObj.type === CvStateChangeType.DESTROYED) {
            this._checkDestroyed();
            this.refresh();
        } else {
            this.props.stateChangeListeners.forEach(listener=> {
                listener(event)
            });
        }
    },
    
    
});
