$(function() {
    var state = { group: null, contact: null, },
        groups = null,
        templates = new ajaxCache({ dataType: "text" });

    function restoreState() {
        var currentState = window.location.hash.slice(1),
            all = $("#groups li:first > a");

        state.group = all.data();

        if (/^\/contacts\/\d+$/.test(currentState) ||
            /^\/groups\/[^\/]+\/contacts\/\d+$/.test(currentState)) {
            loadContact(currentState);
            return;
        }
        else if (/^\/groups\/[^\/]+\/contacts$/.test(currentState)) {
            var link = $("#groups a[href='" +
                window.location.hash.replace("'","\\'").replace(" ","%20") +
                "']");
            if (link.length) { link.click(); return }
        }
        all.click();
    }

    function getState() {
        return (state.contact && state.contact.href) ||
               (state.group && state.group.href) || "";
    }

    function showError(msg) {
        var alertdiv = $('<div class="alert alert-error" />')
            .text("Whoops! " + msg + ". Please try again later...")
            .prepend($('<a class="close" data-dismiss="alert" data-target="#" href="#' + getState() + '">&times;</a>'));
        alertdiv.alert();
        $("#alerts").append(alertdiv).removeClass('hidden');
    }

    function errfn(msg, callback) {
        return function (err) {
            if (msg) showError(msg);
            if (callback) callback(err);
        }
    }

    function callbackOrErr(efn, callback) {
        return function(data, err) { if (err) return efn(err); return callback(data); }
    }

    function ajax(url, jqparms, callback) {
        jqparms.url = url;

        $.ajax(jqparms)
            .done(function (data) { callback(data); })
            .fail(function (jqXHR, msg) { callback(null, msg); });
    }

    function ajaxCache(jqparms) {
        var cache = {};

        function docachefn(url) {
            return function(data, err) { if (!err) cache[url].data = data; };
        }

        function flush(url, args) {
            var fn, queue = cache[url].queue;
            while (fn = queue.shift()) if (fn) fn.apply(this, args);
        }

        function trigger(url) {
            if (cache[url].data) { flush(url, [cache[url].data, null]); }
            else ajax(url,  jqparms, function() { flush(url, arguments); });
        }

        this.enqueue = function(url, callback) {
            cache[url] = cache[url] || {queue:[docachefn(url)]};
            cache[url].queue.push(callback);
            trigger(url);
        }
    }

    function jsonparms(method, data) {
        return {
            type        : method,
            dataType    : "json",
            contentType : "application/json",
            data        : JSON.stringify(data),
        }
    }

    function getTemplate(name, efn, callback) {
        templates.enqueue("static/templates/" + name + ".mustache", callbackOrErr(efn, callback));
    }

    function loadData(url, efn, callback) {
        ajax(url, {dataType: "json"}, callbackOrErr(efn, callback));
    }

    function updateGroups(callback) {
        var efn = errfn("We could not load your groups of contacts.", callback);

        loadData("groups/", efn, function(data, err) {
            groups = data;

            if (data.group = state.group) {
                groups.groups.forEach(function (group) {
                    group.active = function() {
                        return (group.href == state.group.href);
                    }
                });
            }

            getTemplate("groups", efn, function(template, err) {
                $("#groups").html(Mustache.to_html(template, data));
                $("#groups a").click(selectGroup);
                if (callback) callback();
            });
        });
    }

    function loadGroup(group, callback) {
        var efn = errfn("We could not load " + (group.group ? "the group '" + group.group + "'" : "your contacts"));

        loadData(group.href, efn, function(data, err) {
            getTemplate("contacts", efn, function(template, err) {
                if (!data.contacts) data = {contacts: data};
                $("#content").html(Mustache.to_html(template, data));
                $("#content td > a").click(function() {
                    loadContact($(this).data("href"));
                })
                $("#content .dropdown-toggle").dropdown();

                state = { group: group, contact: null};

                if (callback) callback();
            });
        });
    }

    function selectGroup() {
        var target = $(this);
        loadGroup(target.data(), function() {
            $("#groups > li").removeClass("active");
            target.parent().addClass("active");
        });
    }

    function removeContact() {
        if (state.contact.id) {
            //TODO: remove this contact
        }
    }

    function saveContact(newcontact) {
        var param = jsonparms(newcontact.id ? "PUT": "POST", newcontact),
            url= newcontact.id ? state.contact.href : "/contacts";

        ajax(url, param, function(data, err) {
            if (err) { return showError("We could not save this contact information."); }

            $.each(data, function(key, value) { $("#" + key).val(value); });
            $("#remove").removeClass("disabled");

            state.contact = data;
        });
    }

    function saveContactFields() {
        var newcontact = {},
            modified = (!state.contact.id);

        // Fetch the new values
        $("#content input").each(function () {
            newcontact[this.id] = this.value;

            modified |= (
                (state.contact[this.id] == null && this.value != "") ||
                (state.contact[this.id] != null && this.value != state.contact[this.id]))
        });

        modified && saveContact(newcontact);
    }

    function setGroup(group, target) {
        ajax(group.href, jsonparms("POST", state.contact),
            function (data, err) {
                if (err) return;
                state.contact = data;

                target.children("i").addClass("icon-ok").removeClass("icon-empty");
                target.data("badge").removeClass("hidden");

                //Remove the group
                state.contact.groups.some(function(contactgroup) {
                    if (contactgroup.href == group.href) {
                        target.data("contacthref", contactgroup["contact-href"]);
                    }
                });
            });
    }

    function unsetGroup(target) {
        ajax(target.data("contacthref"), { type : "DELETE", },
            function (data, err) {
                if (err) return;

                target.children("i").removeClass("icon-ok").addClass("icon-empty");
                target.data("badge").addClass("hidden");

                //Remove the group
                target.data("contacthref",null);
                state.contact.groups.filter(function(contactgroup) {
                    return (contactgroup.href != group.href);
                });
            });
    }

    function toggleGroup() {
        var target = $(this);

        if (state.contact.id) {
            if (target.data("contacthref")) unsetGroup(target);
            else setGroup(target.data("group"), target);
        }
    }

    function createContactGroupEntry(group, callback) {
        var efn = errfn(null, callback);
        getTemplate("contactgroup", efn, function(template_l, err) {
            getTemplate("badge", efn, function(template_b, err) {
                var data = { linkback: getState(), group: group };

                if (state.contact && state.contact.groups)
                    state.contact.groups.forEach(function (contactgroup) {
                        if (contactgroup.href == group.href) data.contacthref = contactgroup["contact-href"]
                    });

                data.visible = data.contacthref;

                var li = $(Mustache.to_html(template_l, data)),
                    badge = $(Mustache.to_html(template_b, data)),
                    a  = $(li).children("a").data("group", group).data("badge",badge).click(toggleGroup);

                $("#contactgroups .contactgroup:last").after(li);
                $("#contactgroupbadges").append(badge);

                if (callback) callback(a);
            });
        });
    }

    function findGroupTarget(group) {
        var result = null;
        $("#contactgroups .contactgroup:not(:first) a").each(function (key, item) {
            if ($(item).data("group").href == group.href) result = $(item);
        });
        return result;
    }

    function createGroup() {
        var group = { group: $("#groupname").val() };

        ajax("/groups", jsonparms("POST", group), function(group, err) {
            $("#creategroup").modal('hide');
            if (err) return showError("We could not create the group.")

            if (target = findGroupTarget(group)) {
                setGroup(group, target);
            }
            else {
                createContactGroupEntry(group, function(target, err) {
                    if (err) return;
                    setGroup(group, target);
                    updateGroups();
                });
            }
        });
    }

    function showCreateGroup() {
        var efn = errfn("We could not display the group creation page.")
        getTemplate("creategroup", efn, function(template, err) {
            $("#modals").html(Mustache.to_html(template, { linkback: getState() }));
            $("#creategroup").modal();
            $("#groupcreate").click(createGroup);
        });
    }

    function showContactPage(contact) {
        getTemplate("contact", $.noop, function(template, err) {
            //TODO: Add gravatar link
            var data = {
                contact: contact,
                backlink: state.group && state.group.href,
                };

            state.contact = contact;

            $("#content").html(Mustache.to_html(template, data));

            var grp_back = state.group;
            $("#back").click(function() { loadGroup(grp_back); });
            $("#remove").click(removeContact);
            $("#creategroupbtn").click(showCreateGroup);
            $("#content input").blur(saveContactFields);

            groups.groups.forEach(function (group) {
                createContactGroupEntry(group);
            });
        });
    }

    function loadContact(uri) {
        loadData(uri, errfn("We could not load the specified contact."), showContactPage);
    }

    $("#newcontact").click(function() { showContactPage({}); });
    updateGroups(restoreState);
});
