/* 
Copyright © Doghole 2021 - 9999
License: MIT
You can compress it and remove comments below, 
but the copyright and the license must be left.
@see https://daringfireball.net/projects/markdown/syntax
*/
var LinkSync = function() {
    var LinkType = {
        UNKNOWN: 'unknown', IMAGE: 'image', LINK: 'link'
    }, that = this;
    var f = {
        regs : {
            ref: {
                /**
                This pattern matches reference images or links:
                
                1. ![alt as refName]
                2. ![alt as refName][]
                3. ![alt][refName]
                4. [text as refName]
                5. [text as refName][]
                6. [text][refName]
                
                In 1, 2, 4 and 5 cases, alt/text and refName
                are same. For example, the reference image 
                element is:
                
                ![firework]
                
                Then there should be a reference target like:
                
                [firework]: /firework.jpg
                 ^ notice here is the refName
                
                And the final rendered html will be like:
                
                <img src="/firework.jpg" target="_blank" alt="firework">
                                                              ^ and here is the alt
                
                 */
                exp:/(!)?\[(.*?)\](\[(.*?)\])?(?!(: ((([^<>\s]+)|\<(\S+)\>)\s*("(.*?)"|'(.*?)'|\((.*?)\))?\s*$)|\(.*?\)))/gm, 
                match: function(text){
                    var that = this, results = [], m = text.match(that.exp) || [];
                    $.each(m, function(i, v){
                        var e = that.exp.exec(v),t;
                        !!e && (t = {
                            text: v,
                            scheme: 'ref',
                            linkType: e[1] == '!' ? LinkType.IMAGE : LinkType.LINK,
                            name: e[2],
                            refName: e[4] || e[2],
                            index: function(){
                                for(var j = results.length - 1; j >= 0; j--) {
                                    if (results[j].text == v) {
                                        return text.indexOf(v, results[j].index + 1)
                                    }
                                }
                                return text.indexOf(v)
                            }()
                        },
                        t.selector = function(){
                            var sub = text.substring(0, t.index),
                            m = sub.match(/\n/g),
                            line = !!m?m.length:0,
                            ch0 = t.index - sub.lastIndexOf('\n') - 1,
                            ch1 = ch0 + t.text.length;
                            return [{line: line, ch: ch0}, {line: line, ch: ch1}]
                        }(), results.push(t))
                        that.exp.lastIndex = 0
                    })
                    return results;
                }
            }, 
            refTarget: {
                /**  
                This pattern matches several targets below: 
                
                [refName]: url "title(optional)"
                [refName]: url 'title(optional)'
                [refName]: url (title(optional))
                
                Notice that the title is optional, and may 
                bracketed by single quotes, double quotes
                or round brackets.
                
                Sometimes the url may be bracketed by angle
                brackets like below:
                [refName]: <url> "title(optional)"
                
                If the url is too long, author may take a 
                new line to place the title:
                [refName]: url 
                "title(optional)"
                
                */
                exp: /^\s{2,}(\[(.*?)\]: (([^<>\s]+)|\<(\S+)\>)\s*("(.*?)"|'(.*?)'|\((.*?)\))?\s*)$/gm, 
                match: function(text) {
                    var that = this, results = {}, m = text.match(that.exp) || [], t;
                    $.each(m, function(i, v){
                        var e = that.exp.exec(v);
                        !!e && (t = {
                            text: e[1],
                            scheme: 'refTarget',
                            refName: e[2],
                            url: e[4] || e[5],
                            title: e[7] || e[8] || e[9],
                            index: text.lastIndexOf(e[1])
                        },
                        t.selector = function(){
                            var sub = text.substring(0, t.index),
                            m = sub.match(/\n/g),
                            line = !!m?m.length:0,
                            ch0 = t.index - sub.lastIndexOf('\n') - 1,
                            ch1 = ch0 + t.text.length;
                            return [{line: line, ch: ch0}, {line: line, ch: ch1}]
                        }(), results[e[2].toLowerCase()] = t)
                        that.exp.lastIndex = 0
                    })
                    return results;
                }
            },
            direct: {
                exp: /((!)?\[(.*?)\]\((\S+)\s*("(.*?)"|'(.*?)'|\((.*?)\))?\))|<(https?:\/\/.*?)>/gmi,
                match: function(text){
                    var that = this, results = [], m = text.match(that.exp) || [], t;
                    $.each(m, function(i, v){
                        var e = that.exp.exec(v);
                        !!e && (t = {
                            text: v,
                            scheme: 'direct',
                            linkType: e[2] == '!' ? LinkType.IMAGE : LinkType.LINK,
                            name: e[3] || e[9],
                            url: e[4],
                            title: e[6] || e[7] || e[8] || e[9],
                            index: function(){
                                for(var j = results.length - 1; j >= 0; j--) {
                                    if (results[j].text == v) {
                                        return text.indexOf(v, results[j].index + 1)
                                    }
                                }
                                return text.indexOf(v)
                            }()
                        },
                        t.selector = function(){
                            var sub = text.substring(0, t.index),
                            m = sub.match(/\n/g),
                            line = !!m?m.length:0,
                            ch0 = t.index - sub.lastIndexOf('\n') - 1,
                            ch1 = ch0 + t.text.length;
                            return [{line: line, ch: ch0}, {line: line, ch: ch1}]
                        }(),
                        results.push(t))
                        that.exp.lastIndex = 0
                    })
                    return results;
                }
            },
            code: {
                exp: /(\s{0,3}```([\s\S]*?)```)|([^`]`([^`]*?)`[^`])/gm,
                match: function(text) {
                    var that = this, results = [], m = text.match(that.exp) || [];
                    $.each(m, function(i, v){
                        var e = that.exp.exec(v);
                        !!e && (t = {
                            text: v,
                            scheme: 'code',
                            start: function(){
                                for(var j = results.length - 1; j >= 0; j--) {
                                    if (results[j].text == v) {
                                        return text.indexOf(v, results[j].index + 1)
                                    }
                                }
                                return text.indexOf(v)
                            }()
                        }, t.end = t.start + v.length, results.push(t))
                        that.exp.lastIndex = 0
                    })
                    return results;
                }
            },
            imgTag: {
                exp:/<img .*?\/?(><\/img)?>/gmi, 
                match: function(text){
                    var that = this, results = [], m = text.match(that.exp) || [];
                    $.each(m, function(i, v){
                        var e = that.exp.exec(v),o,t;
                        !!e && (o = $(e[0]), a = o.attr('title') || o.attr('alt') || 'None', t = {
                            text: e[0],
                            scheme: 'ref',
                            linkType: LinkType.IMAGE,
                            name: a,
                            url: o.attr('src'),
                            refName: a,
                            index: function(){
                                for(var j = results.length - 1; j >= 0; j--) {
                                    if (results[j].text == v) {
                                        return text.indexOf(v, results[j].index + 1)
                                    }
                                }
                                return text.indexOf(v)
                            }()
                        },
                        t.selector = function(){
                            var sub = text.substring(0, t.index),
                            m = sub.match(/\n/g),
                            line = !!m?m.length:0,
                            ch0 = t.index - sub.lastIndexOf('\n') - 1,
                            ch1 = ch0 + t.text.length;
                            return [{line: line, ch: ch0}, {line: line, ch: ch1}]
                        }(), results.push(t))
                        that.exp.lastIndex = 0
                    })
                    return results;
                }
            }
        },
        
        matchRefs : function(refs, refTargets) {
            var results = [];
            $.each(refs, function(i, ref){
                var refTarget = refTargets[ref.refName.toLowerCase()];
                !!refTarget && (ref.url = refTarget.url, ref.title = refTarget.title, ref.target = refTarget, results.push(ref))
            })
            return results;
        },
        checkRefs : function(refs, codes) {
            var results = [];
            $.each(refs, function(i, ref){
                for(var j = 0; j < codes.length; j++){
                    var code = codes[j];
                    if (ref.index >= code.start && ref.index + ref.text.length <= code.end) {
                        return true;
                    }
                }
                results.push(ref);
            })
            return results;
        },
        list: function(text) {
            if (!text) {
                return null;
            }
            // Match reference target first
            var refTargets = f.regs.refTarget.match(text),
            refs = f.regs.ref.match(text), 
            directs = f.regs.direct.match(text),
            imgTags = f.regs.imgTag.match(text),
            codes = f.regs.code.match(text);
            refs = f.matchRefs(refs, refTargets);
            refs = refs.concat(directs);
            refs = refs.concat(imgTags);
            //for(var i = 0; i < directs.length; i++){
            //    refs.push(directs[i]);
            //}
            refs = f.checkRefs(refs, codes);
            return refs.sort(function(a,b){return a.index-b.index});
        }
    }
    
    return {
        list: f.list,
        regs: f.regs
    }
    
} 