/**
 * Created by rburson on 1/14/16.
 */
import * as React from 'react'
import {CvState, CvProps, CvBaseMixin, CvEvent, CvEventType, CvMessage, CvMessageType} from './catreact-core'
import {
    Prop, PropDef, EntityRec, PropFormatter, PaneContext, Binary, Try, TimeValue, Log,
    ObjUtil, EditorContext
} from 'catavolt-sdk'

export interface CvPropState extends CvState {
    binary:Binary;
}

export interface CvPropProps extends CvProps {

    /**
     * If supplied, this is called when the property is boolean, to allow custom rendering
     */
    booleanRenderer?:(boolVal:boolean) => {};
    /**
     * If supplied, this is called when the property is a binary property, to allow custom rendering
     * The given url may be a 'data' url with image data inline, or it may be a 'remote' url
     * 
     */
    binaryRenderer?:(binaryUrl:string) => {}
    /**
     * className to apply to the property.  wrapperElemProps will override this value
     */
    className?:string,
    /**
     * style to apply to the property.  wrapperElemProps will override this value
     */
    style?:{}
    /**
     * Rendering override.  Simple callback function to access the sdk {Prop} object
     */
    ,
    handler?:(o:Prop) => {};
    /**
     * Function that allows for visibility control.  Accepts sinlge param of sdk {Prop} object.  Should return true
     * if the component should be rendered, false if the component should be hidden.
     */
    isVisible?:(o:Prop) => boolean;
    /**
     * The name of this property.  The given (or enclosing) sdk {EnityRec} will be searched for the sdk {Prop} of this name
     */
    propName:string;
    /**
     * Default to be used if this sdk {Prop} value is null or undefined
     */
    defaultValue?:string;
    /**
     * The sdk {EntityRec} that owns this sdk {Prop}
     */
    entityRec?:EntityRec;
    /**
     * The containing PaneContext
     */
    paneContext?:PaneContext;
    /**
     * The wrapper element name for this prop value (should be html)
     */
    wrapperElemName?:any;
    /**
     * The wrapper element props
     */
    wrapperElemProps?:any;
    /**
     * Force the value of this prop to be the given value
     */
    overrideValue?:string;
    /**
     * Set the named properties' values to the property's final resolved value
     * i.e. 'title' on an html element for a tooltip
     */
    dataPropNames?:Array<string>;
    /**
     * Class name to be set on the image control, if this is for an image.
     */
    imageClassName?:string;
}

/*
 ***************************************************
 * Render a Property
 ***************************************************
 */
export var CvProp = React.createClass<CvPropProps, CvPropState>({

    mixins: [CvBaseMixin],

    componentWillUpdate: function(nextProps, nextState, nextContext) {
        //Log.debug("updating CvProps")
    },

    componentDidMount: function () {
        //Log.debug("mounting CvProps");
        this.refresh();
    },

    componentWillReceiveProps: function (nextProps, nextContext) {
        //reset the loaded state if this is a 'new' prop
        if(this.props.propName !== nextProps.propName ||
            this.entityRec() !== this.entityRec(nextProps, nextContext)) {
            this.loaded = false;
        }
        this.refresh(nextProps, nextContext);
    },

    entityRec: function (nextProps, nextContext) {
        if(nextProps && nextProps.entityRec) {
           return nextProps.entityRec; 
        } else if(nextContext) {
            return this.findEntityRec(nextContext.cvContext.scopeCtx);
        } else {
            return this.findEntityRec();
        }
    },

    entityRecDef: function (nextProps, nextContext) {
        return this.paneContext(nextProps, nextContext).entityRecDef;
    },

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

    getDefaultProps: function () {
        return {
            propName: null,
            defaultValue: null,
            booleanRenderer: null,
            binaryRenderer: null,
            handler: null,
            isVisible: null,
            entityRec: null,
            wrapperElemName: 'span',
            wrapperElemProps: null,
            overrideValue: null,
            style: null,
            className: null,
            renderer: null,
            dataPropNames: []
        }
    },

    getInitialState: function () {
        return {binary: null};
    },

    paneContext: function (nextProps, nextContext) {
        if(nextProps && nextProps.paneContext) {
            return nextProps.paneContext;
        }else if(nextContext) {
            return this.findPaneContext(nextContext.cvContext.scopeCtx);
        } else {
            return this.findPaneContext();
        }
    },

    prop: function (nextProps, nextContext) {
        const propName = nextProps ? nextProps.propName : this.props.propName;
        return this.entityRec(nextProps, nextContext).propAtName(propName)
    },

    propDef: function (nextProps, nextContext) {
        const propName = nextProps ? nextProps.propName : this.props.propName;
        return this.entityRecDef(nextProps, nextContext).propDefAtName(propName);
    },

    render: function () {

        const prop = this.prop();

        if (!prop || (this.props.isVisible && !this.props.isVisible(prop))) {
            return null;
        }

        if (this.props.renderer) {
            return this.props.renderer(this.getChildContext().cvContext);
        } else if (this.props.handler) {
            return this.props.handler(prop);
        } else {
            if (React.Children.count(this.props.children) > 0) {
                return this.props.children
            } else {
                /* setup props for the wrapper element */
                const propDef = this.propDef();
                const nullRenderer = propDef && propDef.isBinaryType;
                let props = {style: this.props.style, className: this.props.className};
                if(this.props.wrapperElemProps) {
                    props = ObjUtil.addAllProps(this.props.wrapperElemProps, props);
                }
                /* overridden value */
                if (this.props.overrideValue != null) {
                    return React.createElement(this.props.wrapperElemName, props, this.props.overrideValue);
                /* missing value */
                } else if (!nullRenderer && (prop.value === null || prop.value === undefined)) {
                    return this.props.defaultValue !== null ?
                        React.createElement(this.props.wrapperElemName, props, this.props.defaultValue) : null;
                } else {
                    const propDef = this.propDef();
                    /* binary property */ 
                    if (propDef && propDef.isBinaryType) {
                        if(this.state.binary) {
                            var dataUrl = this.state.binary.toUrl();
                        }
                        return this.props.binaryRenderer ? this.props.binaryRenderer(dataUrl) :
                            React.createElement(this.props.wrapperElemName, props, '[binary]');

                        /* boolean property */
                    } else if (propDef && propDef.isBooleanType) {
                        return this.props.booleanRenderer ? this.props.booleanRenderer(prop.value) :
                            React.createElement(this.props.wrapperElemName, props, String(prop.value));
                    } else {
                    /* text property */
                        const editorContext:EditorContext = this.paneContext(null, null) as EditorContext;
                        const value = (editorContext && editorContext.isWriteMode) ? PropFormatter.formatForWrite(prop, propDef)
                            : PropFormatter.formatForRead(prop, propDef);
                        /* copy the props rendered value to any attributes that were specified */
                        if(this.props.dataPropNames && this.props.dataPropNames.length > 0) {
                            this.props.dataPropNames.forEach(name=>props[name] = value);
                        }
                        return React.createElement(this.props.wrapperElemName, props, value);
                    }
                }
            }
        }

    },

    refresh: function (nextProps, nextContext) {

        const prop = this.prop(nextProps, nextContext);
        if(prop) {
            const propDef = this.propDef(nextProps, nextContext);
            if (propDef && propDef.isBinaryType) {
                if (prop.value) {
                    //if this is an update and the propName hasn't changed, don't reload the binary
                    if (this.loaded && nextProps && nextProps.propName === this.props.propName) {
                        return;
                    }
                    const paneContext = this.paneContext(nextProps, nextContext);
                    paneContext.binaryAt(this.props.propName, this.entityRec(nextProps, nextContext)).onComplete((binaryTry: Try<Binary>) => {
                        if (binaryTry.isSuccess) {
                            if (this.isMounted()) {
                                this.setState({binary: binaryTry.success});
                            }
                        } else {
                            const event: CvEvent<CvMessage> = {
                                type: CvEventType.MESSAGE,
                                eventObj: {
                                    message: 'Could not load binary property: ' + prop.name,
                                    messageObj: binaryTry.failure,
                                    type: CvMessageType.ERROR
                                }
                            }
                            this.eventRegistry().publish(event, false);
                        }
                        this.loaded = true;
                    });
                } else {
                    // If there is no value, there is no data to read
                    if (this.isMounted()) {
                        this.setState({binary: null});
                    }
                }
            }
        }
    },

});