function circular(n) {
    var i = 0;
    return function() {
        i = n <= i ? 0 : i + 1;
        return i;
    };
}

var AnimatedMessageController = {
    start : function(msg) {
        this.view.clear();
        this.view.append(msg);
    },

    done : function() {
        this.view.clear();
    },

    view : {
        id : null, 

        append : function(msg) {
            var msg_query = $('<span id="message"></span>').appendTo('body');
            var n = circular(5);

            this.id = setInterval(function() {
                msg_query.text(msg + ".".x(n()));
            }, 100);
            return this.id;
        },

        clear : function() {
            if (this.id) {
                clearInterval(this.id);
                this.id = null;
            }
            $('span#message').remove();
        }
    }
};

var Sidebar = {
    names : [],

    init : function() {
        var self = this;
        $("div#index-body a.name").each(function() {
            self.names.push($(this).text());
        });
    },

    queryToArray : function(query) {
        return query.split(/[ 　]+/);
    },

    getQuery : function() {
        return $("div#search input[name=query]").val().trim();
    }


};
Sidebar.init();

var SidebarView = {
    toggleSidebar : function() {
        $("div#container").toggleClass("hide");
        $("div#hide-seek img:first-child").attr("src", function() {
            var src = $(this).attr("src");
            return src.match(/hide.gif$/) ? src.replace("hide.gif", "seek.gif") : src.replace("seek.gif", "hide.gif");
        });
    },

    toggleEntryListFolding : function() {
        var ul = $("ul#index-body");
        var img = $("img#folding");

        if (!ul.hasClass("unfolding")) {
            ul.addClass("unfolding");
            img.attr("src", img.attr("src").replace("folding", "unfolding"));
            return true;
        }
        else {
            ul.removeClass("unfolding");
            img.attr("src", img.attr("src").replace("unfolding", "folding"));
            return false;
        }
    },

    focusSearchQuery : function() {
        var self = $("div#search input[name=query]")[0];
        window.setTimeout(function() {
            self.selectionStart = 0;
            self.selectionEnd = self.value.length;
        }, 10);
    }
};

var SidebarController = {
    init : function() {
        $("div#hide-seek").live("click", function() { 
            SidebarView.toggleSidebar(); 
        });

        $("img#folding").live("click", function() {
            var on = SidebarView.toggleEntryListFolding();
            $.cookie("unfolding", on ? "on" : null, {path : "/", expires : 30});
        });

        $("div#search input[name=query]").live("keyup", (function(self) {
            return self.narrow.methodOf(self);
        })(this));

        $(this.onload.methodOf(this));
    },

    onload : function() {
        $("div#search input[name=query]").focus(SidebarView.focusSearchQuery);

        $("form#search-form").submit(this.enter.methodOf(this));

        setInterval(this.narrow.methodOf(this), 1000);
        
        if ($.cookie("unfolding")) {
            $("img#folding").click();
        }
    },

    // 絞り込み
    narrow : function() {
        var query = Sidebar.getQuery();
        this.previousQuery = this.currentQuery;
        this.currentQuery = query;

        var query_arr = query.split(/[　 ]+/);
        var li = $("ul#index-body li");
        
        function update() {
            var name = $("a.name", this).text().toLowerCase();
            var cond = (function() {
                for (var i = 0; i < query_arr.length; i++ ) {
                    if (!name.has(query_arr[i].toLowerCase())) return false;
                }
                return true;
            })();
            
            $(this)[cond ? "show" : "hide"]();
        }
        
        if (this.currentQuery !== this.previousQuery) {
            li.each(function() {
                $(this).removeClass("goto");
            });

            // 現在のキーワードがひとつ前のキーワードで始まっている場合
            // つまり以前の結果を利用できる場合
            if (this.currentQuery.start(this.previousQuery)) {
                li.filter(function() {
                    return $(this).css("display") != "none";
                }).each(update);
            }
            else {
                li.each(update);
            }
            
            // クエリから直接移動できる記事があったらgotoクラスをつける
            if (query !== "") {
                var candidates = li.filter(function() {
                    return $(this).css("display") !== "none";
                });
                if (candidates.length === 1) {
                    candidates.addClass("goto");
                }
                else {
                    candidates.filter(function() {
                        return query.toLowerCase() === $("a.name", this).text().toLowerCase();
                    }).addClass("goto");
                }
            }
        }
        
        
        $(this).data("query", query);
        $("div#index-footer a").attr("href", Nimpad.url + (!query ? "view?all" : "view?matched&query=" + encodeURIComponent(query)));
    },

    // 移動
    enter : function() {
        var query = Sidebar.getQuery();

        if (query === "") {
            return NimpadController.gotoHome();
        }

        // ヒットしている記事の名前の配列を得る
        var names = $("ul#index-body li").filter(function() {
            return $(this).css("display") !== "none";
        }).map(function() {
            return $("a.name", this).text();
        });

        if (names.length == 0) {
            return this.gotoPage($("form#search-form input[name=query]").val());
        }

        if (names.length == 1) {
            return this.gotoPage(names[0]);
        } 
        
        for (var i = 0; i < names.length; i++) if (query === names[i].toLowerCase()) {
            return this.gotoPage(names[i]);
        }

        location.href = this.matchedViewUrl(query);
        return false;
    },

    matchedViewUrl : function(query) {
        return Nimpad.url + "view?matched&query=" + encodeURIComponent(query);
    },

    gotoPage : function(name) {
        return NimpadController.gotoPage(name);
    },

    previosQuery : null,

    currentQuery : null
};
SidebarController.init();

var NimpadView = {

    changeCursorWait : function() {
        if (!$.browser.opera) $(document.body).css("cursor", "wait");
    },

    changeCursorAuto : function() {
        if (!$.browser.opera) $(document.body).css("cursor", "auto");
    },

    renewDate : function() {
        var self = this;
        $("div.entry").each(function() {
            if ($("form.entry-dict input[name=isnull]", this).val() != "true") {
                $("span.date", this).text(self.date($("form.entry-dict input[name=time]", this).val()));
            }
        });
    },

    date : function(time) {
        var d = new Date(time * 1000);

        var diff = Math.round(((new Date()).getTime() - d.getTime()) / 1000);
                       
        if (diff < 0) {
            return "0分前";
        }
        if (diff < 60 * 60) {
            return Math.round(diff / 60) + "分前 ";
        }
        if (diff < 60 * 60 * 24) {
            return Math.round(diff / 60 / 60) + "時間前 ";
        }
        if (diff < 60 * 60 * 24 * 5) {
            return Math.round(diff / 60 / 60 / 24) + "日前 ";
        }

        var str_date = d.getFullYear() + 
            "/" + pad(d.getMonth() + 1) + 
            "/" + pad(d.getDate()) + 
            " " + pad(d.getHours()) + 
            ":" + pad(d.getMinutes());

        return str_date; 
        
        function pad(n) {
            return ("" + n).length == 1 ? "0" + n : n;
        }
    },

    hideAsHomeLink : function() {
        $($("a#ashome-link")[0].parentNode).hide();
    },

    showNewpageForm : function() {
        $(this).hide();
        $("form", this.parentNode).addClass("show");
        $("input[type=text]", this.parentNode).focus();
        return false;
    }
}

var Nimpad = {
    init : function() {
        $(this.onload.methodOf(this));
    },

    encodeName :function(name) {
        return encodeURIComponent(name).replace(/%2F/gi, "/");
    },

    setAsHome : function(fn) {
        $.post(
            this.url + "admin/ashome", 
            {home : this.currentQuery, token : this.token}, 
            fn, 
            "json");
    },

    entryDict : {},

    onload : function() {
        function p(name) {
            return $("form#param input[name=" + name + "]").val();
        }

        this.token = p("token");
        this.url = p("wikiurl");
        this.name = p("wikiname");
        this.currentQuery = p("query");

        $(".entry").each(function() {
            var self = this;
            function p(name) {
                return $("form.entry-dict input[name=" + name + "]", self).val();
            }
            var name = p("name");
            Nimpad.entryDict[name] = 
                new Entry(Nimpad, name, $("textarea", this).val(), p("hash"));
        });
    }
}; 
Nimpad.init();

var NimpadController = {
    init : function() {
        $("a#ashome-link").live("click", function() { 
            NimpadController.setAsHome();
            return false;
        });

        setInterval(function() { NimpadView.renewDate(); }, 1000 * 15);

        $(this.onload.methodOf(this));
    },

    onload : function() {
        prettyPrint();
        $("form#newpage-form").submit(this.gotoNewpage.methodOf(this));
        $("a#newpage-link").click(NimpadView.showNewpageForm);
        this.entries = EntryController.createEntries();
        
        if (this.entries.length == 1 && this.entries[0].isnull) (function(entry) {
            setTimeout(function() { 
                entry.view.focusTextarea();
            });
        })(this.entries[0]);
        else (function(search) {
            search.selectionStart = 0;
            search.selectionEnd = search.value.length;
            search.focus();
        })($("div#search input[name=query]")[0]);
    }, 

    entries : [],

    setAsHome : function() {
        NimpadView.changeCursorWait();
        NimpadView.hideAsHomeLink();
        Nimpad.setAsHome(function(data, status) {
            if (status !== "success") {
                alert("fail");
            }
            NimpadView.changeCursorAuto();
        });
    },

    gotoNewpage : function() {
        var name = $("form#newpage-form input[type=text]").val();
        name = name.trim().replace(/ /g, "_");
        return this.gotoPage(name);
    },

    gotoHome : function() {
        location.href = Nimpad.url;
        return false;
    },

    gotoPage : function(name) {
        location.href = Nimpad.url + Nimpad.encodeName(name);
        return false;
    },

    updateEntries : function(name, text, html, time) {
        this.entries.filter(function(elt) {
            return name === elt.name;
        }).forEach(function(elt) {
            elt.update(text, html, time);
        });
    }
};
NimpadController.init();


var Entry = function(nimpad, name, text, hash) {
    this.nimpad = nimpad;
    this.name = name;
    this.text = text;
    this.hash = hash;
    this.url = nimpad.url + encodeURIComponent(name).replace(/%2f/ig, "/");
};

Entry.prototype = {

    destroy : function(fn) {
        $.del(
            this.url + "?token=" + this.nimpad.token, 
            {}, 
            function(data, status) {
                fn(data, status);
        });
    },

    save : function(text, doctype, fn) {

        var data = {
            token : this.nimpad.token, 
            text : text, 
            doctype : doctype, 
            action : "save"};

        var self = this;

        if (this.hash) data.hash = this.hash;
        
        $.put(
            this.url, 
            data, 
            function(data, status) {
                if (data.success) {
                    self.text = text;
                    self.hash = data.hash;
                }
                fn(data, status);
            }, function() { 
                fn({}, "fail"); 
            });
    },

    unsaving : function(text) {
        return this.text !== text;
    }

}


var EntryView = function(dom, height) {
    this.dom = dom;
    this.initTextarea(height);
    this.initStatus();
    this.initForm();
};

EntryView.prototype = {
    initTextarea : function(height) {
        if (height) {
            $("textarea", this.dom).css("height", height);
        }
        else {
            $("textarea", this.dom).attr("rows", 25);
        }
    },

    initStatus : function() {
        $("form.entry-edit-form input[type=submit]", this.dom)
            .after(' <span class="status"></span>');
    },

    initForm : function() {
        $("input[type=submit]", this.dom).attr("disabled", "");
    },

    toggleKeybindHelp : function() {
        $("div#keybind-help").toggle();
    },

    focusTextarea : function() {
        var textarea = $("textarea", this.dom)[0];
        textarea.blur();
        textarea.focus();
    },

    hideDeleteEntryLink : function() {
        $("span.delete-entry", this.dom).hide();
    },

    showDeleteEntryLink : function() {
        $("span.delete-entry", this.dom).show();
    },

    showEditForm : function() {
        $(".view", this.dom).hide();
        $(".edit", this.dom).show();
        this.focusTextarea();
    },

    hideEditForm : function() {
        $(".edit", this.dom).hide();
        $(".view", this.dom).show();
    },

    disableEditForm : function() {
        $("textarea, input", this.dom).attr("disabled", "on");
    },

    enableEditForm : function() {
        $("textarea, input", this.dom).attr("disabled", "");
    },

    changeStatus : function(text) {
        $("span.status", this.dom).text(text);
    },

    update : function(text, html) {
        $("div.text-rendering", this.dom).html(html);
        $("textarea", this.dom).val(text);
        $("span.date", this.dom).show();
        $("span.delete-entry", this.dom).show();
    }
};

var EntryController = function(dom) {
    this.dom = dom;
    this.name = param("name");
    this.isnull = param("isnull") === "true";
    this.view = new EntryView(dom, $.cookie(Nimpad.name + "/" + this.name));
    this.model = Nimpad.entryDict[this.name];

    var self = this;

    // キーバインドヘルプ
    $(".entry-edit-form-footer", dom).click(function() {
        self.view.toggleKeybindHelp();
        self.view.focusTextarea();
        return false;
    });

    // 記事の削除リンク
    $("a.delete-entry-link", dom).click(function() {
        self.destroy();
        return false;
    });

    $(".view .menu", dom).click(function() {
        self.view.showEditForm();
        return false;
    });

    $(".edit .menu", dom).click(function() {
        self.view.hideEditForm();
        return false;
    });

    // 保存
    $("form.entry-edit-form", dom).submit(function() {
        self.save();
        return false;
    });

    var textarea = $("textarea", dom)[0];
    var editor = new TextAreaEditor(textarea);
    editor['C-s'] = function() {
        self.save();
        return false;
    }
    $(textarea).bind($.browser.opera ? "keypress" : "keydown", function(e) {
        editor.dispatchKeyEvent(e);
    });

    $(textarea).TextAreaResizer();

    setInterval(function() {
        self.updateUnsaved();
    }, 1000);
    
    function param(name) {
        return $("input[name=" + name + "]", dom).val();
    }
};

EntryController.createEntries = function() {
    var controllers = [];
    $(".entry").each(function() {
        controllers.push(new EntryController(this));
    });
    return controllers;
};

EntryController.prototype = {
    name : null,
    isnull : null,
    entryies : null, 
    model : null,
    view: null,

    save : function() {
        var self = this;
        var t = this.startSaveTransaction();

        this.model.save(t.text, t.doctype, function(data, status) {
            t.done(data, status);
        });

    },

    startSaveTransaction : function() {
        NimpadView.changeCursorWait();
        this.view.disableEditForm();
        var before = (new Date()).getTime();
        var textarea = $("textarea", this.dom)[0];

        var transaction = {
            circular : circular(5),

            name : this.name,

            scrollTop : textarea.scrollTop,

            text : $(textarea).val(),

            doctype : $("select[name=doctype]", this.dom).val(),

            view : this.view,

            time : function() {
                return (new Date()).getTime() - before;
            },

            id : setInterval(function() {
                transaction.view.changeStatus(".".x(transaction.circular()));
            }, 100),

            done : function(data, status) {
                if (status == "success") {
                    data.success 
                        ? NimpadController.updateEntries(
                            this.name, this.text, data.result, data.time)
                        : alert(data.result);
                }
                else {
                    alert("何らかの原因で保存に失敗しました");
                }

                clearInterval(this.id);
                this.view.changeStatus("(" + (this.time() / 1000) + "秒)");
                NimpadView.changeCursorAuto();
                NimpadView.renewDate();
                this.view.enableEditForm();
                this.view.focusTextarea();
                textarea.scrollTop = this.scrollTop;
                prettyPrint();
            }
        };

        $.cookie(
            Nimpad.name + "/" + this.name, 
            $(textarea).css("height"), 
            {path : "/", expires : 30});

        return transaction;
    },

    destroy : function() {
        if (!confirm("記事を削除しますか？削除に成功した場合画面は更新されます")) {
            return false;
        }

        var view = this.view;
        view.hideDeleteEntryLink();
        NimpadView.changeCursorWait();
        this.model.destroy(function(data, status) {
            if (data.success && status == "success") {
                location.reload(true);
            }
            else {
                alert("なんらかの原因で失敗しました");
                view.showDeleteEntryLink();
            }
        });
        NimpadView.changeCursorAuto();
        return false;
    },

    updateUnsaved : function() {
        var text = $("textarea", this.dom).val();
        var submit = $("input[type=submit]", this.dom);
        
        submit.val((this.model.unsaving(text) ? "*" : "") + "保存");
    },

    update : function(text, html, time) {
        this.view.update(text, html);
        $("input[name=time]", this.dom).val(time);
        $("input[name=isnull]", this.dom).val("false");
    }
};


//
// キャレット位置計測用のテキストエリアと押しても何も起きないボタンを挿入する
//
$(function() {
    $("body").append('<textarea id="measure" disabled="disabled"></textarea><button id="fake">fake</button>');
});

