/*
 * 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.debugger.jpda.truffle.ast;

import org.netbeans.modules.debugger.jpda.truffle.source.SourcePosition;

/**
 * A representation of Truffle Node class.
 */
public final class TruffleNode {

    private final String className;
    private final String description;
    private final String sourceURI;
    private final int l1;
    private final int c1;
    private final int l2;
    private final int c2;
    private final String tags;
    private final TruffleNode[] ch;
    private boolean current;
    private boolean currentEncapsulating;

    public TruffleNode(String className, String description, String sourceURI, int l1, int c1, int l2, int c2, String tags, int numCh) {
        this.className = className;
        this.description = description;
        this.sourceURI = sourceURI;
        this.l1 = l1;
        this.c1 = c1;
        this.l2 = l2;
        this.c2 = c2;
        this.tags = tags;
        this.ch = new TruffleNode[numCh];
    }

    private void setChild(int i, TruffleNode node) {
        ch[i] = node;
    }

    public String getClassName() {
        return className;
    }

    public String getClassSimpleName() {
        int index = className.lastIndexOf('.');
        if (index > 0) {
            return className.substring(index + 1);
        } else {
            return className;
        }
    }

    public String getTags() {
        return tags;
    }

    public String getDescription() {
        return description;
    }

    public String getSourceURI() {
        return sourceURI;
    }

    public int getStartLine() {
        return l1;
    }

    public int getStartColumn() {
        return c1;
    }

    public int getEndLine() {
        return l2;
    }

    public int getEndColumn() {
        return c2;
    }

    public TruffleNode[] getChildren() {
        return ch;
    }

    /** This node is currently being executed. */
    public boolean isCurrent() {
        return current;
    }

    public boolean isCurrentEncapsulating() {
        return currentEncapsulating;
    }

    public static Builder newBuilder() {
        return new Builder();
    }

    public static class Builder {

        private TruffleNode node;
        private SourcePosition currentPosition;

        private Builder() {}

        public Builder nodes(String nodes) {
            StringLineReader slr = new StringLineReader(nodes);
            node = parseNode(slr);
            return this;
        }

        public TruffleNode build() {
            if (currentPosition != null) {
                markCurrent(node, currentPosition);
            }
            return node;
        }

        /** Mark all node paths which are currently being executed as current. */
        private static boolean markCurrent(TruffleNode node, SourcePosition currentPosition) {
            if (node.getStartLine() == currentPosition.getStartLine() && node.getEndLine() == currentPosition.getEndLine() &&
                    node.getStartColumn() == currentPosition.getStartColumn() && node.getEndColumn() == currentPosition.getEndColumn()) {
                node.current = true;
                return true;
            } else {
                boolean isSomeCurrent = false;
                for (TruffleNode ch : node.getChildren()) {
                    if (markCurrent(ch, currentPosition)) {
                        isSomeCurrent = true;
                    }
                }
                node.currentEncapsulating = isSomeCurrent;
                return isSomeCurrent;
            }
        }

        private TruffleNode parseNode(StringLineReader slr) {
            String className = slr.nextLine();
            String description = slr.nextLine();
            String sourceURI;
            int l1, c1, l2, c2;
            String ss = slr.nextLine();
            if (ss.isEmpty()) {
                sourceURI = null;
                l1 = c1 = l2 = c2 = -1;
            } else {
                sourceURI = ss;
                ss = slr.nextLine();
                int i1 = 0;
                int i2 = ss.indexOf(':');
                l1 = Integer.parseInt(ss.substring(i1, i2));
                i1 = i2 + 1;
                i2 = ss.indexOf('-');
                c1 = Integer.parseInt(ss.substring(i1, i2));
                i1 = i2 + 1;
                i2 = ss.indexOf(':', i1);
                l2 = Integer.parseInt(ss.substring(i1, i2));
                i1 = i2 + 1;
                i2 = ss.length();
                c2 = Integer.parseInt(ss.substring(i1, i2));
            }
            String tags = slr.nextLine();
            int numCh = Integer.parseInt(slr.nextLine());
            TruffleNode node = new TruffleNode(className, description, sourceURI, l1, c1, l2, c2, tags, numCh);
            for (int i = 0; i < numCh; i++) {
                node.setChild(i, parseNode(slr));
            }
            return node;
        }

        public Builder currentPosition(SourcePosition position) {
            this.currentPosition = position;
            return this;
        }

        private static class StringLineReader {

            private final String lines;
            private int i = 0;

            private StringLineReader(String lines) {
                this.lines = lines;
            }

            String nextLine() {
                int i2 = lines.indexOf('\n', i);
                if (i2 < i) {
                    return null;
                }
                String line = lines.substring(i, i2);
                i = i2 + 1;
                return line;
            }
        }
    }
}
