// Globals
content      = null;
currentlyShown = [null, null];
galleries    = {};
hashHash     = {};
shippingCost = undefined;
preloader    = {};
numSlides    = null;
slideParams  = null;
slideNum     = null;

// Constants
GALLERY_NAMES = ["werewolves", "animals", "paleo", "fantasy"];
GALLERY_NAME = {"news": "", "werewolves": " - Werewolves Gallery",
    "animals": " - Animals Gallery", "paleo": " - Paleo Gallery",
    "fantasy": " - Fantasy Gallery", "cart": " - My Shopping Cart",
    "info": " - Information"};
GALLERY_NAME2 = {"news": "", "werewolves": "Werewolves Gallery - ",
    "animals": "Animals Gallery - ", "paleo": "Paleo Gallery - ",
    "fantasy": "Fantasy Gallery - ", "cart": "My Shopping Cart - ",
    "info": "Information - "};
GALLERY_DESC = {"news": "Animals, werewolves, fantasy, and paleo art. Originals and prints in acrylic, marker, and colored pencil.",
    "werewolves": "Werewolves from a nightmare in acrylic, marker, and colored pencil. Originals and prints of terrifying and amusing lycanthropes.",
    "animals": "Animals and anthropomorphic furries in acrylic, marker, and colored pencil. Originals and prints of amusing and beautiful creatures.",
    "paleo": "Dinosaurs and paleo fossil restorations in acrylic, marker, and colored pencil. Originals and prints of scientifically-accurate pre-historic mammals and extinct reptiles.",
    "fantasy": "Mythical and fantasy creatures in acrylic, marker, and colored pencil. Originals and prints from the World of Warcraft, physics, Lovecraft, and my own creations.",
    "cart": "",
    "info": "I am a freelance fantasy and paleo-illustrator. My work has been featured in books, magazines, Wikipedia, natural history museums, news articles, and scientific papers. I love fantasy and animals, both ancient and modern."};
CART_CLASSES = ["hidden", "one", "two", "three", "four", "five", "six", "seven",
    "eight", "nine", "ten"];
BANNER_STYLES = {"b1": {"text-align": "left"}, "b2": {"text-align": "center"},
    "b3": {"text-align": "right"}};
MAX_SLIDE_HEIGHT = 320;
MAX_SLIDE_WIDTH = 500;

// Utility functions
function hashLength($hash) {
    var $count = 0;
    for (var $i in $hash) $count++
    return $count;
}

function plural($n, $singular, $plural) {
    if ($n == 1) return $n + $singular; else return $n + $plural;
}

function processEmailLink($link) {
    $link.attr("href", "mailto:" + $link.attr("rel") + "@gmail.com");
}

// Highlight (or un-highlight) a given gallery name link and page number
function highlightButtons($enable, $galleryName) {
    if ($enable) {
        $("div#menu_" + $galleryName).removeClass("faded");
    } else {
        $("div#menu_" + $galleryName).addClass("faded");
    }
}

function showPage($galleryName, $page) {
    var $html = "";

    // Hide the current gallery
    highlightButtons(false, currentlyShown[0]);
    if (currentlyShown[0] == "cart") {
        $("div#shopping_cart,div#cart").addClass("hidden");
    } else if ((currentlyShown[0] || "news") == "news") {
        $("div#index").addClass("hidden");
    } else if (currentlyShown[0] == "info") {
        $("div#info").addClass("hidden");
    } else {
        $("div#" + currentlyShown[0]).addClass("hidden");
    }

    // Show the new gallery
    highlightButtons(true, $galleryName);
    if ($galleryName == "cart") {
        $("div#shopping_cart,div#cart").removeClass("hidden");
    } else if ($galleryName == "news") {
        $("div#index,img#news").removeClass("hidden");
    } else if ($galleryName == "info") {
        $("div#info").removeClass("hidden");
    } else {
        $("div#" + $galleryName).removeClass("hidden");
    }
}

function onHashChange($event) {
    var $params = $.deparam(window.location.hash.substring(2));
    var $galleryName = $params["gallery"] || "news";
    var $image = $params["image"];
    var $cart = $params["cart"];

    // If cart is specified in the URL, then this overrides any given gallery
    if ($cart) {
        $galleryName = "cart";

        // Cart command may be "show" or a number ("0", "1"...) to
        // indicate that we are viewing an image on the shopping cart.
        updateCart($cart);
        if ($cart == "show") {
            $image = null;
        } else {
            $image = $cart;
            var $galleryObj = galleries[$galleryName];
            $galleryObj.imageIndex = parseInt($image);
        }
    } else {
        var $galleryObj = galleries[$galleryName];
        if ($galleryObj) $galleryObj.imageIndex = $galleryObj.fnHash[$image];
    }

    // Update which page we're seeing?
    if (currentlyShown[0] != $galleryName) {
        showPage($galleryName);
    }

    // At this point we may need to start showing an image, stop showing an
    // image, or change to a different image
    if ($image) {
        var $title = $galleryObj.titles[$galleryObj.imageIndex];

        document.title = "The Art of H. Kyoht Luterman - " + $title;
        $("div#seo h1").text($title + " - The Art of H. Kyoht Luterman");
        $("div#seo p#content").html($galleryObj.descriptions[$galleryObj.imageIndex]);

        if (currentlyShown[1]) {
            // Change images
            $.prettyPhoto._hideContent(function() {
                $.prettyPhoto.open($galleryObj); });
        } else {
            // Start showing an image
            $.prettyPhoto.open($galleryObj);
        }
    } else {
        // Set the gallery name
        document.title = "The Art of H. Kyoht Luterman" +
            GALLERY_NAME[$galleryName];
        $("div#seo h1").text(GALLERY_NAME2[$galleryName] +
            "The Art of H. Kyoht Luterman");
        $("div#seo p#content").text(GALLERY_DESC[$galleryName]);

        // Stop showing an image
        if (currentlyShown[1]) $.prettyPhoto.close();
    }

    currentlyShown = [$galleryName, $image];

    // Scroll to top
    window.scroll(0, 0);
}

// Cookie handling functions:

// Check for a cart in the cookie list and convert it to an hash[sku]=qty
function orderCookieToHash() {
    var $retVal = {};
    var $cookieString = $.cookie("cart");

    // Is there a cart yet?
    if ($cookieString) {
        // Yes, iterate through each item (delimited with ";")
        $.each($cookieString.split(";"), function($index, $item) {
            // Each item is formatted as sku,qty
            var $components = $item.split(",");
            $retVal[$components[0]] = parseInt($components[1]);
        });
    }
    return $retVal;
}

// Take an hash[sku]=qty and save it in the shopping cart cookie
function hashToOrderCookie($hash) {
    var $temp = new Array();

    // Loop through each hash element
    $.each($hash, function($sku, $qty) {
        // Create a sku,qty entry
        $temp.push($sku + "," + $qty); });

    // Merge the items together (";" delimiter) and save in a cookie
    $.cookie("cart", $temp.join(";"), { expires: 30 });
}

function cartAdd() {
    var $image = $.bbq.getState("image");
    var $id = $image.split(".")[0];
    var $hash = orderCookieToHash();
    $hash[$id] = ($hash[$id] || 0) + 1;
    hashToOrderCookie($hash);
    $.bbq.pushState({cart: "show"});
}

// Update the shopping cart
function updateCart($cartCmd) {
    var $cart = $("div#shopping_cart");
    var $template = $cart.find("tr.hidden");
    var $hash = orderCookieToHash();
    var $numItems = hashLength($hash);
    var $totItems = 0;
    var $totCost = shippingCost;
    var $country = $cart.find("select#country");
    var $shipOpt = $cart.find("select#shipping");

    // Gallery object to display items in cart
    var $gallery = {
        name: "cart",
        images: new Array(), // full urls
        titles: new Array(),
        descriptions: new Array(),
        fnHash: new Array(), // {fn1.jpg: 0, fn2.jpg: 1, }
        imageIndex: null,
        hashes: new Array() // [#<current hash>&cart=0, ...1, ...2]
    };

    // Remove old content
    $cart.find("tr.dynamic").remove();

    // Loop through the shopping cart
    var $i = 0;
    $.each($hash, function($sku, $qty) {
        // Duplicate the template row
        var $row = $($template.clone());
        $row.removeClass("hidden").addClass("dynamic");

        // Find the item in the catalog
        var $product = content.find("item#" + $sku);
        var $choice = $product.find("choice#print");
        var $costPer = Number($choice.attr("cost"));
        var $cost  = $qty * $costPer;
        var $gName = $product.find("gallery").text();
        var $title = $product.find("title").text();
        var $state = $.bbq.getState();
        var $filename = $sku + ".jpg";
        $state["cart"] = $i;
        var $href = $.param.fragment("", $state);
        $totItems += $qty;
        $totCost += $cost;

        // Save image details in a gallery
        $gallery.images.push($product.find("original").text());
        $gallery.titles.push($title);
        $gallery.descriptions.push($product.find("description").text());
        $gallery.fnHash[$filename] = $i;
        $gallery.hashes.push($href);

        // Populate the new row
        var $optSel = $row.find("select");
        $optSel.attr("id", $i);
        $i++;
        $row.find("td.qty").append($qty).find("input").attr({name: "quantity_" +
            $i, value: $qty});
        var $increase = $row.find(".increase");
        $increase.click(function($event) { adjustQty($event, $sku, +1); });
        var $decrease = $row.find(".decrease");
        if ($qty == 1) {
            $decrease.addClass("faded");
        } else {
            $decrease.click(function($event) { adjustQty($event, $sku, -1); });
        }
        $row.find("span.ptype").append($choice.html());
        $row.find("a.title").append($title).attr("href", $href);
        $row.find("a.gname").html($gName).attr("href", "#!gallery=" + $gName);
        $row.find("td.description input").attr({name: "item_name_" + $i,
            value: $choice.html() + ' of "' + $title + '"'});
        $row.find("td.cost").append("$" +
            $cost.toFixed(2)).find("input").attr({name: "amount_" + $i,
            value: $costPer});
        $row.find("img.remove").click(function($event) {
            removeItem($event, $sku); });

        // Add the new row
        $template.before($row);
    });
    galleries["cart"] = $gallery;

    // Shipping
    $i++;
    $cart.find("input#shipping").attr({name: "amount_" + $i,
        value: shippingCost});
    var $handling = $shipOpt.find("option:selected").text() + " shipping (" +
        $country.find("option:selected").text() + ")";
    $cart.find("input#handling").attr({name: "item_name_" + $i,
        value: $handling});

    // Show totals
    $cart.find("td.qty.tally").html(plural($totItems, " item", " items"));
    if ($totCost) $cart.find("td.cost.tally").html("$" + $totCost.toFixed(2));

    // Empty cart?
    $cart.find("div#banner,div#empty,table,input#checkout,div#doublecheck").removeClass("hidden");
    if ($numItems) {
        $cart.find("div#empty").addClass("hidden");
    } else {
        $cart.find("div#banner,table,input#checkout,div#doublecheck").addClass("hidden");
    }
    showCart($numItems);

    // Skin the country select (shipping select is done inside changeCountry).
    // We do this with a timer to give the browser a moment to render the
    // original select. Select_skin uses that information for sizing of the
    // skinned control.
    setTimeout(function() { $("select").select_unskin().select_skin(); }, 100);
}

function adjustQty($event, $sku, $adj) {
    var $hash = orderCookieToHash();
    $hash[$sku] += $adj;
    hashToOrderCookie($hash);
    updateCart("show");
}

function removeItem($event, $sku) {
    var $hash = orderCookieToHash();
    delete $hash[$sku];
    hashToOrderCookie($hash);
    updateCart("show");
}

function showCart($numItems) {
    var $div = $("div#menu_cart");
    var $classNum = ($numItems < 10) ? $numItems : 10;
      $div.find("img").attr("title", plural($numItems, " item", " items") +
          " in your shopping cart");
    $div.removeClass().addClass(CART_CLASSES[$classNum] + " faded");
}

function changeCountry($event) {
    var $country = $("select#country");
    var $shipOpt = $("select#shipping");
    var $countryId = $country.val();
    var $options = content.find("shipping#" + $countryId + " choice");

    // List shipping options
    $shipOpt.html("");
    $options.each(function ($index, $method) {
        $shipOpt.append("<option value='" + $($method).attr("rate") + "'>" +
            $($method).attr("method") + "</option>");
    });

    // Skin the shipping select (country select is done inside onContent). We do
    // this with a timer to give the browser a moment to render the original
    // select. Select_skin uses that information for sizing of the skinned
    // control.
    setTimeout(function() {
        $("select#shipping").select_unskin().select_skin(); }, 100);

    changeShipping($event);
}

function changeShipping($event) {
    var $rate = $("select#shipping").val();
    if ($rate) {
        shippingCost = Number($rate);
        $("td#shipcost").html("$" + shippingCost.toFixed(2) + " *");
        $.cookie("shipping", $("select#country").val() + "," + $rate,
            { expires: 365 });
    }
    updateCart();
}

function onReady() {
    // Fix Twitter URL-mangling
    if (location.href.search("~") != -1) {
        location.href = location.href.replace("~", "&");
    }

    // Enable hashbang
    $.param.fragment.ajaxCrawlable(true);

    // Initialize prettyPhoto
    $.fn.prettyPhoto();

    // Load semi-dynamic content
    var $now = new Date();
    $.ajax({url: "content.xml?" + $now.getTime(), success: onContent,
        dataType: "html"});
}

function parseLink($galleryName, $link) {
    var $gallery = galleries[$galleryName];
    var $image = $link.find("img");

    // Gather the image link parts (gallery & filename)
    var $parts = $link.attr("href").split("/").slice(-3);

    var $hash = "#!gallery=" + $galleryName + "&image=" + $parts[2];
    hashHash[$link.attr("href")] = $hash;

    // First image in the gallery?
    if (! $gallery) {
        $gallery = {
            name: $galleryName,
            images: new Array(),
            titles: new Array(),
            descriptions: new Array(),
            fnHash: new Object(),
            imageIndex: null,
            hashes: new Array()
        };
    }
    var $index = $gallery.images.length;

    // Add details about this item to the gallery
    $gallery.images.push($link.attr("href"));
    $gallery.titles.push($image.attr("title"));
    $gallery.descriptions.push($image.attr("alt"));
    $gallery.hashes.push($hash);

    // Save the image index
    $gallery.fnHash[$parts[2]] = $index;

    // Update the global (gag!) gallery object
    galleries[$galleryName] = $gallery;

    $link.click(function ($event) {
        var $parts = $(this).attr("href").split("/").slice(-3);
        $event.preventDefault();
        $.bbq.pushState({image: $parts[2]});
    });
}

function onContent($xml) {
    content = $($xml);
    var $country = $("select#country");

    // Load the galleries
    $(GALLERY_NAMES).each(function ($index, $galleryName) {
        $("div#" + $galleryName).html(content.find("gallery[name=" +
            $galleryName + "]").html());
        $("div#" + $galleryName + " a").each(function ($index, $link) {
            parseLink($galleryName, $($link)); });
    });

    // Load the news
    $("div#index ul").replaceWith(content.find("news").html());

    // Link the news
    $("div#index a.dupLink").each(function ($index, $link) {
        processDupLink($($link)) });

    // Hook the hash change function and begin the initial navigation
    $(window).bind("hashchange", onHashChange);
    $(window).trigger("hashchange");

    // Fix e-mails
    $("a.email").each(function ($index, $link) {
        processEmailLink($($link)); } );

    // Fix "continue shopping" button
    $("a#continue").click(function($event) {
        $event.preventDefault();
        $.bbq.removeState("cart");
    });

    // Fix shopping cart
    $("div#menu_cart a").click(function($event){
        $event.preventDefault();
        $.bbq.pushState({cart: "show"});
    });
    showCart(hashLength(orderCookieToHash()));
    $("input#checkout").click(function($event) { $.cookie("cart", null); });

    // List shipping options
    content.find("shipping").each(function ($index, $dest) {
        $country.append("<option value='" + $($dest).attr("id") + "'>" +
        $($dest).attr("country") + "</option>");
    });

    // Handle select change
    $("select#shipping").change(changeShipping);
    $("select#country").change(changeCountry);

    // Does user have a preferred country and shipping method?
    var $shipping = $.cookie("shipping") || "0,0";
    var $array = $shipping.split(",");
    $country.val($array[0]);
    changeCountry();
    $("select#shipping").val($array[1]);
    changeShipping();

    // Link banner buttons
    $("div#info td").each(parseBanner);

    // Load slideshow
    content.find("slide").each(preloadSlide);
    setTimeout(testSlideshow, 100);
}

function processDupLink($link) {
    $link.click(function($event) {
        $event.preventDefault();
        $.bbq.pushState(hashHash[$link.attr("href")]);
    });
}

function parseBanner($index, $val) {
    var $cell = $($val);
    $cell.find("a").click(function ($event) {
        clickBanner($event, $cell.attr("id")); });
}

function clickBanner($event, $id) {
    // When user clicks a banner ad, create and show the HTML to display the
    // banner. Unhighlight any previously-clicked banner.
    $event.preventDefault();
    var $td = $("div#info td#" + $id);
    var $url = $td.find("img").attr("src");
    var $html =
        "&lt;a href='http://kyoht.com'>&lt;img src='http://kyoht.com/" + $url +
        "' />&lt;/a>";
    $("div#info tr#ht").removeClass("hidden").css(BANNER_STYLES[$id]).find("tt").html($html);
    $("div#info tr#banners td").removeClass("highlight");
    $td.addClass("highlight");
}

function preloadSlide($index, $val) {
    // Add thumbnail & image to the preloader
    $slide = $($val);
    numSlides = $index + 1;

    var $img = new Image();
    $img.src = $slide.attr("thumb");
    preloader["T" + $index] = $img;
    var $img = new Image();
    $img.src = $slide.attr("image");
    preloader["I" + $index] = $img;
}

function testSlideshow() {
    // Done preloading?
    for ($index in preloader) if (!preloader[$index].complete) {
        // No, check again in another 100ms
        setTimeout(testSlideshow, 100);
        return;
    }

    // Create HTML for each thumbnail and image
    content.find("slide").each(parseSlide);

    // The left and right sides get an extra copy of the thumbnails. This way we
    // don't have to rearrange the images. We just hide as many other ones and
    // the second set will be present for wrapping around the list.
    content.find("slide").each(parseThumbs);

    // Start the slideshow
    slideParams = showSlide(0);
}

function parseSlide($index, $val) {
    $slide = $($val);
    var $link = $slide.attr("link");
    var $title = $slide.html();

    // Thumbnails are full-size
    var $thumbHtml = "<a href='" + $link + "'><img id='T" + $index + "' src='" +
        preloader["T" + $index].src + "' title='" + $title + "' /></a>";

    // Central images are scaled to fit within the area
    var $w = preloader["I" + $index].width;
    var $h = preloader["I" + $index].height;
    if ($h > MAX_SLIDE_HEIGHT) {
        $w = $w * MAX_SLIDE_HEIGHT / $h;
        $h = MAX_SLIDE_HEIGHT;
    }
    if ($w > MAX_SLIDE_WIDTH) {
        $w = preloader["I" + $index].width;
        $h = preloader["I" + $index].height;
        $h = $h * MAX_SLIDE_WIDTH / $w;
        $w = MAX_SLIDE_WIDTH;
    }
    var $imageHtml = "<a href='" + $link + "'><img id='I" + $index + "' src='" +
        $slide.attr("image") + "' width='" + $w + "' height='" + $h +
        "' title='" + $title + "' /></a>";

    // Add thumbnails and images
    $("div#left div").append($thumbHtml);
    $("div#center div").append($imageHtml);
    $("div#right div").append($thumbHtml);
}

function parseThumbs($index, $val) {
    // Add a second copy of the thumbnails to the left & right
    $slide = $($val);
    var $link = $slide.attr("link");
    var $title = $slide.html();
    var $thumbHtml = "<a href='" + $link + "'><img id='T" +
        ($index + numSlides) + "' src='" + preloader["T" + $index].src +
        "' title='" + $title + "' /></a>";
    $("div#left div").append($thumbHtml);
    $("div#right div").append($thumbHtml);
}

function showSlide($index) {
    var $i;
    slideNum = $index;

    // To show slide N:
    // * On left side, show 0 - (7+N)
    // * In center, show N
    // * On right, show (N+1) - 15
    $("div#right div img").css({"opacity": 1.0});
    for ($i = 0; $i < numSlides; $i++) {
        var $slideL = $("div#left div img#T" + ($i + numSlides));
        var $image = $("div#center div img#I" + $i);
        var $slideR = $("div#right div img#T" + $i);
        if ($i < $index) {
            $slideL.removeClass("hidden");
        } else {
            $slideL.addClass("hidden");
        }
        if ($i == $index) {
            $image.removeClass("hidden");
        } else {
            $image.addClass("hidden");
        }
        if ($i <= $index) {
            $slideR.addClass("hidden");
        } else {
            $slideR.removeClass("hidden");
        }
    }

    // Position is based around size of center item (centered)
    var $wCurr = $("div#center div img#I" + $index).width();
    var $offXCurr = (829 - $wCurr) / 2;
    var $offX2Curr = $offXCurr + $wCurr + 18;
    var $offYCurr = (342 - $("div#center div img#I" + $index).height()) / 2;

    // Position left and right lists to butt up against center
    var $xCurr = $offXCurr - (118 * (numSlides + $index));
    var $index2 = ($index + 1) % numSlides;

    // Pre-calculate the same for the next item
    var $wNext = $("div#center div img#I" + $index2).width();
    var $offXNext = (829 - $wNext) / 2;
    var $offX2Next = $offXNext + $wNext - 100;
    var $offYNext = (342 - $("div#center div img#I" + $index2).height()) / 2;
    var $xNext = $offXNext - (118 * (numSlides + $index + 1));

    var $retVal = {"leftCurr": $xCurr + "px", "xCurr": $offXCurr + "px",
        "yCurr": $offYCurr, "rightCurr": $offX2Curr + "px",
        "leftNext": $xNext + "px", "xNext": $offXNext + "px",
        "yNext": $offYNext, "rightNext": $offX2Next + "px"};

    // Position left, center, and right
    $("div#left div").css({"left": $retVal["leftCurr"]});
    $("div#center div").css({"left": $retVal["xCurr"],
        "top": $retVal["yCurr"]});
    $("div#right div").css({"left": $retVal["rightCurr"]});

    // In two seconds, fade this slide out and proceed to the next
    setTimeout(nextSlide, 2000);

    return $retVal;
}

function nextSlide() {
    // Fade out this slide and the next
    $("div#center div").animate({"opacity": 0.0}, 500, "linear", fadeIn);
    $("div#right div img#T" + (slideNum + 1)).animate({"opacity": 0.0}, 500);

    // Slide the left and right to their new position
    $("div#left div").animate({"left": slideParams["leftNext"]}, 1000);
    $("div#right div").animate({"left": slideParams["rightNext"]}, 1000);
}

function fadeIn() {
    // Half-way through the animation, start fading in the new and old
    $("div#left div img#T" + (slideNum + numSlides)).removeClass("hidden").
        css({"opacity": 0.0}).animate({"opacity": 1.0}, 500);
    $("div#center div img#I" + slideNum).addClass("hidden"); // Okay to hide now
    slideNum = (slideNum + 1) % numSlides; // Next slide
    $("div#center div img#I" + slideNum).removeClass("hidden");
    $("div#center div").css({"left": slideParams["xNext"],
        "top": slideParams["yNext"]}).animate({"opacity": 1.0}, 500);

    // Delay again before advancing once more
    setTimeout(function () { slideParams = showSlide(slideNum); }, 2000);
}

