﻿
/** CONSTANTS **/

var ID_DOC = "doc";
var ID_HEAD = "hd";
var ID_BODY = "bd";
var ID_FOOT = "ft";

var ID_INFO = "info";
var ID_THUMBS = "thumbs";
var ID_VIEWER = "viewer";

var ID_TITLE = "title";
var ID_DESCRIPTION = "desc";
var ID_PREV = "prev";
var ID_NEXT = "next";

var CLASS_THUMB = "thumb";
var CLASS_THUMB_SEL = "thumb-selected";

var PADDING = 20;    // the amount of margin between viewer/thumbs/info

var DZI_PATH = "http://static.seadragon.com/content/cjordan/";
var DZI_EXT = ".dzi";       // the default extension to use if no dzi src is specified
var THUMB_PATH = "thumbs/";
var THUMB_EXT = ".png";     // the default extension to use if no thumb src is specified


/** ELEMENTS **/

var docElmt = document.getElementById(ID_DOC);
var headElmt = document.getElementById(ID_HEAD);
var bodyElmt = document.getElementById(ID_BODY);    // not <body>, but <div id="bd">
var footElmt = document.getElementById(ID_FOOT);

var infoElmt = document.getElementById(ID_INFO);
var thumbsElmt = document.getElementById(ID_THUMBS);
var viewerElmt = document.getElementById(ID_VIEWER);

var titleElmt = document.getElementById(ID_TITLE);
var descriptionElmt = document.getElementById(ID_DESCRIPTION);
var prevElmt = document.getElementById(ID_PREV);
var nextElmt = document.getElementById(ID_NEXT);

// size values that need to be derived only once
var infoBorderHor;
var infoTotalHeight;
var thumbsTotalWidth;
var viewerBorderHor;
var viewerBorderVer;


/** LAYOUT **/

function initializeLayout() {
    var htmlStyle = document.documentElement.style;
    var bodyStyle = document.body.style;
    var docStyle = docElmt.style;
    
    htmlStyle.width = /*bodyStyle.width = */docStyle.width = "100%";
    htmlStyle.height = bodyStyle.height = docStyle.height = "100%";
    htmlStyle.overflow = bodyStyle.overflow = docStyle.overflow = "hidden";
    
    // derive necessary size values that only need to be derived once.
    // TEMP not accounting for padding currently, as clientWidth includes it.
    // this is fine for now since the CSS specifies no padding for anything.
    // TODO if padding is needed, derive padding via computed style.
    // TODO also derive horizontal/vertical margins automatically.
    
    infoBorderHor = infoElmt.offsetWidth - infoElmt.clientWidth;
    infoTotalHeight = infoElmt.offsetHeight + PADDING;
    thumbsTotalWidth = thumbsElmt.offsetWidth + PADDING;
    viewerBorderHor = viewerElmt.offsetWidth - viewerElmt.clientWidth;
    viewerBorderVer = viewerElmt.offsetHeight - viewerElmt.clientHeight;
    
    // update: we're going to move the footer out of the way so that the
    // thumbnail bar extends all the way to the bottom of the page.
    footElmt.style.position = "absolute";
    footElmt.style.right = thumbsTotalWidth + "px";
    footElmt.style.bottom = "0px";
    
    // as part of this change, eliminate the top and right padding
    footElmt.style.paddingTop = "0";
    footElmt.style.paddingRight = "0";
    
    // with this change, increase the info's height by the foot height
    infoElmt.style.height = infoElmt.clientHeight + footElmt.clientHeight + "px";
    infoTotalHeight += footElmt.clientHeight;
    
    updateLayout();
    Seadragon.Utils.addEvent(window, "resize", function() {
        updateLayout();
    });
}

function updateLayout() {
    // calculate the "inner" dimensions (just #bd, not #hd or #ft)
    var innerWidth = docElmt.clientWidth;
    var innerHeight = docElmt.clientHeight - headElmt.clientHeight;
    
    // need to update #bd height! #hd and #ft height are constant.
    bodyElmt.style.height = innerHeight + "px";

    infoElmt.style.width = (innerWidth - thumbsTotalWidth - infoBorderHor) + "px";

    viewerElmt.style.width = (innerWidth - thumbsTotalWidth - viewerBorderHor) + "px";
    viewerElmt.style.height = (innerHeight - infoTotalHeight - viewerBorderVer) + "px";
}

// if you don't want dynamically resizing layout, comment out this line:
initializeLayout();


/** LOGIC **/

// var data = [];   // array of items, set by gallery-data.js
var dataDict = {};  // dictionary from item.key to item's index in data
var currentIndex = 0;
var currentUrlHash = "";
var viewer = null;

// Initialization helpers

function initializeControls() {
    // make a thumbnail for each item
    for (var i = 0; i < data.length; i++) {
        createThumbnail(i);
    }
    
    // initialize links and text
    updatePrevNext();
    updateTitleDesc();
    
    // begin listening for changes to the URL (i.e. to detect back button).
    // should poll no slower than 200ms in order to maintain perceptual fusion.
    // 100ms is an average number, but lower is more crisp and responsive.
    window.setInterval(checkUrlHash, 50);
}

function createThumbnail(i) {
    var img = document.createElement("img");
    var item = data[i];

    img.alt = item.key;
    img.src = THUMB_PATH + (item.thumb || (item.key + THUMB_EXT));
        // if no thumb src explicitly specified, use key plus extension
    
    item.img = img; // remember the <img> for this item
    item.handler = function(event) {
        switchTo(i);    // no need to check current, switchTo() already does
    };
    
    thumbsElmt.appendChild(img);
    
    if (i == currentIndex) {
        selectThumb(item);
    } else {
        unselectThumb(item);
    }

    return img;
}

// Updating thumbnails, links, text, etc.

function selectThumb(item) {
    // preserve scroll position, since modifying the DOM will reset it...
    var scrollTop = thumbsElmt.scrollTop;
    var thumb = item.img;
    
    thumb.className = CLASS_THUMB_SEL;
    unlinkify(thumb);
    
    // ...and set the scroll position back to what it was, EXCEPT if the newly
    // selected thumbnail isn't fully in view, scroll enough to put it in view.
    // also temporarily set position relative to calculate offsetTop properly.
    
    thumbsElmt.style.position = "relative";
    
    var containerHeight = thumbsElmt.clientHeight;
    var scrollBottom = scrollTop + containerHeight;
    var thumbTop = thumb.offsetTop;
    var thumbBottom = thumbTop + thumb.offsetHeight;
    
    if (scrollTop > thumbTop) {
        // top of thumb is above top of scroll view
        thumbsElmt.scrollTop = thumbTop;
    } else if (scrollBottom < thumbBottom) {
        // bottom of thumb is below bottom of scroll view
        thumbsElmt.scrollTop = thumbBottom - containerHeight;
    } else {
        // already in view, all's good, preserve it
        thumbsElmt.scrollTop = scrollTop;
    }
    
    thumbsElmt.style.position = "";
}

function unselectThumb(item) {
    // preserve scroll position, since modifying the DOM will reset it...
    var scrollTop = thumbsElmt.scrollTop;
    
    item.img.className = CLASS_THUMB;
    linkify(item.img, "#" + item.key, item.title, item.handler);
    
    // ...and set the scroll position back to what it was.
    thumbsElmt.scrollTop = scrollTop;
}

function updatePrevNext() {
    // prev link
    if (currentIndex > 0) {
        var prevItem = data[currentIndex - 1];
        linkify(prevElmt, "#" + prevItem.key, prevItem.title, onPrevClick);
    } else {
        unlinkify(prevElmt);
    }
    
    // next link
    if (currentIndex < data.length - 1) {
        var nextItem = data[currentIndex + 1];
        linkify(nextElmt, "#" + nextItem.key, nextItem.title, onNextClick);
    } else {
        unlinkify(nextElmt);
    }
}

function updateTitleDesc() {
    var currentItem = data[currentIndex];
    
    titleElmt.innerHTML = "";
    titleElmt.appendChild(document.createTextNode(currentItem.title));
    
    descriptionElmt.innerHTML = "";
    descriptionElmt.appendChild(document.createTextNode(currentItem.desc));
}

// Event listeners, etc.

function switchTo(newIndex) {
    if (newIndex == currentIndex) {
        return;
    }
    
    var currentItem = data[currentIndex];
    var newItem = data[newIndex];
    
    // begin opening the new image immediately, since in silverlight it
    // requires an async http request, and in both silverlight and seajax
    // nothing can be shown until the first tile's http request.
    viewer.openDzi(DZI_PATH + newItem.dzi, newItem.xml);
    
    // needs to come before updatePrevNext() and updateTitleDesc(). also, do it
    // immediately so that multiple quick clicks all take effect.
    currentIndex = newIndex;
    
    // do all this on a timeout so that the URL anchor can change immediately.
    // this is important for prev/next, bc changing the href in the middle of
    // their click event will affect the URL anchor on this event!
    window.setTimeout(function() {
        unselectThumb(currentItem);
        selectThumb(newItem);
        
        updatePrevNext();
        updateTitleDesc();
    }, 0);
}

function onPrevClick(event) {
    if (currentIndex > 0) {
        switchTo(currentIndex - 1);
    }
}

function onNextClick(event) {
    if (currentIndex < data.length - 1) {
        switchTo(currentIndex + 1);
    }
}

function onViewerCreate(v, isReload) {
    // save the reference to this viewer
    viewer = v;

    // *now* initialize the thumbnails and links, to prevent timing errors
    // UPDATE: only if this callback isn't being fired on a Firefox reload!
    if (!isReload)
        initializeControls();
}

function checkUrlHash() {
    var newHash = window.location.hash;
    
    if (newHash == currentUrlHash) {
        return;
    }
    
    currentUrlHash = newHash;
    
    if (!newHash) {
        switchTo(0);    // since this is how the page started
        return;
    }
    
    // substr(1) removes the "#" from a non-empty hash
    var index = dataDict[newHash.substr(1)] || null;
    
    if (index !== null) {
        switchTo(index);
        return;
    }
    
    // ignore URL changes that aren't known item keys
}

// Immediate execution

(function() {
    // immediately pre-process the data to get O(1) retrieval by name.
    // update: also auto-derive dzi and thumb values if not specified.
    for (var i = 0; i < data.length; i++) {
        var datum = data[i];
        var key = datum.key;

        dataDict[key] = i;

        if (!datum.dzi)
            datum.dzi = key + DZI_EXT;
        if (!datum.thumb)
            datum.thumb = key + THUMB_EXT;
    }

    // store the initial hash, and derive the starting index value from it.
    // substr(1) removes the "#" from the hash if it's present.
    currentUrlHash = window.location.hash;
    currentIndex = dataDict[currentUrlHash.substr(1)] || 0;

    // ...and create the viewer and open the starting item immediately.
    // initialize the controls afterwards, to prevent timing errors.
    var start = data[currentIndex];
    Seadragon.ComboViewer.createAndOpenDzi("viewer",
            DZI_PATH + start.dzi, start.xml, onViewerCreate);
})();


/** UTILS **/

function linkify(elmt, href, title, onclick) {
    var parent = elmt.parentNode;
    
    if (parent.tagName.toLowerCase() == "a") {
        // already linkified, so just update the href and title. previously, we
        // were un- then re-linkifying it in order to work around a Firefox bug
        // where active "prev"/"next" links would cause scrollbars, but another
        // effect of that is that it kills the focus if you're on the keyboard.
        // so we'll keep the focus and hopefully fix the scrollbar bug somehow.
        
        parent.href = href;
        parent.title = title;
        parent.onclick = onclick;
        
        return;
    }
    
    var link = document.createElement("a");
    
    link.href = href;
    link.title = title;
    link.onclick = onclick; // this is fine (no IE memory leak), no closure
    
    parent.insertBefore(link, elmt);
    link.appendChild(elmt);
    
    return elmt;
}

function unlinkify(elmt) {
    var link = elmt.parentNode;
    
    if (link.tagName.toLowerCase() != "a") {
        return;     // not a link, nothing to unlinkify
    }
    
    var parent = link.parentNode;
    
    parent.insertBefore(elmt, link);
    parent.removeChild(link);
    
    return elmt;
}