var LINEHEIGHT = 13;

(function($) {
    $.InlineEdit = {
        clickToEdit: 'doubleclick to edit',
        internalError: 'internal error',
        inlinePanel: '<div class="inlinePanel" style="position: absolute; min-width: 200px">' +
                     '<p><input type="button" class="inlineSubmit"/><input class="inlineCancel" type="button"/></div>',
        submitButton: 'Submit',
        cancelButton: 'Cancel',
        okButton: 'OK',
        multi: false,
        getForm: function() {
            return $(this.target).parents("form:first");
        },

        init: function() {
            var $$ = $(this.target);
            var e;
            if ($$.attr('type') == 'text') {
                e = $('<span/>');
            } else if (this.target.tagName.toLowerCase() == 'textarea') {
                e = $('<div/>');
            }
            if (e) {
                e.attr('class', $$.attr('class'));
                e.text($$.val());
                $$.hide();
                e.insertAfter($$);
                this.targetInput = this.target;
                this.target = e.get(0);
                $$ = e;
            }
            $$.data('inlineEdit', this);
            var val = this.getValue($$);
            if (val == '') this.setValue($$, '');
            $$.attr('title', this.clickToEdit);
            if ($$.hasClass('multi')) this.multi = true;
            $$.bind('dblclick', function() { $(this).data('inlineEdit').edit(); });
        },

        edit: function() {
            var $$ = $(this.target);
            var panel = $(this.inlinePanel);
            var input;
            if (this.multi) {
                input = $('<textarea/>');
                input.prependTo(panel);
            } else {
                input = $('<input type="text"/>');
                input.wrap('<p class="inputfield"/>').prependTo(panel);
            }
            input.attr('class', $$.attr('class'));
            input.addClass(this.multi ? 'editarea' : 'field');
            this.old = this.getValue($(this.target));
            input.val(this.old);
            this.input = input.get(0);
            if (this.multi && $.isFunction(input.autogrow)) {
                input.autogrow();
            }

            this.panel = panel.get(0);

            var form = this.getForm();
            var old = form.data('inlineEdit');
            if (old) old.cancel();
            form.data('inlineEdit', this);
            form.bind('keydown', this.keyHandler);
            $$.removeClass('edit');
            $$.addClass('editing');
            panel.appendTo(form);
            this.pos();
            input.focus();

            var me = this;
            this.posInterval = window.setInterval(function() { me.pos(); }, 200);
            panel.find('.inlineCancel').click(function() { me.cancel(); }).val(this.cancelButton);
            panel.find('.inlineSubmit').click(function() { me.submit(); }).val(this.targetInput ? this.okButton : this.submitButton);
        },

        keyHandler: function(e) {
            var me = $(this).data('inlineEdit');
            if (!me) return;
            if (e.which == 27) {
                me.cancel();
            } else if (!me.multi && e.which == 13) {
                me.submit();
            } else {
                return;
            }
            e.preventDefault();
        },

        setValue: function($$, value) {
            $$.text(value == '' ? this.clickToEdit : value);
        },

        getValue: function($$) {
            var val = $$.text();
            if (val == this.clickToEdit) {
                $$.text('');
                return '';
            }
            return val;
        },

        submit: function() {
            if (!this.input) return;
            var value = $(this.input).val();
            if (this.targetInput) {
                $(this.targetInput).val(value);
            }
            if (this.targetInput || this.old == value) {
                this.setValue($(this.target), value);
                this.close();
                return;
            }
            var form = this.getForm();
            var data = form.find('input[type=hidden]').map(function(i, elem){
                var $$ = $(elem);
                return {name: $$.attr('name'), value: $$.val()};
            }).get();
            data.push({name: $(this.target).attr('id'), value: value});
            var panel = $(this.panel);
            panel.find('.error').remove();
            this.wait(true);

            $.ajax({
                inlineEdit: this,
                url: form.attr('action'),
                type: 'POST',
                dataType: 'text',
                global: false,
                data: data,
                success: function(text) {
                    this.inlineEdit.setValue($(this.inlineEdit.target), text);
                    this.inlineEdit.close();
                },
                error: function(req) {
                    var message = req.getResponseHeader('X-Message-URLEncoded');
                    if (message) {
                        message = decodeURIComponent(message.replace('+', ' ', 'gi'));
                    } else {
                        message = this.inlineEdit.internalError;
                    }
                    var panel = $(this.inlineEdit.panel);
                    var err = $('<div class="error"/>');
                    err.text(message);
                    err.prependTo(panel);
                    this.inlineEdit.wait(false);
                }
            });
        },

        wait: function(onoff) {
            if (!this.panel) return;
            var panel = $(this.panel);
            var x = panel.find('.inlineCancel, .inlineSubmit').add(this.input);
            if (onoff) {
                panel.css('cursor', 'wait');
                x.attr('disabled', 'disabled');
            } else {
                panel.css('cursor', 'inherit');
                x.removeAttr('disabled');
            }

        },

        cancel: function() {
            this.setValue($(this.target), this.old);
            this.close();
        },

        close: function() {
            if (this.panel) {
                $(this.panel).remove();
                this.panel = undefined;
            }
            this.input = undefined;
            this.old = undefined;

            var $$ = $(this.target);
            $$.removeClass('editing');
            $$.addClass('edit');

            var form = this.getForm();
            form.unbind('keydown', this.keyHandler);
            if (form.data('inlineEdit') == this) {
                form.removeData('inlineEdit');
            }

            if (this.posInterval) {
                window.clearInterval(this.posInterval);
                this.posInterval = undefined;
            }
        },

        pos: function() {
            if (!this.panel) return;

            var t = this.getPos($(this.target));
            var p = this.getPos($(this.panel));

            var top = t.offset.top + t.height - (p.offset.top - p.position.top);
            var left = t.offset.left - (p.offset.left - p.position.left);
            var width = t.innerWidth - (p.innerWidth - p.width);

            // TODO scroll-visibility

            $(this.panel).css({
                'top'   : top + 'px',
                'left'  : left + 'px',
                'width' : width +'px'
            });
        },

        getPos: function(e) {
            return {position: e.position(), offset: e.offset(), scroll: {left: e.scrollLeft(), top: e.scrollTop()},
                width: e.width(), innerWidth: e.innerWidth(),
                height: e.height(), innerHeight: e.innerHeight()
            };
        }
    };
    $.fn.makeEditable = function(options) {
        return this.each(function(){
            $.extend({target: this}, $.InlineEdit, options).init();
        });
    };
})(jQuery);
