/******************************************************************************
* Copyright (C) 2013  Fabio Zadrozny
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     Fabio Zadrozny <fabiofz@gmail.com> - initial API and implementation
******************************************************************************/
package org.python.pydev.shared_core.auto_edit;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.python.pydev.shared_core.partitioner.PartitionCodeReader;
import org.python.pydev.shared_core.string.ICharacterPairMatcher2;
import org.python.pydev.shared_core.string.StringUtils;

public class AutoEditPairMatcher extends DefaultCharacterPairMatcher implements ICharacterPairMatcher2 {

    private final char[] fChars;
    private final String contentType;

    public AutoEditPairMatcher(char[] chars, String contentType) {
        super(chars, contentType);
        this.fChars = chars;
        this.contentType = contentType;
    }

    @Override
    public int searchForClosingPeer(int offset, char openingPeer, char closingPeer, IDocument document) {
        try {
            PartitionCodeReader reader = new PartitionCodeReader(contentType);
            reader.configureForwardReader(document, offset, document.getLength());

            int stack = 1;
            int c = reader.read();
            while (c != PartitionCodeReader.EOF) {
                if (c == openingPeer && c != closingPeer) {
                    stack++;
                } else if (c == closingPeer) {
                    stack--;
                }

                if (stack <= 0) { //<= 0 because if we have a closing peer without an opening one, we'll return it.
                    return reader.getOffset();
                }

                c = reader.read();
            }

            return -1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int searchForOpeningPeer(int offset, char openingPeer, char closingPeer, IDocument document) {
        try {
            PartitionCodeReader fReader = new PartitionCodeReader(contentType);
            fReader.configureBackwardReader(document, offset);

            int stack = 1;
            int c = fReader.read();
            while (c != PartitionCodeReader.EOF) {
                if (c == closingPeer && c != openingPeer) {
                    stack++;
                } else if (c == openingPeer) {
                    stack--;
                }

                if (stack <= 0) {//<= 0 because if we have an opening peer without a closing one, we'll return it.
                    return fReader.getOffset();
                }

                c = fReader.read();
            }

            return -1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int searchForAnyOpeningPeer(int offset, IDocument document) {
        try {
            PartitionCodeReader fReader = new PartitionCodeReader(contentType);
            fReader.configureBackwardReader(document, offset);

            Map<Character, Integer> stack = new HashMap<Character, Integer>();

            HashSet<Character> closing = new HashSet<Character>();
            HashSet<Character> opening = new HashSet<Character>();

            for (int i = 0; i < fChars.length; i++) {
                stack.put(fChars[i], 1);
                if (i % 2 == 0) {
                    opening.add(fChars[i]);
                } else {
                    closing.add(fChars[i]);
                }
            }

            int c = fReader.read();
            while (c != PartitionCodeReader.EOF) {
                if (closing.contains((char) c)) { // c == ')' || c == ']' || c == '}' 
                    char peer = StringUtils.getPeer((char) c);
                    Integer iStack = stack.get(peer);
                    iStack++;
                    stack.put(peer, iStack);

                } else if (opening.contains((char) c)) { //c == '(' || c == '[' || c == '{'
                    Integer iStack = stack.get((char) c);
                    iStack--;
                    stack.put((char) c, iStack);

                    if (iStack == 0) {
                        return fReader.getOffset();
                    }
                }

                c = fReader.read();
            }

            return -1;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
