// © 2009–2010 EPFL/LAMP
// code by Gilles Dubochet with contributions by Johannes Rudolph, "spiros" and Marcin Kubala

var topLevelTemplates = undefined;
var topLevelPackages = undefined;

var scheduler = undefined;

var kindFilterState = undefined;
var focusFilterState = undefined;

var title = $(document).attr('title');

var lastFragment = "";

$(document).ready(function() {
    $('body').layout({
        west__size: '20%',
        center__maskContents: true
    });
    $('#browser').layout({
        center__paneSelector: ".ui-west-center"
        //,center__initClosed:true
        ,north__paneSelector: ".ui-west-north"
    });
    $('iframe').bind("load", function(){
        try {
            var subtitle = $(this).contents().find('title').text();
            $(document).attr('title', (title ? title + " - " : "") + subtitle);
        } catch (e) {
            // Chrome doesn't allow reading the iframe's contents when
            // used on the local file system.
        }
        setUrlFragmentFromFrameSrc();
    });

    // workaround for IE's iframe sizing lack of smartness
    if($.browser.msie) {
        function fixIFrame() {
            $('iframe').height($(window).height() )
        }
        $('iframe').bind("load",fixIFrame)
        $('iframe').bind("resize",fixIFrame)
    }

    scheduler = new Scheduler();
    scheduler.addLabel("init", 1);
    scheduler.addLabel("focus", 2);
    scheduler.addLabel("filter", 4);

    prepareEntityList();

    configureTextFilter();
    configureKindFilter();
    configureEntityList();

    setFrameSrcFromUrlFragment();

    // If the url fragment changes, adjust the src of iframe "template".
    $(window).bind('hashchange', function() {
      if(lastFragment != window.location.hash) {
        lastFragment = window.location.hash;
        setFrameSrcFromUrlFragment();
      }
    });
});

// Set the iframe's src according to the fragment of the current url.
// fragment = "#scala.Either" => iframe url = "scala/Either.html"
// fragment = "#scala.Either@isRight:Boolean" => iframe url = "scala/Either.html#isRight:Boolean"
// fragment = "#scalaz.iteratee.package@>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]" => iframe url = "scalaz/iteratee/package.html#>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]"
function setFrameSrcFromUrlFragment() {

    function extractLoc(fragment) {
        var loc = fragment.split('@')[0].replace(/\./g, "/");
        if (loc.indexOf(".html") < 0) {
            loc += ".html";
        }
        return loc;
    }

    function extractMemberSig(fragment) {
        var splitIdx = fragment.indexOf('@');
        if (splitIdx < 0) {
            return;
        }
        return fragment.substr(splitIdx + 1);
    }

    var fragment = location.hash.slice(1);
    if (fragment) {
        var locWithMemeberSig = extractLoc(fragment);
        var memberSig = extractMemberSig(fragment);
        if (memberSig) {
            locWithMemeberSig += "#" + memberSig;
        }
        frames["template"].location.replace(location.protocol + locWithMemeberSig);
    } else {
        console.log("empty fragment detected");
        frames["template"].location.replace("package.html");
    }
}

// Set the url fragment according to the src of the iframe "template".
// iframe url = "scala/Either.html"  =>  url fragment = "#scala.Either"
// iframe url = "scala/Either.html#isRight:Boolean"  =>  url fragment = "#scala.Either@isRight:Boolean"
// iframe url = "scalaz/iteratee/package.html#>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]" => fragment = "#scalaz.iteratee.package@>@>[E,A]=scalaz.iteratee.package.Iteratee[E,A]"
function setUrlFragmentFromFrameSrc() {
  try {
    var commonLength = location.pathname.lastIndexOf("/");
    var frameLocation = frames["template"].location;
    var relativePath = frameLocation.pathname.slice(commonLength + 1);

    if(!relativePath || frameLocation.pathname.indexOf("/") < 0)
      return;

    // Add #, remove ".html" and replace "/" with "."
    fragment = "#" + relativePath.replace(/\.html$/, "").replace(/\//g, ".");

    // Add the frame's hash after an @
    if(frameLocation.hash) fragment += ("@" + frameLocation.hash.slice(1));

    // Use replace to not add history items
    lastFragment = fragment;
    location.replace(fragment);
  }
  catch(e) {
    // Chrome doesn't allow reading the iframe's location when
    // used on the local file system.
  }
}

var Index = {};

(function (ns) {
    function openLink(t, type) {
        var href;
        if (type == 'object') {
            href = t['object'];
        } else {
            href = t['class'] || t['trait'] || t['case class'] || t['type'];
        }
        return [
            '<a class="tplshow" target="template" href="',
            href,
            '"><img width="13" height="13" class="',
            type,
            ' icon" src="lib/',
            type,
            '.png" />'
        ].join('');
    }

    function createPackageHeader(pack) {
        return [
            '<li class="pack">',
            '<a class="packfocus">focus</a><a class="packhide">hide</a>',
            '<a class="tplshow" target="template" href="',
            pack.replace(/\./g, '/'),
            '/package.html">',
            pack,
            '</a></li>'
        ].join('');
    };

    function createListItem(template) {
        var inner = '';


        if (template.object) {
            inner += openLink(template, 'object');
        }

        if (template['class'] || template['trait'] || template['case class'] || template['type']) {
            inner += (inner == '') ?
                '<div class="placeholder" />' : '</a>';
            inner += openLink(template, template['trait'] ? 'trait' : template['type'] ? 'type' : 'class');
        } else {
            inner += '<div class="placeholder"/>';
        }

        return [
            '<li>',
            inner,
            '<span class="tplLink">',
            template.name.replace(/^.*\./, ''),
            '</span></a></li>'
        ].join('');
    }


    ns.createPackageTree = function (pack, matched, focused) {
        var html = $.map(matched, function (child, i) {
            return createListItem(child);
        }).join('');

        var header;
        if (focused && pack == focused) {
            header = '';
        } else {
            header = createPackageHeader(pack);
        }

        return [
            '<ol class="packages">',
            header,
            '<ol class="templates">',
            html,
            '</ol></ol>'
        ].join('');
    }

    ns.keys = function (obj) {
        var result = [];
        var key;
        for (key in obj) {
            result.push(key);
        }
        return result;
    }

    var hiddenPackages = {};

    function subPackages(pack) {
        return $.grep($('#tpl ol.packages'), function (element, index) {
            var pack = $('li.pack > .tplshow', element).text();
            return pack.indexOf(pack + '.') == 0;
        });
    }

    ns.hidePackage = function (ol) {
        var selected = $('li.pack > .tplshow', ol).text();
        hiddenPackages[selected] = true;

        $('ol.templates', ol).hide();

        $.each(subPackages(selected), function (index, element) {
            $(element).hide();
        });
    }

    ns.showPackage = function (ol, state) {
        var selected = $('li.pack > .tplshow', ol).text();
        hiddenPackages[selected] = false;

        $('ol.templates', ol).show();

        $.each(subPackages(selected), function (index, element) {
            $(element).show();

            // When the filter is in "packs" state,
            // we don't want to show the `.templates`
            var key = $('li.pack > .tplshow', element).text();
            if (hiddenPackages[key] || state == 'packs') {
                $('ol.templates', element).hide();
            }
        });
    }

})(Index);

function configureEntityList() {
    kindFilterSync();
    configureHideFilter();
    configureFocusFilter();
    textFilter();
}

/* Updates the list of entities (i.e. the content of the #tpl element) from the raw form generated by Scaladoc to a
   form suitable for display. In particular, it adds class and object etc. icons, and it configures links to open in
   the right frame. Furthermore, it sets the two reference top-level entities lists (topLevelTemplates and
   topLevelPackages) to serve as reference for resetting the list when needed.
   Be advised: this function should only be called once, on page load. */
function prepareEntityList() {
    var classIcon = $("#library > img.class");
    var traitIcon = $("#library > img.trait");
    var typeIcon = $("#library > img.type");
    var objectIcon = $("#library > img.object");
    var packageIcon = $("#library > img.package");

    $('#tpl li.pack > a.tplshow').attr("target", "template");
    $('#tpl li.pack').each(function () {
        $("span.class", this).each(function() { $(this).replaceWith(classIcon.clone()); });
        $("span.trait", this).each(function() { $(this).replaceWith(traitIcon.clone()); });
        $("span.type", this).each(function() { $(this).replaceWith(typeIcon.clone()); });
        $("span.object", this).each(function() { $(this).replaceWith(objectIcon.clone()); });
        $("span.package", this).each(function() { $(this).replaceWith(packageIcon.clone()); });
    });
    $('#tpl li.pack')
        .prepend("<a class='packhide'>hide</a>")
        .prepend("<a class='packfocus'>focus</a>");
}

/* Handles all key presses while scrolling around with keyboard shortcuts in left panel */
function keyboardScrolldownLeftPane() {
    scheduler.add("init", function() {
        $("#textfilter input").blur();
        var $items = $("#tpl li");
        $items.first().addClass('selected');

        $(window).bind("keydown", function(e) {
            var $old = $items.filter('.selected'),
                $new;

            switch ( e.keyCode ) {

            case 9: // tab
                $old.removeClass('selected');
                break;

            case 13: // enter
                $old.removeClass('selected');
                var $url = $old.children().filter('a:last').attr('href');
                $("#template").attr("src",$url);
                break;

            case 27: // escape
                $old.removeClass('selected');
                $(window).unbind(e);
                $("#textfilter input").focus();

                break;

            case 38: // up
                $new = $old.prev();

                if (!$new.length) {
                    $new = $old.parent().prev();
                }

                if ($new.is('ol') && $new.children(':last').is('ol')) {
                    $new = $new.children().children(':last');
                } else if ($new.is('ol')) {
                    $new = $new.children(':last');
                }

                break;

            case 40: // down
                $new = $old.next();
                if (!$new.length) {
                    $new = $old.parent().parent().next();
                }
                if ($new.is('ol')) {
                    $new = $new.children(':first');
                }
                break;
            }

            if ($new.is('li')) {
                $old.removeClass('selected');
                $new.addClass('selected');
            } else if (e.keyCode == 38) {
                $(window).unbind(e);
                $("#textfilter input").focus();
            }
        });
    });
}

/* Configures the text filter  */
function configureTextFilter() {
    scheduler.add("init", function() {
        $("#textfilter").append("<span class='pre'/><span class='input'><input id='index-input' type='text' accesskey='/'/></span><span class='post'/>");
        var input = $("#textfilter input");
        resizeFilterBlock();
        input.bind('keyup', function(event) {
            if (event.keyCode == 27) { // escape
                input.attr("value", "");
            }
            if (event.keyCode == 40) { // down arrow
                $(window).unbind("keydown");
                keyboardScrolldownLeftPane();
                return false;
            }
            textFilter();
        });
        input.bind('keydown', function(event) {
            if (event.keyCode == 9) { // tab
                $("#template").contents().find("#mbrsel-input").focus();
                input.attr("value", "");
                return false;
            }
            textFilter();
        });
        input.focus(function(event) { input.select(); });
    });
    scheduler.add("init", function() {
        $("#textfilter > .post").click(function(){
            $("#textfilter input").attr("value", "");
            textFilter();
        });
    });
}

function compilePattern(query) {
    var escaped = query.replace(/([\.\*\+\?\|\(\)\[\]\\])/g, '\\$1');

    if (query.toLowerCase() != query) {
        // Regexp that matches CamelCase subbits: "BiSe" is
        // "[a-z]*Bi[a-z]*Se" and matches "BitSet", "ABitSet", ...
        return new RegExp(escaped.replace(/([A-Z])/g,"[a-z]*$1"));
    }
    else { // if query is all lower case make a normal case insensitive search
        return new RegExp(escaped, "i");
    }
}

// Filters all focused templates and packages. This function should be made less-blocking.
//   @param query The string of the query
function textFilter() {
    var query = $("#textfilter input").attr("value") || '';
    var queryRegExp = compilePattern(query);

    // if we are filtering on types, then we have to display types
    // ("display packages only" is not possible when filtering)
    if (query !== "") {
        kindFilter("all");
    }

    // Three things trigger a reload of the left pane list:
    // typeof textFilter.lastQuery === "undefined" <-- first load, there is nothing yet in the left pane
    // textFilter.lastQuery !== query              <-- the filter text has changed
    // focusFilterState != null                    <-- a package has been "focused"
    if ((typeof textFilter.lastQuery === "undefined") || (textFilter.lastQuery !== query) || (focusFilterState != null)) {

        textFilter.lastQuery = query;

        scheduler.clear("filter");

        $('#tpl').html('');

        var index = 0;

        var searchLoop = function () {
            var packages = Index.keys(Index.PACKAGES).sort();

            while (packages[index]) {
                var pack = packages[index];
                var children = Index.PACKAGES[pack];
                index++;

                if (focusFilterState) {
                    if (pack == focusFilterState ||
                        pack.indexOf(focusFilterState + '.') == 0) {
                        ;
                    } else {
                        continue;
                    }
                }

                var matched = $.grep(children, function (child, i) {
                    return queryRegExp.test(child.name);
                });

                if (matched.length > 0) {
                    $('#tpl').append(Index.createPackageTree(pack, matched,
                                                             focusFilterState));
                    scheduler.add('filter', searchLoop);
                    return;
                }
            }

            $('#tpl a.packfocus').click(function () {
                focusFilter($(this).parent().parent());
            });
            configureHideFilter();
        };

        scheduler.add('filter', searchLoop);
    }
}

/* Configures the hide tool by adding the hide link to all packages. */
function configureHideFilter() {
    $('#tpl li.pack a.packhide').click(function () {
        var packhide = $(this)
        var action = packhide.text();

        var ol = $(this).parent().parent();

        if (action == "hide") {
            Index.hidePackage(ol);
            packhide.text("show");
        }
        else {
            Index.showPackage(ol, kindFilterState);
            packhide.text("hide");
        }
        return false;
    });
}

/* Configures the focus tool by adding the focus bar in the filter box (initially hidden), and by adding the focus
   link to all packages. */
function configureFocusFilter() {
    scheduler.add("init", function() {
        focusFilterState = null;
        if ($("#focusfilter").length == 0) {
            $("#filter").append("<div id='focusfilter'>focused on <span class='focuscoll'></span> <a class='focusremove'><img class='icon' src='lib/remove.png'/></a></div>");
            $("#focusfilter > .focusremove").click(function(event) {
                textFilter();

                $("#focusfilter").hide();
                $("#kindfilter").show();
                resizeFilterBlock();
                focusFilterState = null;
            });
            $("#focusfilter").hide();
            resizeFilterBlock();
        }
    });
    scheduler.add("init", function() {
        $('#tpl li.pack a.packfocus').click(function () {
            focusFilter($(this).parent());
            return false;
        });
    });
}

/* Focuses the entity index on a specific package. To do so, it will copy the sub-templates and sub-packages of the
   focuses package into the top-level templates and packages position of the index. The original top-level
     @param package The <li> element that corresponds to the package in the entity index */
function focusFilter(package) {
    scheduler.clear("filter");

    var currentFocus = $('li.pack > .tplshow', package).text();
    $("#focusfilter > .focuscoll").empty();
    $("#focusfilter > .focuscoll").append(currentFocus);

    $("#focusfilter").show();
    $("#kindfilter").hide();
    resizeFilterBlock();
    focusFilterState = currentFocus;
    kindFilterSync();

    textFilter();
}

function configureKindFilter() {
    scheduler.add("init", function() {
        kindFilterState = "all";
        $("#filter").append("<div id='kindfilter'><a>display packages only</a></div>");
        $("#kindfilter > a").click(function(event) { kindFilter("packs"); });
        resizeFilterBlock();
    });
}

function kindFilter(kind) {
    if (kind == "packs") {
        kindFilterState = "packs";
        kindFilterSync();
        $("#kindfilter > a").replaceWith("<a>display all entities</a>");
        $("#kindfilter > a").click(function(event) { kindFilter("all"); });
    }
    else {
        kindFilterState = "all";
        kindFilterSync();
        $("#kindfilter > a").replaceWith("<a>display packages only</a>");
        $("#kindfilter > a").click(function(event) { kindFilter("packs"); });
    }
}

/* Applies the kind filter. */
function kindFilterSync() {
    if (kindFilterState == "all" || focusFilterState != null) {
        $("#tpl a.packhide").text('hide');
        $("#tpl ol.templates").show();
    } else {
        $("#tpl a.packhide").text('show');
        $("#tpl ol.templates").hide();
    }
}

function resizeFilterBlock() {
    $("#tpl").css("top", $("#filter").outerHeight(true));
}
