package com.excelsior.xds.ui.editor.modula.commands;

import java.util.ArrayList;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension;
import org.eclipse.ui.texteditor.ITextEditorExtension2;

import com.excelsior.xds.ui.commons.utils.WorkbenchUtils;
import com.excelsior.xds.ui.editor.commons.SourceCodeTextEditor;
import com.excelsior.xds.ui.editor.modula.IModulaPartitions;
import com.excelsior.xds.ui.editor.modula.ModulaEditor;

/**
 * A command handler to remove the block comment enclosing the selection.
 */
public class RemoveBlockCommentHandler extends AbstractHandler implements IHandler
{
    /**
     * {@inheritDoc}
     */
    @Override
    public Object execute(ExecutionEvent event) throws ExecutionException {
        try {
            ITextSelection selection = WorkbenchUtils.getActiveTextSelection();
            IDocument      doc       = WorkbenchUtils.getActiveDocument();
            IEditorInput   input     = WorkbenchUtils.getActiveInput();
            IEditorPart    editor    = WorkbenchUtils.getActiveEditor(false);

            boolean isTextOperationAllowed = (selection != null) && (doc != null) 
                    && (input != null)     && (editor != null) 
                    && (editor instanceof ModulaEditor);

            if (isTextOperationAllowed) {
                ITextEditor iTextEditor = (ITextEditor)editor;
                final ITextOperationTarget operationTarget = (ITextOperationTarget) editor.getAdapter(ITextOperationTarget.class);
                String commentPrefix = ((SourceCodeTextEditor)editor).getEOLCommentPrefix();
                isTextOperationAllowed = (operationTarget != null)
                        && (operationTarget instanceof TextViewer)
                        && (validateEditorInputState(iTextEditor))
                        && (commentPrefix != null);

                if ((isTextOperationAllowed)) {
                    int pos = selection.getOffset();

                    ITypedRegion partition = TextUtilities.getPartition(doc, IModulaPartitions.M2_PARTITIONING, pos, false);
                    if (!partition.getType().equals(IModulaPartitions.M2_CONTENT_TYPE_BLOCK_COMMENT)) {
                        if (pos > 1) {
                            // is cursor is exactly after "(* ... *)" ?
                                    partition = TextUtilities.getPartition(doc, IModulaPartitions.M2_PARTITIONING, pos-2, false);
                        }
                    }

                    if (!partition.getType().equals(IModulaPartitions.M2_CONTENT_TYPE_BLOCK_COMMENT)) {
                        // Search next (**) comment near cursor and uncomment it. Test 2 next lines.
                        partition = searchCommentStart(doc, pos, 0);
                        if (partition == null) {
                            partition = searchCommentStart(doc, pos, 1);
                        }
                    }

                    ArrayList<ReplaceEdit> edits = new ArrayList<ReplaceEdit>();
                    int offsAfter = 0;

                    if (partition != null) {
                        // Uncomment this partition
                        int offs = partition.getOffset();
                        int len = partition.getLength();
                        if (len >= 4 && // check it for diff case..
                                doc.get(offs, 2).equals("(*") &&
                                doc.get(offs+len-2, 2).equals("*)"))
                        {
                            // CRLF-s lengths when comment is <crlf> "(*" <CRLF> .... <CRLF> "*)" <crlf>
                            // in this case it will be removed with the comment:
                            int crlf1 = 0;
                            int crlf2 = 0;
                            {
                                int lin1 = doc.getLineOfOffset(offs);
                                int lin2 = doc.getLineOfOffset(offs + len - 1);
                                if (doc.getLineInformation(lin1).getLength() == 2 &&
                                    doc.getLineInformation(lin2).getLength() == 2) 
                                { // lines are exactly "(*" and "*)"
                                    String s = doc.getLineDelimiter(lin1);
                                    crlf1 = s != null ? s.length() : 0;
                                    
                                    if (lin2 > lin1 + 1) {
                                        s = doc.getLineDelimiter(lin2-1);
                                        crlf2 = s != null ? s.length() : 0;
                                    }
                                }
                            }
                            
                            edits.add(new ReplaceEdit(offs, 2 + crlf1, ""));
                            edits.add(new ReplaceEdit(offs+len-2 - crlf2, 2 + crlf2, ""));
                            offsAfter = offs + len - 4 - crlf1 - crlf2;

                            // if offsAfter is on the end of line - jump to beginning of the next line:
                            boolean eol = true;
                            int lnum = doc.getLineOfOffset(offs + len - 1);
                            IRegion reg = doc.getLineInformation(lnum);
                            String line = doc.get(reg.getOffset(), reg.getLength());
                            line = line.substring(offs+len - reg.getOffset()); // line tail after "*)"
                            for (char ch : line.toCharArray()) {
                                if (ch != ' ' && ch != '\t') {
                                    eol = false;
                                    break;
                                }
                                
                            }
                            if (eol) {
                                String crlf = doc.getLineDelimiter(lnum);
                                if (crlf != null) {
                                    offsAfter += line.length() + crlf.length();
                                }
                            }
                        }
                    }

                    if (!edits.isEmpty()) {
                        DocumentRewriteSession drws = null;
                        try {
                            if (doc instanceof IDocumentExtension4) {
                                drws = ((IDocumentExtension4)doc).startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED);
                            }
                            MultiTextEdit edit= new MultiTextEdit(0, doc.getLength());
                            edit.addChildren((TextEdit[]) edits.toArray(new TextEdit[edits.size()]));
                            edit.apply(doc, TextEdit.CREATE_UNDO);
                            iTextEditor.getSelectionProvider().setSelection(new TextSelection(offsAfter, 0));
                        }
                        finally {
                            if (doc instanceof IDocumentExtension4 && drws != null) {
                                ((IDocumentExtension4)doc).stopRewriteSession(drws);
                            }
                        }

                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
    
    protected boolean validateEditorInputState(ITextEditor editor) {
        if (editor instanceof ITextEditorExtension2)
            return ((ITextEditorExtension2) editor).validateEditorInputState();
        else if (editor instanceof ITextEditorExtension)
            return !((ITextEditorExtension) editor).isEditorInputReadOnly();
        else if (editor != null)
            return editor.isEditable();
        else
            return false;
    }

    private ITypedRegion searchCommentStart(IDocument doc, int pos, int addLines) throws BadLocationException {
        int lnum = doc.getLineOfOffset(pos);
        lnum += addLines;
        if (lnum < doc.getNumberOfLines()) {
            IRegion reg = doc.getLineInformation(lnum);
            String line = doc.get(reg.getOffset(), reg.getLength());
            for (int i = addLines > 0 ? 0 : pos - reg.getOffset(); i < line.length(); ++i) {
                if (line.charAt(i) == '(' && 
                    i+1 < line.length() && 
                    line.charAt(i+1) == '*')
                {
                    ITypedRegion partition = TextUtilities.getPartition(doc, IModulaPartitions.M2_PARTITIONING, 
                            reg.getOffset() + i, false);
                    if (partition.getType().equals(IModulaPartitions.M2_CONTENT_TYPE_BLOCK_COMMENT)) {
                        return partition;
                    }
                }
            }
        }
        return null;
    }

}