/*
 * Copyright (c) 2022, ctecinf.com.br
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package br.com.ctecinf.text;

import java.awt.Color;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;

/**
 *
 * @author Cássio Conceição
 * @since 05/09/2020
 * @version 2203
 * @see http://ctecinf.com.br/
 */
public class JavaDocument extends DefaultStyledDocument {

    private final String MATCHES = "(\\W)*(abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|extends|false|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|strictfp|super|switch|synchronized|this|throw|throws|transient|true|try|void|volatile|while)";
    private final StyleContext STYLE_CONTEXT = StyleContext.getDefaultStyleContext();
    private final AttributeSet ATTR_COMMENT = STYLE_CONTEXT.addAttribute(STYLE_CONTEXT.getEmptySet(), StyleConstants.Foreground, Color.GRAY);
    private final AttributeSet ATTR_STRING = STYLE_CONTEXT.addAttribute(STYLE_CONTEXT.getEmptySet(), StyleConstants.Foreground, Color.RED);
    private final AttributeSet ATTR_RESERVED_WORD = STYLE_CONTEXT.addAttribute(STYLE_CONTEXT.getEmptySet(), StyleConstants.Foreground, Color.BLUE);
    private final AttributeSet ATTR_WORD = STYLE_CONTEXT.addAttribute(STYLE_CONTEXT.getEmptySet(), StyleConstants.Foreground, Color.BLACK);

    private int findLastNonWordChar(String text, int index) {

        while (--index >= 0) {
            if (String.valueOf(text.charAt(index)).matches("\\W")) {
                break;
            }
        }

        return index;
    }

    private int findFirstNonWordChar(String text, int index) {

        while (index < text.length()) {
            if (String.valueOf(text.charAt(index)).matches("\\W")) {
                break;
            }
            index++;
        }

        return index++;
    }

    @Override
    public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {

        super.insertString(offset, str, a);

        String text = getText(0, getLength());

        int before = findLastNonWordChar(text, offset);

        if (before < 0) {
            before = 0;
        }

        int after = findFirstNonWordChar(text, offset + str.length());

        int wordL = before;
        int wordR = before;

        while (wordR <= after) {

            if (wordR == after || String.valueOf(text.charAt(wordR)).matches("\\W")) {

                if (text.substring(wordL, wordR).matches(MATCHES)) {
                    setCharacterAttributes(wordL + 1, wordR - wordL, ATTR_RESERVED_WORD, false);
                } else {
                    setCharacterAttributes(wordL, wordR - wordL, ATTR_WORD, false);
                }

                wordL = wordR;
            }

            wordR++;
        }

        Pattern p = Pattern.compile("\\\"(.*?)\\\"");
        Matcher m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_STRING, false);
        }

        p = Pattern.compile("\\'(.*?)\\'");
        m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_STRING, false);
        }

        p = Pattern.compile("\\/*(.*?)\\*/");
        m = p.matcher(text);

        while (m.find()) {
            int start = text.substring(0, m.start()).lastIndexOf("/*");
            setCharacterAttributes(start, m.end() - start, ATTR_COMMENT, false);
        }

        p = Pattern.compile("\\//(.*?)\\n");
        m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_COMMENT, false);
        }
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {

        super.remove(offs, len);

        String text = getText(0, getLength());

        int before = findLastNonWordChar(text, offs);

        if (before < 0) {
            before = 0;
        }

        int after = findFirstNonWordChar(text, offs);

        if (text.substring(before, after).matches(MATCHES)) {
            setCharacterAttributes(before + 1, after - before, ATTR_RESERVED_WORD, false);
        } else {
            setCharacterAttributes(before, after - before, ATTR_WORD, false);
        }

        Pattern p = Pattern.compile("\\\"(.*?)\\\"");
        Matcher m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_STRING, false);
        }

        p = Pattern.compile("\\'(.*?)\\'");
        m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_STRING, false);
        }

        p = Pattern.compile("\\//(.*?)\\n");
        m = p.matcher(text);

        while (m.find()) {
            setCharacterAttributes(m.start(), m.end() - m.start(), ATTR_COMMENT, false);
        }
    }
}
