function ExtractXmlDocumentFromFrame(frame) 
{
    var doc = frame.contentWindow ?
        frame.contentWindow.document :
        frame.contentDocument ?
            frame.contentDocument :
            frame.document;

    if (doc == null)
        return null;
    if (doc.XMLDocument)
        return $(doc.XMLDocument);
    else
        return $(doc);
}

$.fn.sm_show_grid_error = function (a_options) {
    var _this = this;
    var grid;

    var defaults =
        {
            errorMessageHeader: 'Errors:',
            errorMessage: 'Some error message',
            columnSpan: 0,
            useBlockGUI: false
        };
    var options = $.extend(defaults, a_options);

    function ConstructErrorRow() {
        var row = $('<tr></tr>');

        var cell = $('<td></td>');
        cell.attr('colspan', options.columnSpan);
        cell.addClass('odd');
        cell.addClass('grid_error_row');

        var errorPanel = $('<div>');
        errorPanel.addClass('grid_error_message');
        errorPanel.append('<strong>' + options.errorMessageHeader + '</strong><br><br>');
        errorPanel.append(options.errorMessage);

        var closeButton = $('<div>');
        closeButton.addClass('grid_error_message_close_button');
        closeButton
            .sm_hoverclass('grid_error_message_close_button_hover')
            .click(RemoveErrorPanelHandler);

        errorPanel.prepend(closeButton);
        row.append(cell);
        cell.append(errorPanel);
        return row;
    }

    function RemoveErrorPanelHandler() {
        var errorRow = $(this).closest('tr');
        RemoveErrorPanel(errorRow);
    }

    function RemoveErrorPanel(errorRow) {
        var errorPanel = errorRow.find('div.grid_error_message');
        errorPanel.fadeOut(
            '400',
            function () {
                errorPanel.remove();
                errorRow.remove();
            }
        );
    }

    function construct() {
        grid = $(_this);

        grid.each(function () {
            $(this).find('tr#grid_header').before(ConstructErrorRow());
        });
        return _this;
    }

    return construct();
}

$.fn.sm_inline_grid_edit = function (a_options) {
    var _this = this;

    var defaults =
        {
            debug: false,
            editControlsContainer: 'td[data-column-name=InlineEdit]',
            initEditControl: 'a.inline_edit_init',
            cancelEditControl: 'a.inline_edit_cancel',
            commitEditControl: 'a.inline_edit_commit',
            cancelInsertControl: 'a.inline_insert_cancel',
            commitInsertControl: 'a.inline_insert_commit',
            requestAddress: '',
            useBlockGUI: false,
            inlineAddControl: '.inline_add_button',
            cancelButtonHint: 'Cancel',
            commitButtonHint: 'Commit',
            useImagesForActions: true,
            newRecordRowTemplate: '.new-record-row[data-new-row=false]',
            newRecordAfterRowTemplate: '.new-record-after-row[data-new-row=false]',
            responseCheckCount: 200,
            editingErrorMessageHeader: 'Errors:'
        };
    var options = $.extend(defaults, a_options);

    var grid;

    function SetupEditControls(editControlsContainer) {
        var initEditControl = editControlsContainer.find(options.initEditControl);
        initEditControl.click(ShowInlineEditors);

        var cancelControl = editControlsContainer.find(options.cancelEditControl);
        cancelControl.click(CancelEditingClickHandler)

        var commitControl = editControlsContainer.find(options.commitEditControl);
        commitControl.click(CommitClickHandler);

        HideCompleteEditControls(editControlsContainer);
    }

    function HideInitEditControls(editControlsContainer) {
        editControlsContainer.find(options.initEditControl).hide();
    }

    function ShowInitEditControls(editControlsContainer) {
        editControlsContainer.find(options.initEditControl).show();
    }

    function HideCompleteEditControls(editControlsContainer) {
        editControlsContainer.find(options.cancelEditControl).hide();
        editControlsContainer.find(options.commitEditControl).hide();
    }

    function ShowCompleteEditControls(editControlsContainer) {
        editControlsContainer.find(options.cancelEditControl).show();
        editControlsContainer.find(options.commitEditControl).show();
    }

    function CancelEditingClickHandler() {
        var cancelControl = $(this);
        var row = cancelControl.closest('tr');
        var editControlsContainer = cancelControl.closest(options.editControlsContainer);
        CancelInlineEditing(row, editControlsContainer);
    }

    function CommitClickHandler() {
        var commitControl = $(this);
        var row = commitControl.closest('tr');
        var editControlsContainer = commitControl.closest(options.editControlsContainer);
        CommitInlineEditing_IFrame(row, editControlsContainer);
    }

    function CompleteEditing(editControlsContainer) {
        ShowInitEditControls(editControlsContainer);
        HideCompleteEditControls(editControlsContainer);
    }

    function ReturnOldHtmlForCell(dataCell) {
        var oldHtml = dataCell.children('div.phpgen-ui-inline-edit-cell-old-data').html();
        dataCell.html(oldHtml);
    }

    function EmbedEditorFromXml(row, editorXmlElement) {
        var editorHtml = editorXmlElement.find('html').text();
        var editorScript = editorXmlElement.find('script').text();
        var editorFieldName = editorXmlElement.attr('name');

        var dataCell = row.find('td[data-column-name=' + editorFieldName + ']');
        dataCell.attr('data-inline-editing', 'true');

        var inlineEditorContainer = $('<div>');
        inlineEditorContainer.addClass('inline_editor_container');
        inlineEditorContainer.append(editorHtml);

        var oldHtmlContainer = $('<div>');
        oldHtmlContainer.addClass('phpgen-ui-inline-edit-cell-old-data');
        oldHtmlContainer.css('display', 'none');
        oldHtmlContainer.append(dataCell.html());

        dataCell.html('');
        dataCell.append(inlineEditorContainer);
        dataCell.append(oldHtmlContainer);

        try {
            eval(editorScript);
        }
        catch (e)
        { }
    }

    function CancelInlineEditing(currentRow, editControlsContainer) {
        currentRow.children('td[data-inline-editing=true]').each(function () {
            var dataCell = $(this);
            ReturnOldHtmlForCell(dataCell);
        });
        CompleteEditing(editControlsContainer);
    }

    function BlockInterface() {

        if (!options.debug && options.useBlockGUI) {
            $.blockUI(
            {
                message: '<span class="wait_message">Please wait...</span>',
                overlayCSS:
                {
                    backgroundColor: '#fff'
                },
                css:
                {
                    border: 'none',
                    padding: '15px',
                    backgroundColor: '#aaa',
                    '-webkit-border-radius': '10px',
                    '-moz-border-radius': '10px',
                    opacity: .5,
                    color: '#000'
                }
            });
        }
    }

    function UnblockInterface() {
        if (!options.debug && options.useBlockGUI) {
            $.unblockUI();
        }
    }

    function CreateFormForPostInlineEditors() {
        var postForm = $('<form>');
        postForm.attr('id', 'inline_edit_form');
        postForm.attr('enctype', 'multipart/form-data');
        postForm.attr('action', options.requestAddress);
        postForm.attr('method', 'POST');
        postForm.css('display', 'none');

        postForm.append('<input type="hidden" name="operation" value="arqiec" />');
        return postForm;
    }

    function MoveRowInlineEditorsToForm(row, targetForm) {
        targetForm.append(row.find('td[data-inline-editing=true] div.inline_editor_container'));
    }

    function SubmitFormWithTarget(form, target) {
        form.submit(function () {
            form.attr('target', target);
        });
        form.submit();
    }

    function CommitInlineEditing_IFrame(row, editControlsContainer) {
        var currentRow = row;
        var postForm = CreateFormForPostInlineEditors();
        postForm.append(editControlsContainer.find('input:hidden').clone());
        MoveRowInlineEditorsToForm(currentRow, postForm);

        var operationRandom = Math.floor(Math.random() * 100000);

        var resultFrame = $('<iframe name="inlineEditPostForm" id="inlineEditPostForm_' + operationRandom + '"></iframe>');
        resultFrame.attr('src', 'about:black');
        if (options.debug) {
            resultFrame.css('width', '1000px');
            resultFrame.css('height', '800px');
            resultFrame.css('border', '2px');
        }
        else {
            resultFrame.css('width', '0px');
            resultFrame.css('height', '0px');
            resultFrame.css('border', '0px');
        }
        resultFrame.css('padding', '0px');
        resultFrame.css('margin', '0px');

        $('body').append(postForm);
        $('body').append(resultFrame);

        BlockInterface();


        domCheckCount = options.responseCheckCount;
        function ProcessResponce() {

            io = resultFrame[0];
            try {
                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document
                var isXml = doc.XMLDocument || $.isXMLDoc(doc);
            }
            catch (e) {
                isXml = false;
            }

            if (!isXml) {
                if (--domCheckCount) {
                    setTimeout(ProcessResponce, 250);
                    return;
                }

                if (!options.debug)
                    setInterval(function () { $('#inlineEditPostForm_' + operationRandom).remove(); }, 1000);
                $('#inline_edit_form').remove();

                currentRow.children('td[data-inline-editing=true]').each(function () {
                    var dataCell = $(this);
                    ReturnOldHtmlForCell(dataCell);
                });
                CompleteEditing(editControlsContainer);

                grid.sm_show_grid_error(
                    {
                        errorMessage: 'Response is not available',
                        columnSpan: currentRow.children('td').length,
                        errorMessageHeader: options.editingErrorMessageHeader
                    });

                UnblockInterface();
                return;
            }

            responseXml = ExtractXmlDocumentFromFrame(resultFrame[0]);

            if (responseXml.find('errormessage').length > 0) {
                grid.sm_show_grid_error(
                    {
                        errorMessage: responseXml.find('errormessage').text(),
                        columnSpan: currentRow.children('td').length,
                        errorMessageHeader: options.editingErrorMessageHeader
                    });

                responseXml.find('editor').each(function () {
                    var dataCell = currentRow.find('td[data-column-name=' + $(this).attr('name') + ']');
                    ReturnOldHtmlForCell(dataCell);
                    EmbedEditorFromXml(currentRow, $(this));
                });
            }
            else {
                CompleteEditing(editControlsContainer);

                responseXml.find('fieldvalue').each(function () {

                    var newValue = $(this).text();

                    var dataCell = currentRow.children('td[data-column-name=' + $(this).attr('name') + ']');
                    dataCell.attr('data-inline-editing', 'false');

                    dataCell.html('');
                    dataCell.append(newValue);
                });
            }
            UnblockInterface();

            if (!options.debug)
                setInterval(function () { $('#inlineEditPostForm_' + operationRandom).remove(); }, 1000);
            $('#inline_edit_form').remove();
        }

        setTimeout(function () { SubmitFormWithTarget(postForm, 'inlineEditPostForm'); }, 100);
        setTimeout(ProcessResponce, 250);
    }

    function GetEditorsNameSuffix(responseXml) {
        return $(responseXml).find('namesuffix').text();
    }

    function CreateInlineInsertingControls(editControlsContainer) {
        if (options.useImagesForActions) {
            editControlsContainer.append(
            '<span style="white-space: nowrap;" data-content="inline_insert_controls">' +
            '<a href="#" class="inline_insert_cancel" title="' + options.cancelButtonHint + '"><img src="images/cancel.png" alt="' + options.cancelButtonHint + '"></a>' +
            '<a href="#" class="inline_insert_commit" title="' + options.commitButtonHint + '"><img src="images/ok.png" alt="' + options.commitButtonHint + '"></a>' +
            '</span>');
        }
        else {
            editControlsContainer.append(
            '<span style="white-space: nowrap;" data-content="inline_insert_controls">' +
            '<a href="#" style="margin-right: 5px;" class="inline_insert_cancel" title="' + options.cancelButtonHint + '">' + options.cancelButtonHint + '</a>' +
            '<a href="#" class="inline_insert_commit" title="' + options.commitButtonHint + '">' + options.commitButtonHint + '</a>' +
            '</span>');
        }
        editControlsContainer.find(options.cancelInsertControl).click(CancelInsertHandler);
        editControlsContainer.find(options.commitInsertControl).click(CommitInsertHandler);
    }

    function DeleteInlineInsertingControls(editControlsContainer) {
        var inlineInsertControls = editControlsContainer.find('span[data-content=inline_insert_controls]');
        inlineInsertControls.remove();
    }

    function CancelInsertHandler() {
        var row = $(this).closest('tr');
        row.next().remove();
        row.remove();
    }

    function CommitInsertHandler() {
        var row = $(this).closest('tr');
        var editControlsContainer = row.find(options.editControlsContainer);
        CommitInlineInserting(row, editControlsContainer);
    }

    function CreateFormForPostInsertInlineEditors() {
        var postForm = $('<form>');
        postForm.attr('id', 'inline_edit_form');
        postForm.attr('enctype', 'multipart/form-data');
        postForm.attr('action', options.requestAddress);
        postForm.attr('method', 'POST');
        postForm.css('display', 'none');

        postForm.append('<input type="hidden" name="operation" value="arqiic" />');
        return postForm;
    }

    function CommitInlineInserting(row, editControlsContainer) {

        var currentRow = row;
        var postForm = CreateFormForPostInsertInlineEditors();
        postForm.append(editControlsContainer.find('input[type=hidden]').clone());
        MoveRowInlineEditorsToForm(currentRow, postForm);

        var operationRandom = Math.floor(Math.random() * 100000);

        var resultFrame = $('<iframe name="inlineEditPostForm" id="inlineEditPostForm_' + operationRandom + '"></iframe>');
        resultFrame.attr('src', 'about:black');
        if (options.debug) {
            resultFrame.css('width', '1000px');
            resultFrame.css('height', '800px');
            resultFrame.css('border', '2px');
        }
        else {
            resultFrame.css('width', '0px');
            resultFrame.css('height', '0px');
            resultFrame.css('border', '0px');
        }
        resultFrame.css('padding', '0px');
        resultFrame.css('margin', '0px');

        $('body').append(postForm);
        $('body').append(resultFrame);

        BlockInterface();

        domCheckCount = options.responseCheckCount;
        function ProcessResponse() {

            io = resultFrame[0];
            try {
                doc = io.contentWindow ? io.contentWindow.document : io.contentDocument ? io.contentDocument : io.document
                var isXml = doc.XMLDocument || $.isXMLDoc(doc);
            }
            catch (e) {
                isXml = false;
            }

            if (!isXml) {
                if (--domCheckCount) {
                    setTimeout(ProcessResponse, 250);
                    return;
                }

                if (!options.debug)
                    setInterval(function () { $('#inlineEditPostForm_' + operationRandom).remove(); }, 1000);
                $('#inline_edit_form').remove();

                currentRow.next().remove();
                currentRow.remove();

                grid.sm_show_grid_error(
                    {
                        errorMessage: 'Response is not available',
                        columnSpan: currentRow.children('td').length,
                        errorMessageHeader: options.editingErrorMessageHeader
                    });

                UnblockInterface();
                return;
            }


            var responseXml = ExtractXmlDocumentFromFrame(resultFrame[0]);

            if (responseXml.find('errormessage').length > 0) {
                grid.sm_show_grid_error(
                    {
                        errorMessage: responseXml.find('errormessage').text(),
                        columnSpan: currentRow.children('td').length,
                        errorMessageHeader: options.editingErrorMessageHeader
                    });

                responseXml.find('editor').each(function () {
                    var dataCell = currentRow.find('td[data-column-name=' + $(this).attr('name') + ']');
                    ReturnOldHtmlForCell(dataCell);
                    EmbedEditorFromXml(currentRow, $(this));
                });
            }
            else {
                grid.find('.emplygrid').remove();
                DeleteInlineInsertingControls(editControlsContainer);

                responseXml.find('fieldvalue').each(function () {
                    var newValue = $(this).find('value').text();
                    var afterRow = $(this).find('afterrowcontrol').text();
                    var dataCell = currentRow.children('td[data-column-name=' + $(this).attr('name') + ']');
                    dataCell.attr('data-inline-editing', 'false');

                    dataCell.html('');
                    dataCell.append(newValue);
                    currentRow.next().find('td').append(afterRow);
                });

                SetupEditControls(editControlsContainer);
            }

            UnblockInterface();

            if (!options.debug)
                setInterval(function () { $('#inlineEditPostForm_' + operationRandom).remove(); }, 1000);
            $('#inline_edit_form').remove();
        }

        setTimeout(function () { SubmitFormWithTarget(postForm, 'inlineEditPostForm'); }, 100);
        setTimeout(ProcessResponse, 250);
    }

    function InlineAddHandler() {
        var templateRow = grid.find(options.newRecordRowTemplate);
        var templateAfterRow = grid.find(options.newRecordAfterRowTemplate);

        var row = templateRow.clone();
        var afterRow = templateAfterRow.clone();

        row.attr('data-new-row', 'true');
        afterRow.attr('data-new-row', 'true');

        templateRow.before(row);
        row.after(afterRow);

        var editControlsContainer = row.find(options.editControlsContainer);

        var requestData = {};
        requestData['operation'] = 'arqii';

        BlockInterface();

        $.get(
            options.requestAddress,
            requestData,
            function ready(data) {
                row.css('display', '');
                afterRow.css('display', '');
                CreateInlineInsertingControls(editControlsContainer);

                // HideInitEditControls(editControlsContainer);
                // ShowCompleteEditControls(editControlsContainer);

                var nameSuffixInput = $('<input name="namesuffix" type="hidden">');
                nameSuffixInput.val(GetEditorsNameSuffix(data));

                editControlsContainer.append(nameSuffixInput);

                $(data).find('editor').each(function () {
                    EmbedEditorFromXml(row, $(this));
                });
                UnblockInterface();
            }
        );
    }

    function ShowInlineEditors() {
        var row = $(this).closest('tr');
        var editControlsContainer = $(this).closest(options.editControlsContainer);

        BlockInterface();

        var requestData = {};
        editControlsContainer.find('input[type=hidden]').each(function () {
            requestData[$(this).attr('name')] = $(this).val();
        });
        requestData['operation'] = 'arqie';

        $.get(
            options.requestAddress,
            requestData,
            function ready(data) {
                HideInitEditControls(editControlsContainer);
                ShowCompleteEditControls(editControlsContainer);

                var nameSuffixInput = $('<input name="namesuffix" type="hidden">');
                nameSuffixInput.val(GetEditorsNameSuffix(data));
                editControlsContainer.append(nameSuffixInput);

                $(data).find('editor').each(function () {
                    EmbedEditorFromXml(row, $(this));
                });
                UnblockInterface();
            }
        );
    }

    function construct() {
        grid = $(_this);

        var inlineAddButton = grid.find(options.inlineAddControl);
        inlineAddButton.click(InlineAddHandler);

        editControlsContainers = grid.find('tr').children(options.editControlsContainer);
        editControlsContainers.each(function () {
            SetupEditControls($(this));
        });
        //
        return _this;
    }

    return construct();
}

