/*
 *  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.apache.groovy.parser.antlr4.util;

import groovy.lang.Tuple2;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.groovy.parser.antlr4.GroovyParser;
import org.codehaus.groovy.ast.ASTNode;

import static groovy.lang.Tuple.tuple;
import static org.codehaus.groovy.runtime.DefaultGroovyMethods.asBoolean;

/**
 * Utilities for configuring node positions
 */
public class PositionConfigureUtils {
    /**
     * Sets location(lineNumber, colNumber, lastLineNumber, lastColumnNumber) for node using standard context information.
     * Note: this method is implemented to be closed over ASTNode. It returns same node as it received in arguments.
     *
     * @param astNode Node to be modified.
     * @param ctx     Context from which information is obtained.
     * @return Modified astNode.
     */
    public static <T extends ASTNode> T configureAST(T astNode, GroovyParser.GroovyParserRuleContext ctx) {
        Token start = ctx.getStart();
        Token stop = ctx.getStop();

        astNode.setLineNumber(start.getLine());
        astNode.setColumnNumber(start.getCharPositionInLine() + 1);

        configureEndPosition(astNode, stop);

        return astNode;
    }

    public static Tuple2<Integer, Integer> endPosition(Token token) {
        String stopText = token.getText();
        // ANTLR EOF / unset tokens often have null text; treat as zero-width
        if (stopText == null) {
            return tuple(token.getLine(), token.getCharPositionInLine() + 1);
        }
        int stopTextLength = stopText.length();
        int newLineCnt = (int) StringUtils.countChar(stopText, '\n');

        if (0 == newLineCnt) {
            // column numbers are code-point based (the lexer reads a CodePointCharStream), so
            // measure the token text in code points too -- otherwise supplementary characters
            // (e.g. emoji) make lastColumnNumber over-count by one UTF-16 unit each (GROOVY-12085)
            return tuple(token.getLine(), token.getCharPositionInLine() + 1 + stopText.codePointCount(0, stopTextLength));
        } else { // e.g. GStringEnd contains newlines, we should fix the location info
            // measure the trailing line in code points too, so supplementary characters after
            // the last newline don't make lastColumnNumber over-count by a UTF-16 unit each (GROOVY-12085)
            return tuple(token.getLine() + newLineCnt, stopText.codePointCount(stopText.lastIndexOf('\n'), stopTextLength));
        }
    }

    public static <T extends ASTNode> T configureAST(T astNode, TerminalNode terminalNode) {
        return configureAST(astNode, terminalNode.getSymbol());
    }

    public static <T extends ASTNode> T configureAST(T astNode, Token token) {
        astNode.setLineNumber(token.getLine());
        astNode.setColumnNumber(token.getCharPositionInLine() + 1);
        astNode.setLastLineNumber(token.getLine());
        // measure the token text in code points to keep lastColumnNumber code-point based (GROOVY-12085)
        String text = token.getText();
        int extra = text == null ? 0 : text.codePointCount(0, text.length());
        astNode.setLastColumnNumber(token.getCharPositionInLine() + 1 + extra);

        return astNode;
    }

    public static <T extends ASTNode> T configureAST(T astNode, ASTNode source) {
        astNode.setLineNumber(source.getLineNumber());
        astNode.setColumnNumber(source.getColumnNumber());
        astNode.setLastLineNumber(source.getLastLineNumber());
        astNode.setLastColumnNumber(source.getLastColumnNumber());

        return astNode;
    }

    public static <T extends ASTNode> T configureAST(T astNode, GroovyParser.GroovyParserRuleContext ctx, ASTNode initialStop) {
        Token start = ctx.getStart();
        astNode.setLineNumber(start.getLine());
        astNode.setColumnNumber(start.getCharPositionInLine() + 1);

        if (asBoolean(initialStop)) {
            astNode.setLastLineNumber(initialStop.getLastLineNumber());
            astNode.setLastColumnNumber(initialStop.getLastColumnNumber());
        } else {
            Token stop = ctx.getStop();
            configureEndPosition(astNode, stop);
        }

        return astNode;
    }

    public static <T extends ASTNode> void configureEndPosition(T astNode, Token token) {
        Tuple2<Integer, Integer> endPosition = endPosition(token);
        astNode.setLastLineNumber(endPosition.getV1());
        astNode.setLastColumnNumber(endPosition.getV2());
    }

    public static <T extends ASTNode> T configureAST(T astNode, ASTNode start, ASTNode stop) {
        astNode.setLineNumber(start.getLineNumber());
        astNode.setColumnNumber(start.getColumnNumber());

        if (asBoolean(stop)) {
            astNode.setLastLineNumber(stop.getLastLineNumber());
            astNode.setLastColumnNumber(stop.getLastColumnNumber());
        } else {
            astNode.setLastLineNumber(start.getLastLineNumber());
            astNode.setLastColumnNumber(start.getLastColumnNumber());
        }

        return astNode;
    }
}
