'use strict';

const should = require('should');
const _ = require('lodash');
const util = require('util');

function routeToRegex(route) {
    let PATH_REGEXP = new RegExp(["(\\\\.)", "([\\/.])?(?:\\:(\\w+)(?:\\(((?:\\\\.|[^)])*)\\))?|\\(((?:\\\\.|[^)])*)\\))([+*?])?", "([.+*?=^!:${}()[\\]|\\/])"].join("|"), "g");

    function escapeGroup(e) {
        return e.replace(/([=!:$\/()])/g, "\\$1")
    }

    function attachKeys(e, r) {
        return e.keys = r, e;
    }

    function doFlags(e) {
        return e.sensitive ? "" : "i"
    }

    function replacePath(e, r) {
        function a(e, a, n, i, p, o, u, c) {
            if(a) return a;
            if(c) return "\\" + c;
            let s = "+" === u || "*" === u,
                g = "?" === u || "*" === u;
            return r.push({
                name: i || t++,
                delimiter: n || "/",
                optional: g,
                repeat: s
            }), n = n ? "\\" + n : "", p = escapeGroup(p || o || "[^" + (n || "\\/") + "]+?"), s && (p = p + "(?:" + n + p + ")*"), g ? "(?:" + n + "(" + p + "))?" : n + "(" + p + ")"
        }

        let t = 0;
        return e.replace(PATH_REGEXP, a)
    }

    function pathToRegexp(path, replace, flags) {
        replace = [];
        flags = {};

        let t = flags.strict,
            n = flags.end !== !1,
            i = replacePath(path, replace),
            p = "/" === path.charAt(path.length - 1);

        return t || (i = (p ? i.slice(0, -2) : i) + "(?:\\/(?=$))?"), i += n ? "$" : t && p ? "" : "(?=\\/|$)", attachKeys(new RegExp("^" + i, doFlags(flags)), replace)
    }

    return pathToRegexp(route);
}

describe('routing', function() {

    before(() => {
    });

    it('should sort routes', () => {
        let routes = [
            '/api/rest-datastore-api',
            '/api/rest-datastore-api/:id',
            '/api/rest-datastore-api',
            '/api/rest-datastore-api/:id',
            '/api/rest-datastore-api/:id',
            '/api/rest-mysql-api',
            '/api/rest-mysql-api/:id',
            '/api/rest-mysql-api',
            '/api/rest-mysql-api/:id',
            '/api/rest-mysql-api/:id',
            '/api/:test',
            '/api/user/:test',
            '/api/image',
            '/api/html',
            '/api/health',
            '/api/health/:type',
            '/api/user',
            '/api/user/history',
            '/api/user/token',
            '/api/user/token/:id',
            '/api/user/token',
            '/api/user/token/:id',
            '/api/user/token/:id'
        ];

        let ITERATIONS = 20;

        for(let i = 0; i < ITERATIONS; i++) {
            let sortable = routes
                .map((route, index) => {
                    return {
                        index,
                        route,
                        priority: index,
                        regex: routeToRegex(route),
                        isRegex: route.match(/:|{/),
                        test: route.replace(/:[a-zA-Z0-9_]+/g, 'test')
                    }
                });

            // 1. search conflicts
            sortable.forEach(a => {
                sortable.forEach(b => {
                    if(!a.isRegex || !b.isRegex) {
                        return;
                    }

                    if(a.route == b.route) {
                        return;
                    }

                    if(a.isRegex && b.isRegex) {
                        let aMatch = !!a.regex.exec(b.test);
                        let bMatch = !!b.regex.exec(a.test);

                        (aMatch && bMatch).should.not.be.true(util.format('conflict detected between %s and %s', a.route, b.route));
                    }
                });
            });

            // 2. put priorities
            sortable.forEach(a => {
                sortable.forEach(b => {
                    if(a.route == b.route) {
                        return;
                    }

                    if(a.regex.exec(b.test)) {
                        if(a.priority < b.priority) {
                            a.priority = b.priority + 0.0001;
                        }
                    }
                });
            });

            sortable.sort((a, b) => {
                return a.priority < b.priority ? -1 : 1;
            });

            let sorted = sortable.map(route => {
                return route.route;
            });

            /* -------------------------------------------------------------------------
             * Test
             * ---------------------------------------------------------------------- */

            let positions = {};

            for(let i = 0; i < sorted.length; i++) {
                let route = sorted[i];
                positions[route] = i;
            }

            let toTest = {
                '/api/image': '/api/:test',
                '/api/html': '/api/:test',
                '/api/health': '/api/:test',

                '/api/user/history': '/api/user/:test',
            };

            _.forEach(toTest, (lower, higher) => {
                let posL = positions[lower];
                let posH = positions[higher];

                (posH).should.be.below(posL, util.format('%s (%d) should have more priority than %s (%d)', higher, posH, lower, posL));
            });

            routes = _.shuffle(routes);
        }
    });
});