(function () {
    'use strict';

// raise an error in test mode if openerp don't exist
if (typeof openerp === "undefined") {
    var error = "openerp is undefined"
                + "\nhref: " + window.location.href
                + "\nreferrer: " + document.referrer
                + "\nlocalStorage: " + window.localStorage.getItem("tour");
    if (typeof $ !== "undefined") {
        error += '\n\n' + $("body").html();
    }
    throw new Error(error);
}

var website = openerp.website;

// don't rewrite T in test mode
if (typeof openerp.Tour !== "undefined") {
    return;
}

/////////////////////////////////////////////////


/* jQuery selector to match exact text inside an element
 *  :containsExact()     - case insensitive
 *  :containsExactCase() - case sensitive
 *  :containsRegex()     - set by user ( use: $(el).find(':containsRegex(/(red|blue|yellow)/gi)') )
 */
$.extend($.expr[':'],{
    containsExact: function(a,i,m){
        return $.trim(a.innerHTML.toLowerCase()) === m[3].toLowerCase();
    },
    containsExactCase: function(a,i,m){
        return $.trim(a.innerHTML) === m[3];
    },
    // Note all escaped characters need to be double escaped
    // inside of the containsRegex, so "\(" needs to be "\\("
    containsRegex: function(a,i,m){
        var regreg =  /^\/((?:\\\/|[^\/])+)\/([mig]{0,3})$/,
        reg = regreg.exec(m[3]);
        return reg ? new RegExp(reg[1], reg[2]).test($.trim(a.innerHTML)) : false;
    }
});
$.ajaxSetup({
    beforeSend:function(){
        $.ajaxBusy = ($.ajaxBusy|0) + 1;
    },
    complete:function(){
        $.ajaxBusy--;
    }
});

/////////////////////////////////////////////////

var localStorage = window.localStorage;

var Tour = {
    tours: {},
    defaultDelay: 50,
    autoRunning: true,
    retryRunningDelay: 1000,
    errorDelay: 5000,
    state: null,
    $element: null,
    timer: null,
    testtimer: null,
    currentTimer: null,
    register: function (tour) {
        if (tour.mode !== "test") tour.mode = "tutorial";
        Tour.tours[tour.id] = tour;
    },
    run: function (tour_id, mode) {
        var tour = Tour.tours[tour_id];
        if (!tour) {
            return Tour.error(null, "Can't run '"+tour_id+"' (tour undefined)");
        }
        Tour.log("Tour '"+tour_id+"' Begin from run method", true);
        var state = Tour.getState();
        if (state) {
             if (state.mode === "test") {
                return Tour.error(false, "An other running tour has been detected all tours are now killed.");
            } else {
                Tour.endTour();
            }
        }
        this.time = new Date().getTime();
        if (tour.path && !window.location.href.match(new RegExp("("+Tour.getLang()+")?"+tour.path+"#?$", "i"))) {
            var href = Tour.getLang()+tour.path;
            Tour.saveState(tour.id, mode || tour.mode, -1, 0);
            $(document).one("ajaxStop", Tour.running);
            window.location.href = href;
        } else {
            Tour.saveState(tour.id, mode || tour.mode, 0, 0);
            Tour.running();
        }
    },
    registerSteps: function (tour, mode) {
        if (tour.register) {
            return;
        }
        tour.register = true;

        for (var index=0, len=tour.steps.length; index<len; index++) {
            var step = tour.steps[index];
            step.id = index;

            if (!step.waitNot && index > 0 && tour.steps[index-1] &&
                tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
                step.waitNot = '.popover.tour.fade.in:visible';
            }
            if (!step.waitFor && index > 0 && tour.steps[index-1].snippet) {
                step.waitFor = '.oe_overlay_options .oe_options:visible';
            }


            var snippet = step.element && step.element.match(/#oe_snippets (.*) \.oe_snippet_thumbnail/);
            if (snippet) {
                step.snippet = snippet[1];
            } else if (step.snippet) {
                step.element = '#oe_snippets '+step.snippet+' .oe_snippet_thumbnail';
            }

            if (!step.element) {
                step.element = "body";
                step.orphan = true;
                step.backdrop = true;
            } else {
                step.popover = step.popover || {};
                step.popover.arrow = true;
            }
        }
        if (tour.steps[index-1] &&
            tour.steps[index-1].popover && tour.steps[index-1].popover.next) {
            var step = {
                _title: "close popover and finish",
                id: index,
                waitNot: '.popover.tour.fade.in:visible'
            };
            tour.steps.push(step);
        }

        // rendering bootstrap tour and popover
        if (mode !== "test") {
            for (var index=0, len=tour.steps.length; index<len; index++) {
                var step = tour.steps[index];
                step._title = step._title || step.title;
                step.title = Tour.popoverTitle(tour, { title: step._title });
                step.template = step.template || Tour.popover( step.popover );
            }
        }
    },
    closePopover: function () {
        if (Tour.$element) {
            Tour.$element.popover('destroy');
            Tour.$element.removeData("tour");
            Tour.$element.removeData("tour-step");
            $(".tour-backdrop").remove();
            $(".popover.tour").remove();
            Tour.$element = null;
        }
    },
    autoTogglePopover: function () {
        var state = Tour.getState();
        var step = state.step;

        if (Tour.$element &&
            Tour.$element.is(":visible") &&
            Tour.$element.data("tour") === state.id &&
            Tour.$element.data("tour-step") === step.id) {
            Tour.repositionPopover();
            return;
        }

        if (step.busy) {
            return;
        }

        Tour.closePopover();

        var $element = $(step.element).first();
        if (!step.element || !$element.size() || !$element.is(":visible")) {
            return;
        }


        Tour.$element = $element;
        $element.data("tour", state.id);
        $element.data("tour-step", step.id);
        $element.popover({
            placement: step.placement || "auto",
            animation: true,
            trigger: "manual",
            title: step.title,
            content: step.content,
            html: true,
            container: "body",
            template: step.template,
            orphan: step.orphan
        }).popover("show");


        var $tip = $element.data("bs.popover").tip();


        // add popover style (orphan, static, backdrop)
        if (step.orphan) {
            $tip.addClass("orphan");
        }

        var node = $element[0];
        var css;
        do {
            css = window.getComputedStyle(node);
            if (!css || css.position == "fixed") {
                $tip.addClass("fixed");
                break;
            }
        } while ((node = node.parentNode) && node !== document);

        if (step.backdrop) {
            $("body").append('<div class="tour-backdrop"></div>');
        }

        if (step.backdrop || $element.parents("#website-top-navbar, .oe_navbar, .modal").size()) {
            $tip.css("z-index", 2010);
        }

        // button click event
        $tip.find("button")
            .one("click", function () {
                step.busy = true;
                if (!$(this).is("[data-role='next']")) {
                    clearTimeout(Tour.timer);
                    Tour.endTour();
                }
                Tour.closePopover();
            });

        Tour.repositionPopover();
    },
    repositionPopover: function() {
        var popover = Tour.$element.data("bs.popover");
        var $tip = popover.tip();

        if (popover.options.orphan) {
            return $tip.css("top", $(window).outerHeight() / 2 - $tip.outerHeight() / 2);
        }

        if (Tour.$element.parents("div").filter(function(){ return getComputedStyle(this).position === 'fixed'; }).length) {
            var pos = popover.getPosition();
            var top = pos.top;
            if (popover.options.placement === "top") top -= $tip.height();
            if (popover.options.placement === "bottom") top += pos.height;
            $tip.css({'top': top+'px'});
        }

        var offsetBottom, offsetHeight, offsetRight, offsetWidth, originalLeft, originalTop, tipOffset;
        offsetWidth = $tip[0].offsetWidth;
        offsetHeight = $tip[0].offsetHeight;
        tipOffset = $tip.offset();
        originalLeft = tipOffset.left;
        originalTop = tipOffset.top;
        offsetBottom = $(document).outerHeight() - tipOffset.top - $tip.outerHeight();
        if (offsetBottom < 0) {
            tipOffset.top = tipOffset.top + offsetBottom;
        }
        offsetRight = $("html").outerWidth() - tipOffset.left - $tip.outerWidth();
        if (offsetRight < 0) {
            tipOffset.left = tipOffset.left + offsetRight;
        }
        if (tipOffset.top < 0) {
            tipOffset.top = 0;
        }
        if (tipOffset.left < 0) {
            tipOffset.left = 0;
        }
        $tip.offset(tipOffset);
        if (popover.options.placement === "bottom" || popover.options.placement === "top") {
                var left = Tour.$element.offset().left + Tour.$element.outerWidth()/2 - tipOffset.left;
                popover.arrow().css("left", left ? left + "px" : "");
        } else if (popover.options.placement !== "auto") {
                var top = Tour.$element.offset().top + Tour.$element.outerHeight()/2 - tipOffset.top;
                popover.arrow().css("top", top ? top + "px" : "");
        }
    },
    _load_template: false,
    load_template: function () {
        // don't need template to use bootstrap Tour in automatic mode
        Tour._load_template = true;
        if (typeof QWeb2 === "undefined") return $.when();
        var def = $.Deferred();
        openerp.qweb.add_template('/web/static/src/xml/website.tour.xml', function(err) {
            if (err) {
                def.reject(err);
            } else {
                def.resolve();
            }
        });
        return def;
    },
    popoverTitle: function (tour, options) {
        return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover_title', options) : options.title;
    },
    popover: function (options) {
        return typeof QWeb2 !== "undefined" ? openerp.qweb.render('tour.popover', options) : options.title;
    },
    getLang: function () {
        return $("html").attr("lang") ? "/" + $("html").attr("lang").replace(/-/, '_') : "";
    },
    getState: function () {
        var state = JSON.parse(localStorage.getItem("tour") || 'false') || {};
        if (state) { this.time = state.time; }
        var tour_id,mode,step_id;
        if (!state.id && window.location.href.indexOf("#tutorial.") > -1) {
            state = {
                "id": window.location.href.match(/#tutorial\.(.*)=true/)[1],
                "mode": "tutorial",
                "step_id": 0
            };
            window.location.hash = "";
            Tour.log("Tour '"+state.id+"' Begin from url hash");
            Tour.saveState(state.id, state.mode, state.step_id, 0);
        }
        if (!state.id) {
            return;
        }
        state.tour = Tour.tours[state.id];
        state.step = state.tour && state.tour.steps[state.step_id === -1 ? 0 : state.step_id];
        return state;
    },
    log: function (message, add_user) {
        if (add_user) {
            var user = $(".navbar .dropdown:has(>.js_usermenu) a:first, .navbar .oe_topbar_name, .pos .username").text();
            if (!user && $('a[href*="/login"]')) user = 'Public User';
            message += " (" + (user||"").replace(/^\s*|\s*$/g, '') + ")";
        }
        console.log(message);
    },
    error: function (step, message) {
        var state = Tour.getState();
        console.log(state.tour.steps.slice());
        message += '\n tour: ' + state.id
            + (step ? '\n step: ' + step.id + ": '" + (step._title || step.title) + "'" : '' )
            + '\n href: ' + window.location.href
            + '\n referrer: ' + document.referrer
            + (step ? '\n element: ' + Boolean(!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) : '' )
            + (step ? '\n waitNot: ' + Boolean(!step.waitNot || !$(step.waitNot).size()) : '' )
            + (step ? '\n waitFor: ' + Boolean(!step.waitFor || $(step.waitFor).size()) : '' )
            + "\n localStorage: " + JSON.stringify(localStorage)
            + '\n\n' + $("body").html();
        Tour.log(message, true);
        Tour.endTour();
    },
    lists: function () {
        var tour_ids = [];
        for (var k in Tour.tours) {
            tour_ids.push(k);
        }
        return tour_ids;
    },
    saveState: function (tour_id, mode, step_id, number, wait) {
        localStorage.setItem("tour", JSON.stringify({
            "id":tour_id,
            "mode":mode,
            "step_id":step_id || 0,
            "time": this.time,
            "number": number+1,
            "wait": wait || 0
        }));
    },
    reset: function () {
        var state = Tour.getState();
        if (state && state.tour) {
            for (var k in state.tour.steps) {
                state.tour.steps[k].busy = false;
            }
        }
        localStorage.removeItem("tour");
        clearTimeout(Tour.timer);
        clearTimeout(Tour.testtimer);
        Tour.closePopover();
        Tour.log("Tour reset");
    },
    running: function () {
        var state = Tour.getState();
        if (!state) return;
        else if (state.tour) {
            if (!Tour._load_template) {
                Tour.load_template().then(Tour.running);
                return;
            }
            Tour.log("Tour '"+state.id+"' is running", true);
            Tour.registerSteps(state.tour, state.mode);
            Tour.nextStep();
        } else {
            if (state.mode === "test" && state.wait >= 10) {
                return Tour.error(state.step, "Tour '"+state.id+"' undefined");
            }
            Tour.saveState(state.id, state.mode, state.step_id, state.number-1, state.wait+1);
            Tour.log("Tour '"+state.id+"' wait for running (tour undefined)");
            setTimeout(Tour.running, Tour.retryRunningDelay);
        }
    },
    check: function (step) {
        return (step &&
            (!step.element || ($(step.element).size() && $(step.element).is(":visible") && !$(step.element).is(":hidden"))) &&
            (!step.waitNot || !$(step.waitNot).size()) &&
            (!step.waitFor || $(step.waitFor).size()));
    },
    waitNextStep: function () {
        var state = Tour.getState();
        var time = new Date().getTime();
        var timer;
        var next = state.tour.steps[state.step.id+1];
        var overlaps = state.mode === "test" ? Tour.errorDelay : 0;

        window.onbeforeunload = function () {
            clearTimeout(Tour.timer);
            clearTimeout(Tour.testtimer);
        };

        function checkNext () {
            if (!Tour.getState()) return;

            Tour.autoTogglePopover();

            clearTimeout(Tour.timer);
            if (Tour.check(next)) {
                clearTimeout(Tour.currentTimer);
                // use an other timeout for cke dom loading
                Tour.saveState(state.id, state.mode, state.step.id, 0);
                setTimeout(function () {
                    Tour.nextStep(next);
                }, Tour.defaultDelay);
            } else if (!overlaps || new Date().getTime() - time < overlaps) {
                Tour.timer = setTimeout(checkNext, Tour.defaultDelay);
            } else {
                return Tour.error(next, "Can't reach the next step");
            }
        }
        checkNext();
    },
    nextStep: function (step) {
        var state = Tour.getState();

        if (!state) {
            return;
        }

        step = step || state.step;
        var next = state.tour.steps[step.id+1];

        if (state.mode === "test" && state.number > 3) {
            return Tour.error(next, "Cycling. Can't reach the next step");
        }
        
        Tour.saveState(state.id, state.mode, step.id, state.number);

        if (step.id !== state.step_id) {
            Tour.log("Tour '"+state.id+"' Step: '" + (step._title || step.title) + "' (" + (new Date().getTime() - this.time) + "ms)");
        }

        Tour.autoTogglePopover(true);

        if (step.onload) {
            step.onload();
        }

        if (next) {
            setTimeout(function () {
                if (Tour.getState()) {
                    Tour.waitNextStep();
                }
                if (state.mode === "test") {
                    setTimeout(function(){
                        Tour.autoNextStep(state.tour, step);
                    }, Tour.defaultDelay);
                }
            }, next.wait || 0);
        } else {
            setTimeout(function(){
                Tour.autoNextStep(state.tour, step);
            }, Tour.defaultDelay);
            Tour.endTour();
        }
    },
    endTour: function () {
        var state = Tour.getState();
        var test = state.step && state.step.id >= state.tour.steps.length-1;
        Tour.reset();
        if (test) {
            Tour.log("Tour '"+state.id+"' finish: ok");
            Tour.log('ok');
        } else {
            Tour.log("Tour '"+state.id+"' finish: error");
            Tour.log('error');
        }
    },
    autoNextStep: function (tour, step) {
        clearTimeout(Tour.testtimer);

        function autoStep () {
            if (!Tour.getState()) return;

            if (!step) return;

            if (step.autoComplete) {
                step.autoComplete(tour);
            }

            $(".popover.tour [data-role='next']").click();

            var $element = $(step.element);
            if (!$element.size()) return;

            if (step.snippet) {

                Tour.autoDragAndDropSnippet($element);
            
            } else if ($element.is(":visible")) {

                $element.trigger($.Event("mouseenter", { srcElement: $element[0] }));
                $element.trigger($.Event("mousedown", { srcElement: $element[0] }));
        
                var evt = document.createEvent("MouseEvents");
                evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
                $element[0].dispatchEvent(evt);

                // trigger after for step like: mouseenter, next step click on button display with mouseenter
                setTimeout(function () {
                    if (!Tour.getState()) return;
                    $element.trigger($.Event("mouseup", { srcElement: $element[0] }));
                    $element.trigger($.Event("mouseleave", { srcElement: $element[0] }));
                }, 1000);
            }
            if (step.sampleText) {
            
                $element.trigger($.Event("keydown", { srcElement: $element }));
                if ($element.is("input") ) {
                    $element.val(step.sampleText);
                } if ($element.is("select")) {
                    $element.find("[value='"+step.sampleText+"'], option:contains('"+step.sampleText+"')").attr("selected", true);
                    $element.val(step.sampleText);
                } else {
                    $element.html(step.sampleText);
                }
                setTimeout(function () {
                    if (!Tour.getState()) return;
                    $element.trigger($.Event("keyup", { srcElement: $element }));
                    $element.trigger($.Event("change", { srcElement: $element }));
                }, self.defaultDelay<<1);
            
            }
        }
        Tour.testtimer = setTimeout(autoStep, 100);
    },
    autoDragAndDropSnippet: function (selector) {
        var $thumbnail = $(selector).first();
        var thumbnailPosition = $thumbnail.position();
        $thumbnail.trigger($.Event("mousedown", { which: 1, pageX: thumbnailPosition.left, pageY: thumbnailPosition.top }));
        $thumbnail.trigger($.Event("mousemove", { which: 1, pageX: document.body.scrollWidth/2, pageY: document.body.scrollHeight/2 }));
        var $dropZone = $(".oe_drop_zone").first();
        var dropPosition = $dropZone.position();
        $dropZone.trigger($.Event("mouseup", { which: 1, pageX: dropPosition.left, pageY: dropPosition.top }));
    }
};
openerp.Tour = Tour;

/////////////////////////////////////////////////

$(document).ready(function () {
    if (Tour.autoRunning) {
        Tour.running();
    };
});

}());
