<template>
    <div :style="{width: data.config && data.config.width}" class="fm-form" :class="'fm-'+formStyleKey">
        <el-form :ref="formRef"
                 :key="formKey"
                 v-if="formShow"
                 :class="{
                            [data.config && data.config.customClass]:  (data.config && data.config.customClass) ? true : false,
                            'no-label-form': data.config && (data.config.labelWidth == 0)
                          }"
                 :size="data.config && data.config.size"
                 :model="models"
                 :rules="rules"
                 :label-position="data.config && data.config.labelPosition"
                 :label-width="data.config && data.config.labelWidth + 'px'">
            
            <template v-for="item in data.list">
                <generate-col-item
                    v-if="item.type == 'grid'"
                    :key="item.key"
                    :model="models"
                    :rules="rules"
                    :element="item"
                    :remote="remote"
                    :blanks="blanks"
                    :display="displayFields"
                    @input-change="onInputChange"
                    :edit="edit"
                    :remote-option="remoteOption"
                    :platform="platform"
                    :preview="preview"
                    :container-key="containerKey"
                    :data-source-value="dataSourceValue"
                    :event-function="eventFunction"
                    :print-read="printRead"
                    :form-component="$refs[formRef]"
                >
                    <template v-slot:[blank.name]="scope" v-for="blank in blanks">
                        <slot :name="blank.name" :model="scope.model"></slot>
                    </template>
                </generate-col-item>
                
                <generate-tab-item
                    v-else-if="item.type == 'tabs'"
                    :key="item.key"
                    :model="models"
                    :rules="rules"
                    :element="item"
                    :remote="remote"
                    :blanks="blanks"
                    :display="displayFields"
                    @input-change="onInputChange"
                    :edit="edit"
                    :remote-option="remoteOption"
                    :platform="platform"
                    :preview="preview"
                    :container-key="containerKey"
                    :data-source-value="dataSourceValue"
                    :event-function="eventFunction"
                    :print-read="printRead"
                    :form-component="$refs[formRef]"
                >
                    <template v-slot:[blank.name]="scope" v-for="blank in blanks">
                        <slot :name="blank.name" :model="scope.model"></slot>
                    </template>
                </generate-tab-item>
                
                <generate-report
                    v-else-if="item.type == 'report'"
                    :key="item.key"
                    :model="models"
                    :rules="rules"
                    :element="item"
                    :remote="remote"
                    :blanks="blanks"
                    :display="displayFields"
                    @input-change="onInputChange"
                    :edit="edit"
                    :remote-option="remoteOption"
                    :platform="platform"
                    :preview="preview"
                    :container-key="containerKey"
                    :data-source-value="dataSourceValue"
                    :event-function="eventFunction"
                    :print-read="printRead"
                    :form-component="$refs[formRef]"
                >
                    <template v-slot:[blank.name]="scope" v-for="blank in blanks">
                        <slot :name="blank.name" :model="scope.model"></slot>
                    </template>
                </generate-report>
    
                <generate-inline
                    v-else-if="item.type == 'inline'"
                    :key="item.key"
                    :model="models"
                    :rules="rules"
                    :element="item"
                    :remote="remote"
                    :blanks="blanks"
                    :display="displayFields"
                    @input-change="onInputChange"
                    :edit="edit"
                    :remote-option="remoteOption"
                    :platform="platform"
                    :preview="preview"
                    :container-key="containerKey"
                    :data-source-value="dataSourceValue"
                    :event-function="eventFunction"
                    :print-read="printRead"
                    :form-component="$refs[formRef]"
                >
                    <template v-slot:[blank.name]="scope" v-for="blank in blanks">
                        <slot :name="blank.name" :model="scope.model"></slot>
                    </template>
                </generate-inline>
                
                <generate-form-item
                    v-else
                    :key="item.key"
                    :models="models"
                    :rules="rules"
                    :widget="item"
                    :remote="remote"
                    :blanks="blanks"
                    :display="displayFields"
                    @input-change="onInputChange"
                    :edit="edit"
                    :remote-option="remoteOption"
                    :platform="platform"
                    :preview="preview"
                    :container-key="containerKey"
                    :serverURL="serverURL"
                    :data-source-value="dataSourceValue"
                    :event-function="eventFunction"
                    :print-read="printRead"
                    :form-component="$refs[formRef]"
                >
                    <template v-slot:[blank.name]="scope" v-for="blank in blanks">
                        <slot :name="blank.name" :model="scope.model"></slot>
                    </template>
                </generate-form-item>
            </template>
        </el-form>
    </div>
</template>

<script>
import GenerateFormItem from './GenerateFormItem'
import GenerateColItem from './GenerateColItem'
import GenerateTabItem from './GenerateTabItem'
import GenerateReport from './GenerateReport'
import GenerateInline from './GenerateInline'
import {loadJs, updateStyleSheets, splitStyleSheets, clearStyleSheets} from '../util/index.js'
import {EventBus} from '../util/event-bus.js'
import _ from 'lodash'
import axios from 'axios'

export default {
    name: 'fm-generate-form',
    components: {
        GenerateFormItem,
        GenerateColItem,
        GenerateTabItem,
        GenerateReport,
        GenerateInline
    },
    props: {
        data: {
            type: Object,
            default: () => ({
                "list": [],
                "config": {
                    "labelWidth": 100,
                    "labelPosition": "right",
                    "size": "small",
                    "customClass": "",
                    "ui": "element",
                    "layout": "horizontal",
                    "labelCol": 3
                }
            })
        },
        remote: {
            type: Object,
            default: () => ({})
        },
        value: {
            type: Object,
            default: () => ({})
        },
        edit: {
            type: Boolean,
            default: true
        },
        printRead: {
            type: Boolean,
            default: false
        },
        serverURL: {
            type: String,
            default: ''
        },
        remoteOption: {
            type: Object,
            default: () => ({})
        },
        preview: {
            type: Boolean,
            default: false
        },
        platform: {
            type: String,
            default: 'pc'
        }
    },
    data() {
        return {
            models: {},
            rules: {},
            blanks: [],
            displayFields: {},
            dataBindFields: [],
            generateShow: false,
            resetModels: {},
            formKey: Math.random().toString(36).slice(-8),
            formStyleKey: Math.random().toString(36).slice(-8),
            formValue: this.value,
            formShow: false,
            formRef: Math.random().toString(36).slice(-8) + 'Form',
            containerKey: Math.random().toString(36).slice(-8),
            dataSourceValue: [],
            eventFunction: {}
        }
    },
    created() {
        this.loadDataSource()
    
        this.loadEvents()
        
        this._initForm()
    },
    mounted() {
        const _this = this
        EventBus.$on('on-change-' + this.containerKey, (value, field, containerKey, item) => {
            if (this.containerKey == containerKey) {
                _this.setData({
                    [field]: value
                },item.type)
            }
    
            _this.$emit('on-change', field, value, _this.models)
            _this.$emit(`on-${field}-change`, value)
        })
        EventBus.$on('on-upload-state-change', (modelKey) => {
            if (_this.$refs[this.formRef]){
                _this.$refs[this.formRef].validateField(modelKey)
            }
        })
    
        this.$nextTick(() => {
            this.eventFunction.mounted && this.eventFunction['mounted']()
        })
    },
    beforeDestroy() {
        let head = '.fm-' + this.formStyleKey + ' '
        clearStyleSheets(head)
        EventBus.$off('on-change-' + this.containerKey)
    },
    methods: {
        _initForm() {
            this.formShow = false
            if (Object.keys(this.data).length) {
                this.generateModel(this.data.list)
            } else {
                this.generateModel([])
            }
            
            this.resetModels = _.cloneDeep(this.models)
            this.models = {...this.models}
            
            this.formShow = true
            
            if (this.data.config && this.data.config.styleSheets) {
                let head = '.fm-' + this.formStyleKey + ' '
                
                updateStyleSheets(splitStyleSheets(this.data.config.styleSheets), head)
            }
        },
        loadEvents () {
            if (this.data.config && this.data.config.eventScript) {
                for (let i = 0; i < this.data.config.eventScript.length; i++) {
                    let currentScript = this.data.config.eventScript[i]
                
                    this.eventFunction[currentScript.key] = Function(currentScript.func).bind(this)
                }
            }
        },
        loadDataSource () {
            if (this.data.config && this.data.config.dataSource) {
                for (let i = 0; i < this.data.config.dataSource.length; i++) {
                    let currentDataSource = this.data.config.dataSource[i]
                
                    if (currentDataSource.auto) {
                        this.sendRequest(currentDataSource.name)
                    }
                }
            }
        },
        sendRequest(name) {
            let currentDataSource = this.data.config.dataSource.find(item => item.name === name)
        
            if (currentDataSource) {
                let options = {
                    method: currentDataSource.method,
                    url: currentDataSource.url,
                    headers: currentDataSource.headers,
                    params: currentDataSource.params
                }
            
                //请求发送前处理函数
                if (currentDataSource.requestFunc) {
                    const requestDynamicFunc = Function('config', currentDataSource.requestFunc).bind(this)
                
                    options = requestDynamicFunc(options)
                }
            
                axios(options).then(res => {
                
                    let data = res
                
                    if (currentDataSource.responseFunc) {
                        const dynamicFunc = Function('res', currentDataSource.responseFunc).bind(this)
                    
                        data = dynamicFunc(res.data)
                    }
                
                    let sourceValue = this.dataSourceValue.find(item => item.key === currentDataSource.key)
                
                    if (sourceValue) {
                        sourceValue.value = data
                    } else {
                        this.dataSourceValue.push({
                            key: currentDataSource.key,
                            value: data
                        })
                    }
                }).catch((error) => {
                    //请求错误处理函数
                    if (currentDataSource.errorFunc) {
                        const errorDynamicFunc = Function('error', currentDataSource.errorFunc).bind(this)
                    
                        errorDynamicFunc(error)
                    }
                })
            }
        },
        generateModel(genList) {
            for (let i = 0; i < genList.length; i++) {
                if (genList[i].type === 'grid') {
                    this.displayFields[genList[i].model] = !genList[i].options.hidden
                    
                    genList[i].columns.forEach(item => {
                        this.generateModel(item.list)
                    })
                } else if (genList[i].type === 'tabs') {
                    genList[i].tabs.forEach(item => {
                        this.generateModel(item.list)
                    })
                    
                    this.displayFields[genList[i].model] = !genList[i].options.hidden
                } else if (genList[i].type === 'report') {
                    genList[i].rows.forEach(row => {
                        row.columns.forEach(column => {
                            this.generateModel(column.list)
                        })
                    })
                    this.displayFields[genList[i].model] = !genList[i].options.hidden
                } else if (genList[i].type === 'inline') {
                    this.generateModel(genList[i].list)
                    this.displayFields[genList[i].model] = !genList[i].options.hidden
                } else {
                    if (Object.keys(this.formValue).indexOf(genList[i].model) >= 0) {
                        // 处理老版本没有dataBind值的情况，默认绑定数据
                        if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
                            this.models[genList[i].model] = this.formValue[genList[i].model]
                            this.dataBindFields.push(genList[i].model)
                        }
                        
                        this.displayFields[genList[i].model] = !genList[i].options.hidden
                        
                        if (genList[i].type === 'blank') {
                            this.blanks.push({
                                name: genList[i].model
                            })
                        }
                        
                    } else {
                        if (genList[i].type === 'blank') {
                            // bound the default value
                            if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
                                this.models[genList[i].model] = genList[i].options.defaultType === 'String' ? '' : (genList[i].options.defaultType === 'Object' ? {} : [])
                                this.dataBindFields.push(genList[i].model)
                            }
                            this.displayFields[genList[i].model] = !genList[i].options.hidden
                            
                            this.blanks.push({
                                name: genList[i].model
                            })
                        } else {
                            console.log(genList[i].options.defaultValue)
                            if ((Object.keys(genList[i].options).indexOf('dataBind') < 0 || genList[i].options.dataBind) && genList[i].key && genList[i].model) {
                                if((genList[i].type === 'timerange' || genList[i].type === 'daterange') && genList[i].options.defaultValue){
                                    this.models[genList[i].model+'Min'] = genList[i].options.defaultValue[0]
                                    this.models[genList[i].model+'Max'] = genList[i].options.defaultValue[1]
                                } else if (genList[i].options.defaultValue === undefined){
                                    this.models[genList[i].model+'Min'] = ''
                                    this.models[genList[i].model+'Max'] = ''
                                }
                                this.models[genList[i].model] = genList[i].options.defaultValue
                                this.dataBindFields.push(genList[i].model)
                            }
                            this.displayFields[genList[i].model] = !genList[i].options.hidden
                        }
                    }
    
    
                   
                    genList[i].tableColumns && genList[i].tableColumns.length && genList[i].tableColumns.forEach(item => {
                        if (item.type === 'blank') {
                            this.blanks.push({
                                name: item.model
                            })
                        }
                        // 处理 rules
                        if (this.rules[`${genList[i].model}.${item.model}`]) {
                            this.rules[`${genList[i].model}.${item.model}`] = [
                                ...this.rules[`${genList[i].model}.${item.model}`],
                                ...(item.rules ? item.rules.map(im => {
                                    if (im.pattern) {
                                        return {...im, pattern: eval(im.pattern)}
                                    } else if (im.func) {
    
                                        const validatorFunc = Function('rule', 'value', 'callback', im.func).bind(this)
    
                                        return {...im, validator: validatorFunc}
                                    } else {
                                        return {...im}
                                    }
                                }) : [])
                            ]
                        } else {
                            this.rules[`${genList[i].model}.${item.model}`] = [
                                ...(item.rules ? item.rules.map(im => {
                                    if (im.pattern) {
                                        return {...im, pattern: eval(im.pattern)}
                                    } else if (im.func) {
                                        const validatorFunc = Function('rule', 'value', 'callback', im.func).bind(this)
    
                                        return {...im, validator: validatorFunc}
                                    } else {
                                        return {...im}
                                    }
                                }) : [])
                            ]
                        }
                    })

                    if (this.rules[genList[i].model]) {
                        
                        this.rules[genList[i].model] = [...this.rules[genList[i].model], ...(genList[i].rules ? genList[i].rules.map(item => {
                            if (item.pattern) {
                                return {...item, pattern: eval(item.pattern)}
                            } else if (item.func) {
                                const validatorFunc = Function('rule', 'value', 'callback', item.func).bind(this)
    
                                return {...item, validator: validatorFunc}
                            } else {
                                return {...item}
                            }
                        }) : [])]
                    } else {
                        
                        this.rules[genList[i].model] = [...(genList[i].rules ? genList[i].rules.map(item => {
                            if (item.pattern) {
                                return {...item, pattern: eval(item.pattern)}
                            } else if (item.func) {
                                const validatorFunc = Function('rule', 'value', 'callback', item.func).bind(this)
    
                                return {...item, validator: validatorFunc}
                            } else {
                                return {...item}
                            }
                        }) : [])]
                    }
                    
                    if (genList[i].type === 'uploader'){
                        let uploadFinished = (rule, value, callback) => {
                            if (value && value === '\"uploading\"'){
                                return callback(new Error('还有附件未完成上传, 请稍后...'))
                            }else{
                                return callback()
                            }
                        }
                        this.rules[genList[i].model].push({validator: uploadFinished, trigger: 'blur'})
                    }
                }
            }
        },
        _setDisabled(genList, fields, disabled) {
            for (let i = 0; i < genList.length; i++) {
                if (genList[i].type === 'grid') {
                    genList[i].columns.forEach(item => {
                        this._setDisabled(item.list, fields, disabled)
                    })
                } else if (genList[i].type === 'tabs') {
                    genList[i].tabs.forEach(item => {
                        this._setDisabled(item.list, fields, disabled)
                    })
                } else if (genList[i].type === 'report') {
                    genList[i].rows.forEach(row => {
                        row.columns.forEach(column => {
                            this._setDisabled(column.list, fields, disabled)
                        })
                    })
                } else if (genList[i].type === 'inline') {
                    this._setDisabled(genList[i].list, fields, disabled)
                } else {
                    if (fields.indexOf(genList[i].model) >= 0) {
                        
                        this.$set(genList[i].options, 'disabled', disabled)
                    }
                }
            }
        },
        _updateClassName(genList, fields, className, updateType) {
            for (let i = 0; i < genList.length; i++) {
                if (genList[i].type === 'grid') {
                    genList[i].columns.forEach(item => {
                        this._updateClassName(item.list, fields, className, updateType)
                    })
                } else if (genList[i].type === 'tabs') {
                    genList[i].tabs.forEach(item => {
                        this._updateClassName(item.list, fields, className, updateType)
                    })
                } else if (genList[i].type === 'report') {
                    genList[i].rows.forEach(row => {
                        row.columns.forEach(column => {
                            this._updateClassName(column.list, fields, className, updateType)
                        })
                    })
                } else if (genList[i].type === 'inline') {
                    this._updateClassName(genList[i].list, fields, className, updateType)
                } else {
                    if (fields.indexOf(genList[i].model) >= 0) {
                        
                        if (updateType == 'add' && !genList[i].options.customClass.split(' ').includes(className)) {
                            this.$set(genList[i].options, 'customClass', [...genList[i].options.customClass.split(' '), className].join(' '))
                        }
                        
                        if (updateType == 'remove' && genList[i].options.customClass.split(' ').includes(className)) {
                            let originArray = genList[i].options.customClass.split(' ')
                            originArray.splice(originArray.findIndex(item => item == className), 1)
                            this.$set(genList[i].options, 'customClass', originArray.join(' '))
                        }
                    }
                }
            }
        },
        _setOptions (genList, fields, opts) {
            for (let i = 0; i < genList.length; i++) {
                if (genList[i].type === 'grid') {
                    genList[i].columns.forEach(item => {
                        this._setOptions(item.list, fields, opts)
                    })
                } else if (genList[i].type === 'tabs') {
                    genList[i].tabs.forEach(item => {
                        this._setOptions(item.list, fields, opts)
                    })
                } else if (genList[i].type === 'report') {
                    genList[i].rows.forEach(row => {
                        row.columns.forEach(column => {
                            this._setOptions(column.list, fields, opts)
                        })
                    })
                } else if (genList[i].type === 'inline') {
                    this._setOptions(genList[i].list, fields, opts)
                } else {
                    if (fields.indexOf(genList[i].model) >= 0) {
                    
                        Object.keys(opts).forEach(key => {
                            this.$set(genList[i].options, key, opts[key])
                        })
                    }
                }
            }
        },
        getData(isValidate = true) {
            return new Promise((resolve, reject) => {
                if (isValidate) {
                    this.$refs[this.formRef].validate(valid => {
                        if (valid) {
                            resolve(JSON.parse(JSON.stringify(this.models)))
                        } else {
                            reject(new Error(this.$t('fm.message.validError')).message)
                        }
                    })
                } else {
                    resolve(JSON.parse(JSON.stringify(this.models)))
                }
            })
        },
        reset() {
            
            this.setData(_.cloneDeep(this.resetModels))
            this.$nextTick(() => {
                this.$refs[this.formRef].clearValidate()
            })
        },
        onInputChange(value, field) {
            this.$emit('on-change', field, value, this.models)
            this.$emit(`on-${field}-change`, value)
        },
        display(fields) {
            Object.keys(this.displayFields).forEach(key => {
                if (fields.indexOf(key) >= 0) {
                    this.$set(this.displayFields, key, true)
                }
            })
            this.displayFields = {...this.displayFields}
        },
        hide(fields) {
            
            Object.keys(this.displayFields).forEach(key => {
                
                if (fields.indexOf(key) >= 0) {
                    this.$set(this.displayFields, key, false)
                }
            })
            
            this.displayFields = {...this.displayFields}
        },
        disabled(fields, disabled) {
            this._setDisabled(this.data.list, fields, disabled)
        },
        addClassName(fields, className) {
            this._updateClassName(this.data.list, fields, className, 'add')
        },
        removeClassName(fields, className) {
            this._updateClassName(this.data.list, fields, className, 'remove')
        },
        refresh() {
            this.loadDataSource()
    
            this.loadEvents()
    
            this._initForm()
        },
        setData(value,type) {
            console.log(value)
            console.log(type)
            Object.keys(value).forEach(item => {
                if (item === 'timerange' || type === 'timerange' || item === 'daterange' || type === 'daterange') {
                    if (value[item] !== null) {
                        this.$set(this.models, item+'Min', value[item][0])
                        this.$set(this.models, item+'Max', value[item][1])
                    } else {
                        this.$set(this.models, item+'Min', '')
                        this.$set(this.models, item+'Max', '')
                    }
                }
                this.$set(this.models, item, value[item])
            })
        },
        _findComponentDownward(context, name, result) {
            let children = context.$children
            
            if (result) {
                return result
            }
            
            if (children.length == 0) {
                result = null
            } else {
                for (let i = 0; i < children.length; i++) {
                    let currentComponent = children[i]
                    
                    let currentRefArray = Object.keys(currentComponent.$refs)
                    
                    if (currentRefArray.indexOf(name) >= 0) {
                        
                        result = currentComponent.$refs[name]
                        break
                    } else {
                        result = this._findComponentDownward(currentComponent, name, result)
                    }
                }
            }
            return result
        },
        getComponent(refName) {
            return this._findComponentDownward(this, refName, null)
        },
        getValues () {
            return this.models
        },
        getValue (fieldName) {
            return this.models[fieldName]
        },
        setRules (field, rules) {
        
            this.$set(this.rules, field, [...rules])
        
            this.$refs[this.formRef].clearValidate(field)
        },
        setOptions (fields, options) {
            this._setOptions(this.data.list, fields, options)
        }
    }
}
</script>

<style lang="scss">
</style>
