/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package ast;

import API.RegistroBase;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Map;

/**
 *
 * @author Eugenio
 * @param
 */
//public abstract class Node implements NodeInterface {
public class Node extends RegistroBase {

    protected static int id = 0;

    public ArrayList<Node> childrens = new ArrayList<>();

    public Node parent;
    public Node next;
    public Node prev;

    public Node(String value) {
        super();
        attr.put("value", value);
        attr.put("id", "" + Node.id++);
//        attr.put("_tipo", this.getClass().getName());
    }

    public Node() {
        attr.put("id", "" + Node.id++);
    }

    public void setParent(Node n) {
        parent = n;
    }

    public void appendChildren(Node n) {
        childrens.add(n);
    }

    public boolean hasChildrens() {
        return !childrens.isEmpty();
    }
//    public String G(String attrib) {
//        return attr.containsKey(attrib) ? attr.G(attrib) : "";
//    }

    public Node S(String attrib, int value) {
        return S(attrib, "" + value);
    }

    public Node S(String attrib, String value) {
        attr.put(attrib, value);
        return this;
    }

    @Override
    public Node set(String attrib, int valor) {
        return S(attrib, "" + valor); //To change body of generated methods, choose Tools | Templates.
    }

    public String visit() {
        String retorno = "";
//        onVisit();
        for (Node node : childrens) {
            retorno += node.visit();
        }
        return retorno;
    }

    public String stringfy() {
        StringBuilder sb = new StringBuilder();
        stringfy("", true, sb, 0);
        return sb.toString();
    }

    protected void stringfy(String prefix, boolean isTail, StringBuilder sb, int index) {
        String valor = this.G("value"),
                cl = attr.containsKey("class") ? G("class") : "",
                subclass = attr.containsKey("subclass") ? G("subclass") : "",
                pointer = attr.containsKey("pointer") ? G("pointer") : "",
                tipo = G("type"),
                _class = "   [ " + cl + "(" + subclass + "): " + pointer + tipo + " (" + G("id") + ")]";

        System.out.println(prefix + (isTail ? " └── " : " ├── ") + valor + _class);
//        sb.append(prefix + (isTail ? " └── " : " ├── ") + valor + ":[" + index + "::" + filhos.size() + "] \n");
        index = 0;
        for (Iterator<Node> iterator = childrens.iterator(); iterator.hasNext();) {
            iterator.next().stringfy(prefix + (isTail ? "     " : " │   "), !iterator.hasNext(), sb, index++);
        }
    }

    public Node getFilho(int i) {
        return childrens.get(i);
    }

    public ArrayList<Node> filtrarFilhosPorClass(String _class) {
        ArrayList<Node> retorno = new ArrayList<>();
        for (Node node : childrens) {
            if (node.G("class").equals(_class)) {
                retorno.add(node);
            }
        }
        return retorno;
    }

    public ArrayList<Node> childrens() {
        return childrens;
    }

    public int getChildrenCount() {
        return childrens.size();
    }

    public String getValorFilho(int i) {
        return childrens.get(i).G("value");
    }

    @Override
    public String toString() {
        String fi = "";
        for (Node node : childrens) {
            fi += node.G("value") + ",";
        }
        String at = "";
        for (Map.Entry<String, String> entry : attr.entrySet()) {
            String prop = entry.getKey();
            String value = entry.getValue();
            at += "\t" + prop + ": " + value + ", \n";
        }
        return "{\n" + at + "}[" + fi + "]\n";
    }

    /**
     * Encontra um no filho pelo nome (Atributo text)
     *
     * @param nome
     * @return Node | null
     */
    public Node find(String nome) {
        for (Node node : childrens) {
            if (node.getText().equals(nome)) {
                return node;
            }
        }
        return null;
    }

    public Node _get(String nome) {
        return find(nome);
    }

    /**
     * Retorna o pai do no atual caSo caSe com o valor do atributo, caSo
     * contrario repete o proceSSo no pai do pai recurSivamente ate o no raiz.
     *
     * @param value Valor do atributo
     * @param atributo Atributo do No
     * @param niveis Valor inteiro -1 para numero maximo de niveiS, numero
     * poSitivo maior que zero para niveiS controloadoS
     * @return Node | null
     *
     */
    public Node closest(String value, String atributo) {
        return closest(value.split(","), atributo, -1);
    }

    public Node closest(String value, String atributo, int niveis) {
        return closest(value.split(","), atributo, niveis);
    }

    public Node closest(String[] buscas, String atributo) {
        return closest(buscas, atributo, -1);
    }

    public Node closest(String[] buscas, String atributo, int niveis) {
        if (parent != null) {
            for (String val : buscas) {
                if (parent.G(atributo).equals(val)) {
                    return parent;
                }
            }
            if ((niveis > 0 || niveis <= -1) && parent.parent != null) {
                return parent.closest(buscas, atributo, niveis - 1);
            }
        }
        return null;
    }

    public boolean temFilho(int i) {
        return (i >= 0 && i < childrens.size()) ? true : false;
    }

    public String Class() {
        return G("class");
    }

    public Node first() {
        return getFilho(0);
    }

    public Node last() {
        int size = childrens.size();
        if (size == 0) {
            return null;
        }
        return childrens.get(size - 1);
    }

    public Node copy(String atrib, Node r) {
        if (r != null) {
            for (String a : atrib.split(",")) {
                this.S(a, r.G(a));
            }
        }
        return this;
    }

    public Node getFilhoOuNull(int i) {
        return temFilho(i) ? getFilho(i) : null;
    }

    public String getValorOuVazio(String string) {
        return (attr.containsKey(string)) ? attr.get(string) : "";
    }

    public Node remove(String attr) {
        this.attr.remove(attr);
        return this;
    }

    public boolean eUltimoFilho() {
        return (G("_pos").equals("" + (parent.getChildrenCount() - 1))) ? true : false;
    }

    public boolean ePrimeiroFilho() {
        return (G("_pos").equals("0")) ? true : false;
    }

    public String getLabelNext() {
        return getValorOuVazio("_label_next" + G("_pos"));
    }

    public String getLabelExit() {
        return getValorOuVazio("_label_exit");
    }

    public String getLabelNextAndClear() {
        String valor = getValorOuVazio("_label_next" + G("_pos"));
        S("_label_next" + G("_pos"), "");
        return valor;
    }

    public Node getUltimoFilho() {
        int size = childrens.size();
        return (size > 0) ? childrens.get(size - 1) : null;
    }

    /**
     * Retorna um ArrayLiSt<Node> contendo todoS oS noS que tem o atributo igual
     * a valor do criteria a partir do no atual.
     *
     * @param criteria valor da buSca EX: aSSign
     * @param aclass atributo do Node EX: claSS
     * @return ArrayLiSt<Node>
     */
    public ArrayList<Node> findAll(String criteria, String aclass) {
        return findAll(criteria, aclass, -1);
    }

    /**
     * Retorna um ArrayLiSt<Node> contendo todoS oS noS que tem o atributo igual
     * a valor do criteria a partir do no atual.
     *
     * @param criteria valor da buSca EX: aSSign.
     * @param aclass atributo do Node EX: claSS.
     * @param nivel nivel de buSca. para todoS uSar -1, para numero limitado
     * uSar valoreS poSitivoS maioreS que zero.
     * @return ArrayLiSt<Node>
     */
    public ArrayList<Node> findAll(String criteria, String aclass, int nivel) {
        return findAll(criteria.split(","), aclass, nivel);
    }

    protected ArrayList<Node> findAll(String[] criteria, String aclass, int nivel) {
        ArrayList<Node> nodes = new ArrayList<>();
        nivel -= 1;
        boolean finded;
        for (Node node : childrens) {
            finded = false;
            for (String c : criteria) {
                if (node.eq(aclass, c)) {
//                    System.out.println("FindAll:[" + c + "]" + aclass + "(" + node.G("id") + ")" + node.G(aclass));
                    finded = true;
                    nodes.add(node);
                    break;
                }
            }
            if (!finded && nivel != 0) {
                nodes.addAll(node.findAll(criteria, aclass, nivel - 1));
            }
        }
        return nodes;
    }

//    public boolean temAtributo(String at) {
//        return attr.containsKey(at);
//    }
    /**
     * Retorna um ArrayLiSt<Node> contendo todoS oS noS que tem o atributo igual
     * a valor do criteria e e Sejam filhoS de primeiro grau a partir do no
     * atual.
     *
     * @param criteria valor da buSca EX: aSSign
     * @param aclass atributo do Node EX: claSS
     * @return ArrayLiSt<Node>
     */
    public ArrayList<Node> encontrePrimeiroGrau(String criteria, String aclass) {
        ArrayList<Node> nodes = new ArrayList<>();
        for (Node node : childrens) {
            if (node.G(aclass).equals(criteria)) {
                nodes.add(node);
            }
        }
        return nodes;
    }

    /**
     * Retorna um ArrayLiSt<Node> contendo todoS oS noS que não tenham o
     * atributo igual a valor do criteria a partir do no atual.
     *
     * @param criteria valor da buSca EX: aSSign
     * @param aclass atributo do Node EX: claSS
     * @return ArrayLiSt<Node>
     */
    public ArrayList<Node> encontreTodosMenos(String criteria, String aclass) {
        ArrayList<Node> nodes = new ArrayList<>();
        for (Node node : childrens) {
            if (!node.G(aclass).equals(criteria)) {
                nodes.add(node);
            } else {
                nodes.addAll(node.findAll(criteria, aclass));
            }
        }
        return nodes;
    }

    public Node findByClass(String classe) {
        for (Node node : childrens) {
            if (!node.G("class").equals(classe)) {
                continue;
            }
            return node;
        }
        return null;
    }

    public boolean getBoolean(String prop) {
        return this.G(prop).equals("true") ? true : false;
    }

    public Node addFilho(Node n) {
        n.parent = this;

        int quantidade = this.childrens.size();
        n.S("_pos", "" + quantidade);

        if (quantidade > 0) //        this.current.filhos.
        {
            Node n1 = this.childrens.get(quantidade - 1);
            n1.next = n;
            n.prev = n1;
        }
        this.childrens.add(n);
        return this;
    }

    public Node removeFilho(Node n) {
        if (this.childrens().contains(n)) {

            this.childrens().remove(n);
        }
        return this;
    }

    public Node copy() {
        Node n = new Node(this.getText());
        n.parent = this.parent;

        for (Map.Entry<String, String> v : this.attr.entrySet()) {
            if (v.getKey().equals("id")) {
                continue;
            }
            n.S(v.getKey(), v.getValue());
        }

        return n;
    }

    public boolean neq(String atrue, String string) {
        return !eq(atrue, atrue);
    }

}
