/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc and Fabio Zadrozny. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
/*
 * @author: Fabio Zadrozny
 * Created: July 2004
 */
package org.python.pydev.shared_core.auto_edit;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.python.pydev.shared_core.log.Log;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.string.TextSelectionUtils;
import org.python.pydev.shared_core.structure.Tuple;

/**
 * @author Fabio Zadrozny
 * 
 * Makes a backspace happen...
 * 
 * We can:
 * - go to the indentation from some uncommented previous line (if we
 *   only have whitespaces in the current line).
 * - erase all whitespace characters until we find some character.
 * - erase a single character.
 */
public class AutoEditStrategyBackspaceHelper {

    private int dontEraseMoreThan = -1;
    private IIndentationStringProvider indentationStringProvider;

    /**
     * Returns the position of the last non whitespace char in the current line.
     * @param doc
     * @param cursorOffset
     * @return position of the last character of the line (returned as an absolute
     *            offset)
     * 
     * @throws BadLocationException
     */
    protected int getLastCharPosition(IDocument doc, int cursorOffset) throws BadLocationException {
        IRegion region;
        region = doc.getLineInformationOfOffset(cursorOffset);
        int offset = region.getOffset();
        String src = doc.get(offset, region.getLength());

        int i = src.length();
        boolean breaked = false;
        while (i > 0) {
            i--;
            //we have to break if we find a character that is not a whitespace or a tab.
            if (Character.isWhitespace(src.charAt(i)) == false && src.charAt(i) != '\t') {
                breaked = true;
                break;
            }
        }
        if (!breaked) {
            i--;
        }
        return (offset + i);
    }

    public void perform(TextSelectionUtils ps) {
        // Perform the action
        try {
            ITextSelection textSelection = ps.getTextSelection();

            if (textSelection.getLength() != 0) {
                eraseSelection(ps);
                return;
            }

            int lastCharPosition = getLastCharPosition(ps.getDoc(), ps.getLineOffset());

            int cursorOffset = textSelection.getOffset();

            IRegion lastCharRegion = ps.getDoc().getLineInformationOfOffset(lastCharPosition + 1);
            //System.out.println("cursorOffset: "+ cursorOffset);
            //System.out.println("lastCharPosition: "+lastCharPosition);
            //System.out.println("lastCharRegion.getOffset(): "
            // +lastCharRegion.getOffset());
            //          IRegion cursorRegion =
            // ps.doc.getLineInformationOfOffset(cursorOffset);

            if (cursorOffset == lastCharRegion.getOffset()) {
                //System.out.println("We are in the beginning of the line.");
                //in this situation, we are in the first character of the
                // line...
                //so, we have to get the end of the other line and delete it.
                if (cursorOffset != 0) {
                    // the first line.
                    eraseLineDelimiter(ps);
                }
            } else if (cursorOffset <= lastCharPosition) {
                //System.out.println("cursorOffset <= lastCharPosition");
                //this situation is:
                //    |a (delete to previous indentation - considers cursor
                // position)
                //or
                //    as | as (delete single char)
                //or
                //  | a (delete to previous indentation - considers cursor
                // position)
                //so, we have to treat it carefully
                eraseToPreviousIndentation(ps, false, lastCharRegion);
            } else if (lastCharRegion.getOffset() == lastCharPosition + 1) {
                //System.out.println("Only whitespaces in the line.");
                //in this situation, this line only has whitespaces,
                //so, we have to erase depending on the previous indentation.
                eraseToPreviousIndentation(ps, true, lastCharRegion);
            } else {

                if (cursorOffset - lastCharPosition == 1) {
                    //System.out.println("Erase single char.");
                    //last char and cursor are in the same line.
                    //this situation is:
                    //    a|
                    eraseSingleChar(ps);

                } else if (cursorOffset - lastCharPosition > 1) {
                    //this situation is:
                    //    a |
                    //System.out.println("Erase until last char is found.");
                    eraseUntilLastChar(ps, lastCharPosition);
                }
            }
        } catch (Exception e) {
            Log.log(e);
        }
    }

    /**
     * @param ps
     * @param hasOnlyWhitespaces
     * @param lastCharRegion 
     * @throws BadLocationException
     */
    private void eraseToPreviousIndentation(TextSelectionUtils ps, boolean hasOnlyWhitespaces, IRegion lastCharRegion)
            throws BadLocationException {
        String lineContentsToCursor = ps.getLineContentsToCursor();
        if (hasOnlyWhitespaces) {
            //System.out.println("only whitespaces");
            eraseToIndentation(ps, lineContentsToCursor);
        } else {
            //System.out.println("not only whitespaces");
            //this situation is:
            //    |a (delete to previous indentation - considers cursor position)
            //
            //or
            //
            //    as | as (delete single char)
            //
            //so, we have to treat it carefully
            //TODO: use the conditions above and not just erase a single
            // char.

            if (TextSelectionUtils.containsOnlyWhitespaces(lineContentsToCursor)) {
                eraseToIndentation(ps, lineContentsToCursor);

            } else {
                eraseSingleChar(ps);
            }
        }
    }

    private void eraseSingleChar(TextSelectionUtils ps) throws BadLocationException {

        ITextSelection textSelection = ps.getTextSelection();

        int replaceLength = 1;
        int replaceOffset = textSelection.getOffset() - replaceLength;
        IDocument doc = ps.getDoc();
        String contentType = doc.getContentType(replaceOffset);

        if (replaceOffset >= 0 && replaceOffset + replaceLength < doc.getLength()) {
            char c = doc.getChar(replaceOffset);
            if (c == '(' || c == '[' || c == '{' || c == '<') {
                //When removing a (, check if we have to delete the corresponding ) too.
                char peer = StringUtils.getPeer(c);
                if (replaceOffset + replaceLength < doc.getLength()) {
                    char c2 = doc.getChar(replaceOffset + 1);
                    if (c2 == peer) {
                        //Ok, there's a closing one right next to it, now, what we have to do is
                        //check if the user was actually removing that one because there's an opening
                        //one without a match.
                        //To do that, we go backwards in the document searching for an opening match and then
                        //search its match. If it's found, it means we can delete both, otherwise, this
                        //delete will make things correct.

                        //Create a matcher only matching this char

                        AutoEditPairMatcher pairMatcher = new AutoEditPairMatcher(
                                new char[] { c, peer }, contentType);
                        int openingPeerOffset = pairMatcher.searchForAnyOpeningPeer(replaceOffset, doc);
                        if (openingPeerOffset == -1) {
                            replaceLength += 1;
                        } else {
                            int closingPeerOffset = pairMatcher.searchForClosingPeer(openingPeerOffset + 1, c, peer,
                                    doc);
                            if (closingPeerOffset != -1) {
                                //we have a match, so, things are balanced and we can delete the next
                                replaceLength += 1;
                            }
                        }
                    }
                }

            } else if (c == '\'' || c == '"') {
                //when removing a ' or ", check if we have to delete another ' or " too.
                Tuple<String, String> beforeAndAfterMatchingChars = ps.getBeforeAndAfterMatchingChars(c);
                int matchesBefore = beforeAndAfterMatchingChars.o1.length();
                int matchesAfter = beforeAndAfterMatchingChars.o2.length();
                if (matchesBefore == 1 && matchesBefore == matchesAfter) {
                    replaceLength += 1;
                }
            }
        }
        makeDelete(doc, replaceOffset, replaceLength);
    }

    /**
     * 
     * @param ps
     * @throws BadLocationException
     */
    private void eraseLineDelimiter(TextSelectionUtils ps) throws BadLocationException {

        ITextSelection textSelection = ps.getTextSelection();

        int length = TextSelectionUtils.getDelimiter(ps.getDoc()).length();
        int offset = textSelection.getOffset() - length;

        //System.out.println("Replacing offset: "+(offset) +" lenght: "+
        // (length));
        makeDelete(ps.getDoc(), offset, length);
    }

    /**
     * 
     * @param ps
     * @throws BadLocationException
     */
    private void eraseSelection(TextSelectionUtils ps) throws BadLocationException {
        ITextSelection textSelection = ps.getTextSelection();

        makeDelete(ps.getDoc(), textSelection.getOffset(), textSelection.getLength());
    }

    /**
     * @param ps
     * @param lastCharPosition
     * @throws BadLocationException
     */
    private void eraseUntilLastChar(TextSelectionUtils ps, int lastCharPosition) throws BadLocationException {
        ITextSelection textSelection = ps.getTextSelection();
        int cursorOffset = textSelection.getOffset();

        int offset = lastCharPosition + 1;
        int length = cursorOffset - lastCharPosition - 1;
        //System.out.println("Replacing offset: "+(offset) +" lenght: "+
        // (length));
        makeDelete(ps.getDoc(), offset, length);
    }

    /**
     * TODO: Make use of the indentation gotten previously. This implementation
     * just uses the indentation string and erases the number of chars from it.
     * 
     * @param ps
     * @param indentation this is in number of characters.
     * @throws BadLocationException
     */
    private void eraseToIndentation(TextSelectionUtils ps, String lineContentsToCursor) throws BadLocationException {
        final int cursorOffset = ps.getAbsoluteCursorOffset();
        final int lineContentsToCursorLen = lineContentsToCursor.length();

        if (lineContentsToCursorLen > 0) {
            char c = lineContentsToCursor.charAt(lineContentsToCursorLen - 1);
            if (c == '\t') {
                eraseSingleChar(ps);
                return;
            }
        }

        String indentationString = this.indentationStringProvider.getIndentationString();

        int replaceLength;
        int replaceOffset;

        final int indentationLength = indentationString.length();
        final int modLen = lineContentsToCursorLen % indentationLength;

        if (modLen == 0) {
            replaceOffset = cursorOffset - indentationLength;
            replaceLength = indentationLength;
        } else {
            replaceOffset = cursorOffset - modLen;
            replaceLength = modLen;
        }

        IDocument doc = ps.getDoc();
        //        final int cursorLine = ps.getCursorLine();
        //        if (cursorLine > 0) {
        //            IRegion prevLineInfo = doc.getLineInformation(cursorLine - 1);
        //            int prevLineEndOffset = prevLineInfo.getOffset() + prevLineInfo.getLength();
        //            Tuple<Integer, Boolean> tup = PyAutoIndentStrategy.determineSmartIndent(prevLineEndOffset, doc, prefs);
        //            Integer previousContextSmartIndent = tup.o1;
        //            if (previousContextSmartIndent > 0 && lineContentsToCursorLen > previousContextSmartIndent) {
        //                int initialLineOffset = cursorOffset - lineContentsToCursorLen;
        //                if (replaceOffset < initialLineOffset + previousContextSmartIndent) {
        //                    int newReplaceOffset = initialLineOffset + previousContextSmartIndent + 1;
        //                    if (newReplaceOffset != cursorOffset) {
        //                        replaceOffset = newReplaceOffset;
        //                        replaceLength = cursorOffset - replaceOffset;
        //                    }
        //                }
        //            }
        //        }

        //now, check what we're actually removing here... we can only remove chars if they are the
        //same, so, if we have a replace for '\t ', we should only remove the ' ', and not the '\t'
        if (replaceLength > 1) {
            String strToReplace = doc.get(replaceOffset, replaceLength);
            char prev = 0;
            for (int i = strToReplace.length() - 1; i >= 0; i--) {
                char c = strToReplace.charAt(i);
                if (prev != 0) {
                    if (c != prev) {
                        replaceOffset += (i + 1);
                        replaceLength -= (i + 1);
                        break;
                    }
                }
                prev = c;
            }
        }

        makeDelete(doc, replaceOffset, replaceLength);
    }

    private void makeDelete(IDocument doc, int replaceOffset, int replaceLength) throws BadLocationException {
        if (replaceOffset < dontEraseMoreThan) {
            int delta = dontEraseMoreThan - replaceOffset;
            replaceOffset = dontEraseMoreThan;
            replaceLength -= delta;
            if (replaceLength <= 0) {
                return;
            }
        }
        doc.replace(replaceOffset, replaceLength, "");
    }

    public void setDontEraseMoreThan(int offset) {
        this.dontEraseMoreThan = offset;
    }

    /**
     * Creates a handler that will properly treat backspaces considering python code.
     */
    public static VerifyKeyListener createVerifyKeyListener(final TextViewer viewer,
            final IIndentationStringProvider indentationStringProvider) {
        return new VerifyKeyListener() {

            @Override
            public void verifyKey(VerifyEvent event) {
                if ((event.doit && event.character == SWT.BS && event.stateMask == 0 && viewer != null && viewer
                        .isEditable())) { //isBackspace
                    boolean blockSelection = false;
                    try {
                        blockSelection = viewer.getTextWidget().getBlockSelection();
                    } catch (Throwable e) {
                        //that's OK (only available in eclipse 3.5)
                    }
                    if (!blockSelection) {
                        ISelection selection = viewer.getSelection();
                        if (selection instanceof ITextSelection) {
                            //Only do our custom backspace if we're not in block selection mode.
                            AutoEditStrategyBackspaceHelper pyBackspace = new AutoEditStrategyBackspaceHelper();
                            pyBackspace.setIndentationStringProvider(indentationStringProvider);
                            TextSelectionUtils ps = new TextSelectionUtils(viewer.getDocument(),
                                    (ITextSelection) selection);
                            pyBackspace.perform(ps);
                            event.doit = false;
                        }
                    }
                }
            }
        };
    }

    protected void setIndentationStringProvider(IIndentationStringProvider indentationStringProvider) {
        this.indentationStringProvider = indentationStringProvider;
    }

}