<template>
<div id="example-panel" :class="shared.computedOptionExampleLayout + '-layout'">
    <h2>{{$t('example.title')}}</h2>
    <p class="intro">{{ shared.allOptionExamples ? $t('example.intro') : $t('example.noExample')}}</p>
    <div class="preview-and-code" v-if="shared.currentExampleOption">
        <div class="preview-main"></div>
        <div class="example-code">
            <div class="codemirror-main"></div>
        </div>
        <el-alert
            :title="$t('example.setOptionError')"
            v-if="hasError"
            type="error"
        >
        </el-alert>
    </div>
    <div class="toolbar">
        <el-select size='mini' v-if="shared.allOptionExamples" class="example-list" v-model="shared.currentExampleName"
            :popper-append-to-body="false">
            <el-option v-for="item in shared.allOptionExamples"
                :key="item.name"
                :value="item.name"
                :label="shared.locale === 'en' ? item['title-en'] : item.title"
            ></el-option>
        </el-select>
        <el-button v-if="shared.currentExampleOption"
            type="primary"
            icon="el-icon-refresh"
            size="mini"
            :title="$t('example.refresh')"
            @click="refreshForce"></el-button>
        <el-button
            style="margin-left: 0"
            type="primary"
            icon="el-icon-s-operation"
            size="mini"
            :title="$t('example.changeLayout')"
            v-popover:changeLayoutPopover></el-button>
        <el-button size='mini' circle icon="el-icon-close" @click="closeExamplePanel"></el-button>
    </div>
    <el-popover
        ref="changeLayoutPopover"
        placement="bottom"
        trigger="click"
        v-model="showChangeLayoutPopover">
        <div class="example-change-layout">
            <div class="layout-title"><i class="el-icon-s-operation"></i>{{$t('example.changeLayout')}}</div>
            <div class="layout-mode">
                <el-radio-group v-model="shared.optionExampleLayout" @change="changeLayout" size="mini">
                    <el-radio-button
                        v-for="layout in optionExampleLayouts"
                        :key="layout"
                        :label="layout">{{$t('example.layout.' + layout)}}</el-radio-button>
                </el-radio-group>
            </div>
        </div>
    </el-popover>
</div>
</template>

<script>

// Remarks:
// 代码不能编辑，可以跳转到 examples 带上 base64，在 examples 页面编辑

import {store, getPagePath, updateOptionExampleLayout, optionExampleLayouts} from '../store';
import CodeMirror from 'codemirror';
import 'codemirror/lib/codemirror.css';
// import 'codemirror/theme/paraiso-dark.css';
import 'codemirror/theme/dracula.css';
// import 'codemirror/mode/javascript/javascript.js'
import beautifier from 'js-beautify';
import throttle from 'lodash.throttle';
import arrayDiff from 'zrender/lib/core/arrayDiff';
import scrollIntoView from 'scroll-into-view';
import {ECHARTS_LIB} from '../config';

let echartsLoadPromise;

function fetchECharts() {
    return echartsLoadPromise || (echartsLoadPromise = new Promise(function (resolve) {
        const script = document.createElement('script');
        script.src = ECHARTS_LIB;
        script.async = true;
        script.onload = function () {
            resolve();
            echartsLoadPromise = null;
        }
        document.body.appendChild(script);
    }));
}

function diffUpdateCode(oldCode, newCode, cmInstance) {
    const oldLines = oldCode.split(/\n/);
    const newLines = newCode.split(/\n/);
    const diff = arrayDiff(oldLines, newLines);

    const changedLines = [];

    // Remove lines from bottom to top so the line number won't be changed.
    for (let i = diff.length - 1; i >= 0; i--) {
        const item = diff[i];
        if (item.removed) {
            for (let k = item.count - 1; k >= 0; k--) {
                const idx = item.indices[k];
                cmInstance.replaceRange(
                    '', {line: idx, ch: 0}, {line: idx + 1, ch: 0}
                );
            }
        }
    }
    for (let i = 0; i < diff.length; i++) {
        const item = diff[i];
        if (item.added) {
            for (let k = 0; k < item.count; k++) {
                const idx = item.indices[k];
                cmInstance.replaceRange(
                    newLines[idx] + '\n', {line: idx, ch: 0}
                );
                changedLines.push(idx);
            }
        }
    }

    changedLines.forEach(function (idx) {
        cmInstance.addLineClass(idx, 'wrap', 'option-changed');
    });

    if (diff.length) {
        setTimeout(() => {
            cmInstance.scrollIntoView({
                line: changedLines[0],
                ch: 0
            }, cmInstance.getWrapperElement().clientHeight - 50);
        }, 20);
    }

    return changedLines;
}

function updateOption(option, isRefreshForce) {
    if (this.shared.currentExampleName !== this.lastUpdateExampleName) {
        this.lastUpdateExampleName = this.shared.currentExampleName;
        // Refresh all if example base option is changed.
        this.refreshForce();
        return;
    }

    const viewport = this.$el.querySelector('.preview-main');
    if (!viewport) {
        return;
    }

    // Clear error msg.
    this.hasError = false;
    if (typeof echarts === 'undefined') {
        // TODO Put fetch charts when component is initialized.
        fetchECharts().then(() => {
            if (!this.echartsInstance) {
                this.chartInstance = echarts.init(viewport);
            }
            if (this.shared.cleanMode) {
                this.chartInstance.clear();
            }
            this.chartInstance.setOption(option, true);
        })
    }
    else {
        if (!this.echartsInstance) {
            this.chartInstance = echarts.init(viewport);
        }
        try {
            if (this.shared.cleanMode) {
                this.chartInstance.clear();
            }
            this.chartInstance.setOption(option, true);
        }
        catch (e) {
            // 一些属性切换的时候可能会出现一些位置的错误
            console.error(e);
            this.hasError = true;
        }
    }

    if (!this.cmInstance) {
        this.cmInstance = CodeMirror(this.$el.querySelector('.codemirror-main'), {
            value: this.formattedOptionCodeStr,
            mode: 'javascript',
            // theme: 'paraiso-dark',
            theme: 'dracula',
            readOnly: true
        });
    }
    else {
        // TODO: Highlight the diff lines.
        // TODO: Only change the changed line. optimize
        const oldCode = this.cmInstance.getValue();
        const newCode = this.formattedOptionCodeStr;

        if (this.oldHighlightedLines) {
            this.oldHighlightedLines.forEach((idx) => {
                this.cmInstance.removeLineClass(idx, 'wrap', 'option-changed');
            });
        }

        if (!isRefreshForce) {
            this.oldHighlightedLines = diffUpdateCode(oldCode, newCode, this.cmInstance);
        }
        else {
            this.cmInstance.setValue(newCode);
            this.oldHighlightedLines = [];
        }
    }

    this.lastUpdateExampleName = this.shared.currentExampleName;
}

export default {

    data() {
        return {
            shared: store,

            hasError: false,

            lastUpdateExampleName: '',

            oldHighlightedLines: [],

            showChangeLayoutPopover: false,

            optionExampleLayouts
        };
    },

    mounted() {
        // TODO use css?
        this.resize = this.resize.bind(this);
        window.addEventListener('resize', this.resize);
        this.resize();

        if (this.shared.currentExampleOption) {
            this.updateOptionThrottled(this.shared.currentExampleOption);
        }

        if (this.shared.allOptionExamples) {
            this.shared.currentExampleName = this.shared.allOptionExamples[0].name;
        }
        else {
            this.shared.currentExampleName = ''
        }
    },

    destroyed() {
        if (this.chartInstance) {
            this.chartInstance.dispose();
            this.chartInstance = null;
        }
        window.removeEventListener('resize', this.resize);
    },

    watch: {
        'shared.currentExampleOption'(newVal) {
            if (newVal) {
                this.updateOptionThrottled(newVal);
            }
        },

        'shared.allOptionExamples'(newVal) {
            // Use the first example as default.
            if (newVal) {
                this.shared.currentExampleName = newVal[0].name;
            }
            else {
                this.shared.currentExampleName = '';
            }
        },
        'shared.currentExampleName'(newVal) {
            this.changeExample(newVal);
        }
    },

    methods: {
        updateOption,

        updateOptionThrottled: throttle(updateOption, 300, {
            leading: false
        }),

        resize() {
            const examplePanel = this.$el;
            const previewMain = examplePanel.querySelector('.preview-main');
            if (this.shared.computedOptionExampleLayout !== 'right') {
                examplePanel.style.height = (window.innerHeight * 0.5 - 60) + 'px';
                examplePanel.style.width = 'auto';
            }
            else {
                examplePanel.style.width = examplePanel.parentNode.clientWidth * 0.45 + 'px';
                examplePanel.style.height = 'auto';
            }
            if (this.chartInstance) {
                this.chartInstance.resize();
            }
        },

        refreshForce: function () {
            // Dispose first
            if (this.shared.currentExampleOption) {
                if (this.chartInstance) {
                    this.chartInstance.dispose();
                    this.chartInstance = null;
                }
                this.updateOption(this.shared.currentExampleOption, true);
            }
        },

        closeExamplePanel() {
            this.shared.showOptionExample = false;
        },

        changeExample(exampleName) {
            const example = this.shared.allOptionExamples &&
                this.shared.allOptionExamples.find(item => item.name === exampleName);
            if (!example) {
                this.shared.currentExampleOption = null;
                return false;
            }
            const code = example.code;

            try {
                const func = new Function(code + '\n return option');
                this.shared.currentExampleOption = Object.freeze(func());
            }
            catch(e) {
                console.error(e);
                console.log(code);
            }
        },

        changeLayout(layout) {
            this.showChangeLayoutPopover = false;
            updateOptionExampleLayout(layout);
            this.$nextTick(() => {
                this.resize();
            });
        }
    },

    computed: {
        optionCodeStr() {
            const optStr = JSON.stringify(this.shared.currentExampleOption, function (key, value) {
                if (typeof value === 'function') {
                    return "__functionstart__"
                        + value.toString().replace(/\n/g, '__newline__')    // avoid \n being escaped by JSON.stringify
                        + "__functionend__";
                }
                return value;
            });
            return `option = ${optStr}`;
        },

        formattedOptionCodeStr() {
            return beautifier.js(this.optionCodeStr
                .replace(/"(\w+)"\s*:/g, '$1:')
                .replace(/"__functionstart__/g, "")
                .replace(/__functionend__"/g, "")
                // newline from function
                .replace(/__newline__/g, '\n'), {
                indent_size: 2
            });
        }
    }
}
</script>

<style lang="scss">

#example-panel {
    position: fixed;
    box-shadow: 0 0 20px rgba(0, 0, 0, 0.1);
    padding: 10px 0px;
    text-align: left;
    background: #fff;

    // transition: width 500ms cubic-bezier(0.215, 0.610, 0.355, 1);


    // background: #162436;
    // border-left: 1px solid #ddd;

    h2 {
        font-weight: normal;
        font-size: 20px;
        color: #333;
        padding-left: 20px;
        font-weight: bold;
        margin: 5px 0;
    }

    p.intro {
        color: #aaa;
        padding-left: 20px;
        margin: 5px 0;
        font-size: 12px;
    }


    .preview-and-code {
        position: absolute;
        top: 75px;
        bottom: 0;
        left: 0;
        right: 0;
    }

    .el-alert {
        position: absolute;
        top: 0;
    }
    .preview-main {
        position: relative;
        padding: 0 10px;
        background: #fefefe;
        box-sizing: border-box;
    }

    .example-code {
        position: relative;

        .codemirror-main {
            position: absolute;
            left: 10px;
            top: 10px;
            right: 10px;
            bottom: 15px;
            box-shadow: -5px -5px 15px rgba(0, 0, 0, 0.1);
            .CodeMirror {
                height: 100%;
                overflow-y: scroll;
                border-radius: 6px;
                .CodeMirror-scroll {
                    padding: 15px;
                }
                font-family: 'Source Code Pro', monospace;
                font-size: 13px;

                ::-webkit-scrollbar-thumb {
                    width: 8px;
                    min-height: 15px;
                    background: rgba(255, 255, 255, 0.3) !important;
                    -webkit-transition: all 0.3s ease-in-out;
                    transition: all 0.3s ease-in-out;
                    border-radius: 2px
                }

                .option-changed {
                    background: rgba(255, 255, 255, 0.1);
                    // border-left: 3px solid #32dde6;
                }

                .CodeMirror-cursor {
                    display: none;
                }
            }
        }
    }

    .toolbar {
        position: absolute;
        top: 15px;
        right: 10px;

        .example-list {
            width: 180px;
        }
    }


    &.right-layout {
        bottom: 0;
        top: 40px;
        right: 10px;

        .preview-main {
            width: 100%;
            height: 50%;
        }
        .example-code {
            width: 100%;
            height: 50%;
        }
    }

    &.bottom-layout {
        left: 300px;
        bottom: 0;
        right: 10px;

        .preview-main {
            width: 50%;
            height: 100%;
            float: left;
        }
        .example-code {
            width: 50%;
            height: 100%;
            float: left;
        }
    }


    &.top-layout {
        left: 300px;
        // dev
        // top: 40px;
        top: 50px;
        right: 10px;

        .preview-main {
            width: 50%;
            height: 100%;
            float: left;
        }
        .example-code {
            width: 50%;
            height: 100%;
            float: left;
        }
    }
}

.example-change-layout {
    .layout-title > i {
        font-size: 14px;
        margin-right: 5px;
    }
    .layout-mode {
        margin-top: 10px;
    }
}

</style>