$(function() {
    
    var startYear = -21000,
        endYear = 2012,
        gamma = 2,
        zoomLevel = 4,
        zoomStep = Math.SQRT2,
        minZoomLevel = 0,
        maxZoomLevel = 14,
        sliderWidth = 3000,
        $slider = $(".timeline-mill-slider"),
        $zoomSlider = $(".timeline-slider"),
        $tlcontainer = $(".timeline-container"),
        canvas = $tlcontainer.find('canvas')[0],
        lineDistances = [ 50000, 25000, 10000, 5000, 2500, 1000, 500, 250, 100, 50, 25, 10, 5, 2 ],
        miniLines = [-20000, -5000, -2000, -1000, 0, 1000, 2000],
        wCenter = .875,
        itemCount = 12,
        tlCache = [], tlIdCache = [], currentTerm = null,
        startSlide = startYear, endSlide = endYear, userStartSlide = startSlide, userEndSlide = endSlide,
        fromYear, toYear, cWidth, wLineDist, zoomFactor,
        isRtl = $("html").is("[dir=rtl]");
    
    /*
     *  HOW UNIT CONVERSIONS WORK ?
     * 
     *  First, Years from startYear (-20K) to endYear (now) are mapped to a [0,1] range that we call T
     *    - tToYr converts units in this range to years, and yrToT does the reverse conversion
     *  
     *  Then, a non-linear scale is applied to have a focus on recent years.
     *  In this case, I chose a gamma correction : y = x^gamma which also yields a [0,1] range
     *    - The Years-Based ranged is converted to the range used for pixel distances with the function
     *      wToT (reverse tToW -> x = y^(1/gamma))
     *  
     *  This new range "w" is used for all pixel distance calculations.
     *  
     *  For the "mini-Timeline" (full year range, at the top), these are converted directly
     * 
     *  For the main timeline, which only show a subset (window) of the full timeline,
     *  two more parameters are required :
     *    - The zoom level (variable zoomLevel)
     *    - The position of the centre of the window relative to the range (called wCenter)
     *  
     *  The year range of our window is calculated from these parameters
     *  
     *  Each time we zoom in, the scale is increased by 41 % (that's the square root of 2
     *  and also the ratio between An and An-1 - e.g. A4 and A3 paper sheets)
     *  This way, the scale is proportional to 1.41 to the power of the zoom level.
     *  This is why we calculate a zoomFactor from zoomLevel
     *  
     * 
     * */
    
    function tToW(t) {
        //return 2 / (2-Math.min(1,Math.max(0,t))) - 1,;
        return Math.pow(t, gamma);
    }
    function wToT(w) {
        //return 2 - 2/(Math.min(1,Math.max(0,w))+1);
        return Math.pow(w, 1/gamma);
    }
    function tToYr(t) {
        return startYear + (endYear - startYear) * t;
    }
    function yrToT(yr) {
        return (yr - startYear)/(endYear - startYear);
    }
    function wToYr(w) {
         return tToYr(wToT(w));
    }
    function yrToW(yr) {
        return tToW(yrToT(yr));
    }
    function deltaXToDeltaW(dx) {
        return (isRtl ? -1 : 1) * dx / (zoomFactor * cWidth);
    }
    function deltaWToDeltaX(dw) {
        return (isRtl ? -1 : 1) * dw * zoomFactor * cWidth;
    }
    function wToX(w) {
        return (isRtl ? cWidth : 0) + deltaWToDeltaX(w - wCenter + (.5 / zoomFactor));
    }
    function xToW(x) {
        return deltaXToDeltaW(x - (isRtl ? cWidth : 0)) + wCenter - (.5 / zoomFactor);
    }
    function wToMini(w) {
        var res = cWidth * w;
        if (isRtl) {
            res = cWidth - res;
        }
        return res;
    }
    function deltaMiniToDeltaW(m) {
        return (isRtl ? -1 : 1) * m / cWidth;
    }
    function wToSlider(w) {
        var res = zoomFactor * sliderWidth * (w - wCenter + (.5 / zoomFactor));
        if (isRtl) {
            res = sliderWidth - res;
        }
        return res;
    }
    function sliderToW(slidepos) {
        var p = slidepos;
        if (isRtl) {
            p = sliderWidth - p;
        }
        return p / (sliderWidth * zoomFactor) + wCenter - (.5 / zoomFactor);
    }
    function yrToMiniX(yr) {
        return wToMini(yrToW(yr));
    }
    function yrToX(yr) {
        return wToX(yrToW(yr));
    }
    function yrToSliderPos(yr) {
        return wToSlider(yrToW(yr));
    }
    function sliderPosToYr(slidepos) {
        return wToYr(sliderToW(slidepos));
    }
    function boundValue(val) {
        return Math.max(Math.floor(fromYear), Math.min(Math.floor(toYear), val));
    }
    function updateZoomFactor() {
        zoomFactor = Math.pow(Math.SQRT2, zoomLevel);
    }
    function updateCoords() {
        /* Let's first check if the timeline width has been changed */
        cWidth = $tlcontainer.width();
        cHeight = $tlcontainer.height();
        wLineDist = null;
        /* We calculate the bounding years of our window */
        fromYear = wToYr(wCenter - .5 / zoomFactor);
        toYear = wToYr(wCenter + .5 / zoomFactor);
        /*
         We want to choose the distance between lines that we want to display, so that
         two lines at the first quarter of our range are more than 50 pixels apart.
        */
        var wAtQuarter = (wCenter - .25 /zoomFactor),
            yearAtQuarter = wToYr(wAtQuarter),
            posAtQuarter = wToX(wAtQuarter);
        for (var i = 0; i < lineDistances.length; i++) {
            var yr = yearAtQuarter + lineDistances[i],
                x = yrToX(yr),
                delta = x - posAtQuarter;
            if (Math.abs(delta) < 50) {
                break;
            }
            wLineDist = lineDistances[i];
        }
        /* If the slider range is larger than the window, we constrain it to the window */
        startSlide = boundValue(userStartSlide);
        endSlide = boundValue(userEndSlide);
        var startPos = yrToSliderPos(startSlide),
            endPos = yrToSliderPos(endSlide);
        $slider.slider("values",[Math.min(startPos, endPos),Math.max(startPos, endPos)]);
        updateSpans();
    }
    
    var itemTpl = _.template(
        '<li class="timeline-item"><div class="timeline-item-box<%- current ? " timeline-current" : "" %>"'
        + ' data-dbpedia-uri="<%- item.dbpedia_uri %>" style="left: <%- left %>px; width: <%- width %>px;">'
        + '<div class="timeline-item-label"><%- item.label %></div></div></li>'
    );
    
    function redrawView() {
        canvas.width = cWidth;
        canvas.height = cHeight;
        var ctx = canvas.getContext("2d"),
            xstart = yrToMiniX(fromYear),
            xend = yrToMiniX(toYear),
            xleft = Math.min(xstart, xend),
            xright = Math.max(xstart, xend);
        
        /* We first draw the darkish rectangle showing the full timeline */
        ctx.fillStyle = "#e0e0e0";
        ctx.fillRect( 0, 0, cWidth, 30 );
        /* We draw the rectangle for the main timeline */
        ctx.fillStyle = "#f8f8f8";
        ctx.fillRect(0, 60, cWidth, cHeight - 60);
        /* We now draw the blue "funnel" showing the relationship between timelines */
        ctx.fillStyle = "rgba(0, 64, 255, .05)";
        ctx.strokeStyle = "#3090ff";
        ctx.beginPath();
        ctx.moveTo(xleft, 0);
        ctx.lineTo(xright, 0);
        ctx.lineTo(xright, 30);
        ctx.lineTo(cWidth, 60);
        ctx.lineTo(cWidth, cHeight);
        ctx.lineTo(0, cHeight);
        ctx.lineTo(0, 60);
        ctx.lineTo(xleft, 30);
        ctx.closePath();
        ctx.stroke();
        ctx.fill();
        /* Whoaw, that was a long line to draw */
        /* Now, we draw the years on the full timeline */
        ctx.font = 'bold 12px Arial,Helvetica';
        ctx.fillStyle = "#333333";
        ctx.strokeStyle = "#333333";
        for (var i = 0; i < miniLines.length; i++ ) {
            var y = miniLines[i],
                x = yrToMiniX(y);
            ctx.beginPath();
            ctx.moveTo(x,0);
            ctx.lineTo(x,6);
            ctx.moveTo(x,24);
            ctx.lineTo(x,30);
            ctx.stroke();
            ctx.textAlign = (i ? (i == miniLines.length - 1 ? (isRtl ? "left" : "right" ) : "center") : (isRtl ? "right" : "left" ) );
            ctx.fillText(y || 1, x,20);
        }
        /* Now, we draw the years on the main timeline */
        ctx.textAlign = 'center';
        ctx.font = 'bold 14px Arial,Helvetica';
        ctx.fillStyle = "#000000";
        for (var y = wLineDist * Math.ceil(fromYear/wLineDist); y <= toYear; y += wLineDist ) {
            var x = yrToX(y),
                isPrimary = !((y/wLineDist) % 2);
            ctx.strokeStyle = (isPrimary ? "#000000": "#999999");
            ctx.beginPath();
            ctx.moveTo(x, 90);
            ctx.lineTo(x, cHeight - 20);
            ctx.stroke();
            if (isPrimary) {
                ctx.fillText(y || 1, x, cHeight - 4);
                ctx.fillText(y || 1, x, 80);
            }
        }
        /* Now displaying the different terms that we will show on the timeline */
        var html = _(tlCache).chain()
            .filter(function(item) { // Only show those within the range
                return (item.end_year >= fromYear && item.start_year <= toYear);
            }).first(itemCount) // Take the first 12
            .sortBy(function(item) { // Sort by mean year
                return (item.start_year + item.end_year);
            }).map(function(item) { // Render them as HTML
                var l = Math.min(cWidth, Math.max(0, yrToX(item.start_year))),
                    r = Math.min(cWidth, Math.max(0, yrToX(item.end_year + 1)));
                return itemTpl({
                    current: (item.dbpedia_uri == currentTerm),
                    item: item,
                    left: Math.min(l,r),
                    width: Math.abs(r-l)
                });
            }).value().join("");
        $(".timeline-list").html(html);
        // Binding events to the freshly rendered HTML
        dbpediaBox.bind(".timeline-item-box");
        $(".timeline-item-box").click(function() {
            var $this = $(this);
            currentTerm = $this.attr("data-dbpedia-uri");
            $(".timeline-item-box").removeClass("timeline-current");
            $this.addClass("timeline-current");
            loadSearchResults({ dbpedia_uri: currentTerm });
        });
    }
    
    function setZoomLevel(level, center) {
        var c = (typeof center === "number" ? center : wCenter);
        zoomLevel = Math.max(minZoomLevel, Math.min(maxZoomLevel, level));
        $zoomSlider.slider("value",zoomLevel);
        updateZoomFactor();
        var halfSpan = .5/zoomFactor;
        wCenter = Math.max(halfSpan, Math.min(1 - halfSpan, c));
        throttledRedraw();
        debouncedGetData();
    }
  
    function getData() {
        $.getJSON(
            urls.ajax_years,
            {
                from_year: Math.floor(fromYear),
                to_year: Math.floor(toYear)
            },
            function(data) {
                _(data).each(function(term) {
                    if (tlIdCache.indexOf(term.dbpedia_uri) === -1) {
                        tlIdCache.push(term.dbpedia_uri);
                        tlCache.push(term);
                    }
                });
                tlCache = _(tlCache).sortBy(function(item) {
                    return -item.nb_notice;
                });
                throttledRedraw();
            }
        );
    }

    var throttledRedraw = _.throttle(function() {
        updateCoords();
        redrawView();
    }, 100);
    
    var debouncedGetData = _.debounce(getData, 1000);
        
    var mousedown = false, dragging = false, xstart = null, wcStart, dragmini;
    
    /* Managing Timeline dragging */
    $tlcontainer.mousedown(function(e) {
        dragging = false;
        mousedown = true;
        dragmini = (e.clientY - $tlcontainer.offset().top < 40);
        xstart = e.clientX;
        wcStart = wCenter;
        return false;
    }).mousemove(function(e) {
        if (mousedown) {
            dragging = true;
            var halfSpan = .5/zoomFactor;
            wCenter = Math.max(
                halfSpan,
                Math.min(
                    1 - halfSpan,
                    dragmini ?
                        (wcStart + deltaMiniToDeltaW(e.clientX - xstart)) :
                        (wcStart + deltaXToDeltaW(xstart - e.clientX))
                    )
                );
            throttledRedraw();
            debouncedGetData();
            return false;
        }
    }).mousewheel(function(e, d) {
        var wMouse = xToW(e.clientX - $(this).offset().left),
            zoomDelta = (d > 0 ? 1 : -1),
            scaleRatio = Math.pow(zoomStep, zoomDelta);
        if (d < 0 && zoomLevel <= minZoomLevel) {
            return;
        }
        if (d >= 0 && zoomLevel >= maxZoomLevel) {
            return;
        }
        setZoomLevel(zoomLevel + zoomDelta, wMouse * (1 - 1 / scaleRatio) + wCenter / scaleRatio);
        return false;
    });
    
    $("body").mouseup(function() {
        mousedown = false;
        dragging = false;
    });
    
    var $startSpan = $(".timeline-mill-from-year"),
        $endSpan = $(".timeline-mill-to-year");
    
    function updateSpans() {
        $startSpan.text(startSlide);
        $endSpan.text(endSlide);
    }
    
    updateSpans();
    
    $slider.slider({
        range: true,
        min: 0,
        max: sliderWidth,
        values: [0, sliderWidth],
        slide: function(e, ui) {
            var y0 = Math.floor(sliderPosToYr(ui.values[0])),
                y1 = Math.floor(sliderPosToYr(ui.values[1]));
            userStartSlide = startSlide = Math.min(y0,y1);
            userEndSlide = endSlide = Math.max(y0,y1);
            updateSpans();
        }
    });
    
    $zoomSlider.slider({
        orientation: "vertical",
        min: minZoomLevel,
        max: maxZoomLevel,
        range: "min",
        value: zoomLevel,
        slide: function(e, ui) {
            setZoomLevel(ui.value);
        }
    });
    
    $(".timeline-zoom-in").click(function() {
        setZoomLevel(zoomLevel + 1);
    });
    
    $(".timeline-zoom-out").click(function() {
        setZoomLevel(zoomLevel - 1);
    });
    
    $(".timeline-mill-submit").click(function() {
        loadSearchResults({ from_year: startSlide, to_year: endSlide, show_years: 1 });
    });
    
    updateZoomFactor();
    throttledRedraw();
    getData();
    
    $(window).resize(throttledRedraw);
    
});
