import * as _ from 'lodash'
import { $win, AUTHOR, logger, NAME, VERSION } from './config'
import { MindMapMain } from './mind-map-main'
import { MindMapMind } from './mind-map-mind'
import { MindMapNode } from './mind-map-node'

function getBasicMind (source, formatType: 'nodeTree' | 'node_array') {
    const df = customizeFormat[formatType]
    const mind = new MindMapMind()
    mind.name = _.get(source, 'meta.name', NAME)
    mind.author = _.get(source, 'meta.author', AUTHOR)
    mind.version = _.get(source, 'meta.version', VERSION)
    df._parse(mind, source.data)
    return mind
}

export const customizeFormat = {
    config: {
        selectable: true,
    },
    setSelectable (val) {
        customizeFormat.config = {
            ...customizeFormat.config,
            selectable: val,
        }
    },
    nodeTree: {
        example: {
            meta: {
                name: NAME,
                author: AUTHOR,
                version: VERSION,
            },
            format: 'nodeTree',
            data: { id: 'root', topic: 'Main Node' },
        },
        getMind: function (source) {
            return getBasicMind(source, 'nodeTree')
        },
        getData: function (mind) {
            const df = customizeFormat.nodeTree
            let json = { meta: {}, format: '', data: {} }
            json.meta = {
                name: mind.name,
                author: mind.author,
                version: mind.version,
            }
            json.format = 'nodeTree'
            json.data = df._buildNode(mind.root)
            return json
        },

        _parse: function (mind, node_root) {
            const df = customizeFormat.nodeTree
            const data = df._extractData(node_root)
            mind.setRoot(node_root.id, node_root.topic, data)
            if ('children' in node_root) {
                const children = node_root.children
                for (let i = 0; i < children.length; i++) {
                    df._extractSubNode(mind, mind.root, children[i])
                }
            }
        },

        _extractData: function (node_json) {
            const data = {}
            for (let k in node_json) {
                if (k === 'id' ||
                    k === 'topic' ||
                    k === 'children' ||
                    k === 'direction' ||
                    k === 'expanded' ||
                    k === 'selectedType') {
                    continue
                }
                if (k === 'backgroundColor') {
                    data['background-color'] = node_json[k]
                } else {
                    data[k] = node_json[k]
                }
            }
            return data
        },

        _extractSubNode: function (mind, node_parent, node_json) {
            const df = customizeFormat.nodeTree
            const data = df._extractData(node_json)
            let d = null
            if (node_parent.isroot) {
                d = node_json.direction === 'left' ? MindMapMain.direction.left : MindMapMain.direction.right
            }
            const node = mind.addNode(node_parent, node_json.id, node_json.topic, data, null, d, node_json.expanded, node_json.selectedType, customizeFormat.config.selectable)
            if ('children' in node_json) {
                const children = node_json.children
                for (let i = 0; i < children.length; i++) {
                    df._extractSubNode(mind, node, children[i])
                }
            }
        },

        _buildNode: function (node) {
            const df = customizeFormat.nodeTree
            if (!(node instanceof MindMapNode)) {
                return
            }
            const o = {
                id: node.id,
                topic: node.topic,
                direction: '',
                children: [],
                selectedType: node.selectedType,
                isCreated: node.isCreated,
                isroot: node.isroot,
                expanded: node.expanded,
            }
            if (!!node.parent && node.parent.isroot) {
                o.direction = node.direction === MindMapMain.direction.left ? 'left' : 'right'
            }
            if (node.data != null) {
                const node_data = node.data
                for (let k in node_data) {
                    o[k] = node_data[k]
                }
            }
            const children = node.children
            if (children.length > 0) {
                o.children = []
                for (let i = 0; i < children.length; i++) {
                    o.children.push(df._buildNode(children[i]))
                }
            }
            return o
        },
    },

    node_array: {
        example: {
            meta: {
                name: NAME,
                author: AUTHOR,
                version: VERSION,
            },
            format: 'node_array',
            data: [
                { id: 'root', topic: 'Main Node', isroot: true },
            ],
        },

        getMind: function (source) {
            return getBasicMind(source, 'node_array')
        },

        getData: function (mind) {
            const df = customizeFormat.node_array
            const json = {
                meta: {},
                format: '',
                data: [],
            }
            json.meta = {
                name: mind.name,
                author: mind.author,
                version: mind.version,
            }
            json.format = 'node_array'
            json.data = []
            df._array(mind, json.data)
            return json
        },

        _parse: function (mind, node_array) {
            const df = customizeFormat.node_array
            const narray = node_array.slice(0)
            // reverse array for improving looping performance
            narray.reverse()
            const root_id = df._extractRoot(mind, narray)
            if (!!root_id) {
                df._extractSubNode(mind, root_id, narray)
            } else {
                logger.error('root node can not be found')
            }
        },

        _extractRoot: function (mind, node_array) {
            const df = customizeFormat.node_array
            let i = node_array.length
            while (i--) {
                if ('isroot' in node_array[i] && node_array[i].isroot) {
                    const root_json = node_array[i]
                    const data = df._extractData(root_json)
                    mind.setRoot(root_json.id, root_json.topic, data)
                    node_array.splice(i, 1)
                    return root_json.id
                }
            }
            return null
        },

        _extractSubNode: function (mind, parentid, node_array) {
            const df = customizeFormat.node_array
            let i = node_array.length
            let node_json = null
            let data = null
            let extract_count = 0
            while (i--) {
                node_json = node_array[i]
                if (node_json.parentid === parentid) {
                    data = df._extractData(node_json)
                    let d = null
                    const node_direction = node_json.direction
                    if (!!node_direction) {
                        d = node_direction === 'left' ? MindMapMain.direction.left : MindMapMain.direction.right
                    }
                    mind.addNode(parentid, node_json.id, node_json.topic, data, null, d, node_json.expanded)
                    node_array.splice(i, 1)
                    extract_count++
                    const sub_extract_count = df._extractSubNode(mind, node_json.id, node_array)
                    if (sub_extract_count > 0) {
                        // reset loop index after extract subordinate node
                        i = node_array.length
                        extract_count += sub_extract_count
                    }
                }
            }
            return extract_count
        },

        _extractData: function (node_json) {
            const data = {}
            for (const k in node_json) {
                if (k === 'id' || k === 'topic' || k === 'parentid' || k === 'isroot' || k === 'direction' || k === 'expanded') {
                    continue
                }
                data[k] = node_json[k]
            }
            return data
        },

        _array: function (mind, node_array) {
            const df = customizeFormat.node_array
            df._arrayNode(mind.root, node_array)
        },

        _arrayNode: function (node, node_array) {
            const df = customizeFormat.node_array
            if (!(node instanceof MindMapNode)) {
                return
            }
            const o = {
                id: node.id,
                topic: node.topic,
                parentid: '',
                isroot: false,
                direction: '',
                expanded: node.expanded,
            }
            if (!!node.parent) {
                o.parentid = node.parent.id
            }
            if (node.isroot) {
                o.isroot = true
            }
            if (!!node.parent && node.parent.isroot) {
                o.direction = node.direction === MindMapMain.direction.left ? 'left' : 'right'
            }
            if (node.data != null) {
                const node_data = node.data
                for (const k in node_data) {
                    o[k] = node_data[k]
                }
            }
            node_array.push(o)
            const ci = node.children.length
            for (let i = 0; i < ci; i++) {
                df._arrayNode(node.children[i], node_array)
            }
        },
    },

    freemind: {
        example: {
            meta: {
                name: NAME,
                author: AUTHOR,
                version: VERSION,
            },
            format: 'freemind',
            data: '<map version=\"1.0.1\"><node ID=\"root\" TEXT=\"freemind Example\"/></map>',
        },
        getMind: function (source) {
            const df = customizeFormat.freemind
            const mind = new MindMapMind()
            mind.name = _.get(source, 'meta.name', NAME)
            mind.author = _.get(source, 'meta.author', AUTHOR)
            mind.version = _.get(source, 'meta.version', VERSION)
            const xml = source.data
            const xml_doc = df._parseXml(xml)
            const xml_root = df._findRoot(xml_doc)
            df._loadNode(mind, null, xml_root)
            return mind
        },

        getData: function (mind) {
            const df = customizeFormat.freemind
            const json = { meta: {}, format: '', data: '' }
            json.meta = {
                name: mind.name,
                author: mind.author,
                version: mind.version,
            }
            json.format = 'freemind'
            const xmllines = []
            xmllines.push('<map version=\"1.0.1\">')
            df._buildMap(mind.root, xmllines)
            xmllines.push('</map>')
            json.data = xmllines.join(' ')
            return json
        },

        _parseXml: function (xml) {
            let xml_doc = null
            if ($win.DOMParser) {
                const parser = new DOMParser()
                xml_doc = parser.parseFromString(xml, 'text/xml')
            } else { // Internet Explorer
                xml_doc = new ActiveXObject('Microsoft.XMLDOM')
                xml_doc.async = false
                xml_doc.loadXML(xml)
            }
            return xml_doc
        },

        _findRoot: function (xml_doc) {
            const nodes = xml_doc.childNodes
            let node = null
            let root = null
            let n = null
            for (let i = 0; i < nodes.length; i++) {
                n = nodes[i]
                if (n.nodeType === 1 && n.tagName === 'map') {
                    node = n
                    break
                }
            }
            if (!!node) {
                const ns = node.childNodes
                node = null
                for (let i = 0; i < ns.length; i++) {
                    n = ns[i]
                    if (n.nodeType === 1 && n.tagName === 'node') {
                        node = n
                        break
                    }
                }
            }
            return node
        },

        _loadNode: function (mind, parent_id, xml_node) {
            const df = customizeFormat.freemind
            const node_id = xml_node.getAttribute('ID')
            let node_topic = xml_node.getAttribute('TEXT')
            // look for richcontent
            if (node_topic == null) {
                const topic_children = xml_node.childNodes
                let topic_child = null
                for (let i = 0; i < topic_children.length; i++) {
                    topic_child = topic_children[i]
                    // logger.debug(topic_child.tagName);
                    if (topic_child.nodeType === 1 && topic_child.tagName === 'richcontent') {
                        node_topic = topic_child.textContent
                        break
                    }
                }
            }
            const node_data: { expanded?: string } = df._loadAttributes(xml_node)
            const node_expanded = ('expanded' in node_data) ? (node_data.expanded === 'true') : true
            delete node_data.expanded

            const node_position = xml_node.getAttribute('POSITION')
            let node_direction = null
            if (!!node_position) {
                node_direction = node_position === 'left' ? MindMapMain.direction.left : MindMapMain.direction.right
            }
            // logger.debug(node_position +':'+ node_direction);
            if (!!parent_id) {
                mind.addNode(parent_id, node_id, node_topic, node_data, null, node_direction, node_expanded)
            } else {
                mind.setRoot(node_id, node_topic, node_data)
            }
            const children = xml_node.childNodes
            let child = null
            for (let i = 0; i < children.length; i++) {
                child = children[i]
                if (child.nodeType === 1 && child.tagName === 'node') {
                    df._loadNode(mind, node_id, child)
                }
            }
        },

        _loadAttributes: function (xml_node) {
            const children = xml_node.childNodes
            let attr = null
            let attr_data = {}
            for (let i = 0; i < children.length; i++) {
                attr = children[i]
                if (attr.nodeType === 1 && attr.tagName === 'attribute') {
                    attr_data[attr.getAttribute('NAME')] = attr.getAttribute('VALUE')
                }
            }
            return attr_data
        },

        _buildMap: function (node, xmllines) {
            const df = customizeFormat.freemind
            let pos = null
            if (!!node.parent && node.parent.isroot) {
                pos = node.direction === MindMapMain.direction.left ? 'left' : 'right'
            }
            xmllines.push('<node')
            xmllines.push('ID=\"' + node.id + '\"')
            if (!!pos) {
                xmllines.push('POSITION=\"' + pos + '\"')
            }
            xmllines.push('TEXT=\"' + node.topic + '\">')

            // store expanded status as an attribute
            xmllines.push('<attribute NAME=\"expanded\" VALUE=\"' + node.expanded + '\"/>')

            // for attributes
            const node_data = node.data
            if (node_data != null) {
                for (const k in node_data) {
                    xmllines.push('<attribute NAME=\"' + k + '\" VALUE=\"' + node_data[k] + '\"/>')
                }
            }

            // for children
            const children = node.children
            for (let i = 0; i < children.length; i++) {
                df._buildMap(children[i], xmllines)
            }

            xmllines.push('</node>')
        },
    },
}

