/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.netbeans.modules.javascript2.editor;

import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.spi.editor.typinghooks.CamelCaseInterceptor;

/**
 * Replaced old KeystrokeHandler and takes care about delete word actions.
 *
 * @author Martin Fousek <marfous@netbeans.org>
 */
public class JsCamelCaseInterceptor implements CamelCaseInterceptor {

    @Override
    public boolean beforeChange(MutableContext context) throws BadLocationException {
        return false;
    }

    @Override
    public void change(final MutableContext context) throws BadLocationException {
        final Document doc = context.getDocument();
        final int offset = context.getOffset();
        final boolean reverse = context.isBackward();
        doc.render(new Runnable() {
            @Override
            public void run() {
                int nextOffset = getWordOffset(doc, offset, reverse);
                context.setNextWordOffset(nextOffset);
            }
        });
    }

    @Override
    public void afterChange(MutableContext context) throws BadLocationException {
    }

    @Override
    public void cancelled(MutableContext context) {
    }

    protected static int getWordOffset(Document doc, int offset, boolean reverse) {
        TokenSequence<? extends JsTokenId> ts = LexUtilities.getJsTokenSequence(doc, offset);
        if (ts == null) {
            return -1;
        }
        ts.move(offset);
        if (!ts.moveNext() && !ts.movePrevious()) {
            return -1;
        }
        if (reverse && ts.offset() == offset && !ts.movePrevious()) {
            return -1;
        }

        Token<? extends JsTokenId> token = ts.token();

        if (token.id() == JsTokenId.WHITESPACE) {
            // Eat up spaces
            if ((reverse && ts.offset() < offset) || (!reverse && ts.offset() > offset)) {
                return ts.offset();
            }
        }

        if (token.id() == JsTokenId.IDENTIFIER) {
            String image = token.text().toString();
            int imageLength = image.length();
            int offsetInImage = offset - ts.offset();

            if (reverse) {
                return getPreviousIdentifierWordOffset(ts, image, imageLength, offsetInImage);
            } else {
                return getNextIdentifierWordOffset(ts, image, imageLength, offsetInImage);
            }
        }

        return -1;
    }

    private static int getPreviousIdentifierWordOffset(TokenSequence<? extends JsTokenId> ts, String image, int imageLength, int offsetInImage) {
        offsetInImage = offsetInImage - 1;
        if (offsetInImage < 0) {
            return -1;
        }
        if (offsetInImage < imageLength && Character.isUpperCase(image.charAt(offsetInImage))) {
            for (int i = offsetInImage - 1; i >= 0; i--) {
                char charAtI = image.charAt(i);
                if (!Character.isUpperCase(charAtI)) {
                    // return offset of previous uppercase char in the identifier
                    return ts.offset() + i + 1;
                }
            }
            return ts.offset();
        } else {
            for (int i = offsetInImage - 1; i >= 0; i--) {
                char charAtI = image.charAt(i);
                if (Character.isUpperCase(charAtI)) {
                    // now skip over previous uppercase chars in the identifier
                    for (int j = i; j >= 0; j--) {
                        char charAtJ = image.charAt(j);
                        if (!Character.isUpperCase(charAtJ)) {
                            // return offset of previous uppercase char in the identifier
                            return ts.offset() + j + 1;
                        }
                    }
                    return ts.offset();
                }
            }
            return ts.offset();
        }
    }

    private static int getNextIdentifierWordOffset(TokenSequence<? extends JsTokenId> ts, String image, int imageLength, int offsetInImage) {
        int start = offsetInImage + 1;
        if (offsetInImage < 0 || offsetInImage >= image.length()) {
            return -1;
        }
        if (Character.isUpperCase(image.charAt(offsetInImage))) {
            // if starting from a Uppercase char, first skip over follwing upper case chars
            for (int i = start; i < imageLength; i++) {
                char charAtI = image.charAt(i);
                if (!Character.isUpperCase(charAtI)) {
                    break;
                }
                start++;
            }
        }
        for (int i = start; i < imageLength; i++) {
            char charAtI = image.charAt(i);
            if (Character.isUpperCase(charAtI)) {
                return ts.offset() + i;
            }
        }
        return ts.offset() + imageLength;
    }

    @MimeRegistration(mimeType = "text/javascript", service = CamelCaseInterceptor.Factory.class)
    public static class Factory implements CamelCaseInterceptor.Factory {

        @Override
        public CamelCaseInterceptor createCamelCaseInterceptor(MimePath mimePath) {
            return new JsCamelCaseInterceptor();
        }
    }
}
