// 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.doris.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import com.google.common.base.Predicate;
import com.google.common.collect.Lists;

/**
 * Generic tree structure. Only concrete subclasses of this can be instantiated.
 */
public class TreeNode<NodeType extends TreeNode<NodeType>> {
    protected ArrayList<NodeType> children = Lists.newArrayList();

    public NodeType getChild(int i) {
        return hasChild(i) ? children.get(i) : null;
    }

    public void addChild(NodeType n) {
        children.add(n);
    }

    public void addChildren(List <? extends NodeType > n) {
        children.addAll(n);
    }
    
    public boolean hasChild(int i) { return children.size() > i; }
    public void setChild(int index, NodeType n) { children.set(index, n); }
    public ArrayList<NodeType> getChildren() { return children; }
    public void clearChildren() { children.clear(); }

    /**
     * Count the total number of nodes in this tree. Leaf node will return 1.
     * Non-leaf node will include all its children.
     */
    public int numNodes() {
        int numNodes = 1;
        for (NodeType child: children) numNodes += child.numNodes();
        return numNodes;
    }

    /**
     * Add all nodes in the tree that satisfy 'predicate' to the list 'matches'
     * This node is checked first, followed by its children in order. If the node
     * itself matches, the children are skipped.
     */
    public <C extends TreeNode<NodeType>, D extends C> void collect(
            Predicate<? super C> predicate, Collection<D> matches) {
        // TODO: the semantics of this function are very strange. contains()
        // checks using .equals() on the nodes. In the case of literals, slotrefs
        // and maybe others, two different tree node objects can be equal and
        // this function would only return one of them. This is not intuitive.
        // We rely on these semantics to not have duplicate nodes. Investigate this.
        if (predicate.apply((C) this) && !matches.contains(this)) {
            matches.add((D) this);
            return;
        }
        for (NodeType child: children) child.collect(predicate, matches);
    }

    /**
     * Add all nodes in the tree that are of class 'cl' to the list 'matches'.
     * This node is checked first, followed by its children in order. If the node
     * itself is of class 'cl', the children are skipped.
     */
    public <C extends TreeNode<NodeType>, D extends C> void collect(
            Class cl, Collection<D> matches) {
        if (cl.equals(getClass())) {
            matches.add((D) this);
            return;
        }
        for (NodeType child: children) child.collect(cl, matches);
    }

    /**
     * Add all nodes in the tree that satisfy 'predicate' to the list 'matches'
     * This node is checked first, followed by its children in order. All nodes
     * that match in the subtree are added.
     */
    public <C extends TreeNode<NodeType>, D extends C> void collectAll(
            Predicate<? super C> predicate, List<D> matches) {
        if (predicate.apply((C) this)) matches.add((D) this);
        for (NodeType child: children) child.collectAll(predicate, matches);
    }

    /**
     * For each expression in 'nodeList', collect all subexpressions satisfying 'predicate'
     * into 'matches'
     */
    public static <C extends TreeNode<C>, D extends C> void collect(
            Collection<C> nodeList, Predicate<? super C> predicate, Collection<D> matches) {
        for (C node: nodeList) node.collect(predicate, matches);
    }

    /**
     * For each expression in 'nodeList', collect all subexpressions of class 'cl'
     * into 'matches'
     */
    public static <C extends TreeNode<C>, D extends C> void collect(
            Collection<C> nodeList, Class cl, Collection<D> matches) {
        for (C node: nodeList) node.collect(cl, matches);
    }

    public boolean contains(Class cl) {
        if (cl.isAssignableFrom(this.getClass()) && this.getClass().isAssignableFrom(cl)) {
            return true;
        }
        for (NodeType child : children) {
            if (child.contains(cl)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return true if this node or any of its children satisfy 'predicate'.
     */
    public <C extends TreeNode<NodeType>> boolean contains(
            Predicate<? super C> predicate) {
        if (predicate.apply((C) this)) return true;
        for (NodeType child: children) if (child.contains(predicate)) return true;
        return false;
    }

    /**
     * For each node in nodeList, return true if any subexpression satisfies
     * contains('predicate').
     */
    public static <C extends TreeNode<C>, D extends C> boolean contains(
            Collection<C> nodeList, Predicate<? super C> predicate) {
        for (C node: nodeList) if (node.contains(predicate)) return true;
        return false;
    }

    /**
     * Return true if any node in nodeList contains children of class cl.
     */
    public static <C extends TreeNode<C>> boolean contains(
            List<C> nodeList, Class cl) {
        for (C node: nodeList) if (node.contains(cl)) return true;
        return false;
    }
    
    public boolean containsSubclass(Class cl) {
        if (cl.isAssignableFrom(this.getClass())) {
            return true;
        }
        for (NodeType child : children) {
            if (child.containsSubclass(cl)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Return 'this' or first child that is exactly of class 'cl'.
     * Looks for matching children via depth-first, left-to-right traversal.
     */
    public <C extends NodeType> C findFirstOf(Class<C> cl) {
        if (this.getClass().equals(cl)) {
            return (C) this;
        }
        for (NodeType child : children) {
            NodeType result = child.findFirstOf(cl);
            if (result != null) {
                return (C) result;
            }
        }
        return null;
    }

}
