package zemberek.normalization;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import zemberek.core.collections.UIntMap;

class Node {

  static final int TYPE_EMPTY = 0;
  static final int TYPE_WORD = 1;
  static final int TYPE_ENDING = 2;
  static final int TYPE_GRAPH_ROOT = 3;
  char chr;
  String word;
  private int index;
  private UIntMap<Node> nodes = new UIntMap<>(2);
  private Node[] epsilonNodes = null;
  private int type;

  Node(int index, char chr, int type) {
    this.index = index;
    this.chr = chr;
    this.type = type;
  }

  Node(int index, char chr, int type, String word) {
    this.index = index;
    this.chr = chr;
    this.type = type;
    this.word = word;
  }

  public int getType() {
    return type;
  }

  Iterable<Node> getImmediateChildNodeIterable() {
    return nodes;
  }

  List<Node> getImmediateChildNodes() {
    return nodes.getValues();
  }

  List<Node> getAllChildNodes() {
    List<Node> nodeList = nodes.getValues();
    if (epsilonNodes == null) {
      return nodeList;
    }
    for (Node emptyNode : epsilonNodes) {
      for (Node n : emptyNode.nodes) {
        nodeList.add(n);
      }
    }
    return nodeList;
  }

  boolean hasEpsilonConnection() {
    return epsilonNodes != null;
  }

  boolean hasImmediateChild(char c) {
    return nodes.containsKey(c);
  }

  boolean hasChild(char c) {
    if (hasImmediateChild(c)) {
      return true;
    }
    if (epsilonNodes == null) {
      return false;
    }
    for (Node node : epsilonNodes) {
      if (node.hasImmediateChild(c)) {
        return true;
      }
    }
    return false;
  }

  Node getImmediateChild(char c) {
    return nodes.get(c);
  }

  private void addIfChildExists(char c, List<Node> nodeList) {
    Node child = this.nodes.get(c);
    if (child != null) {
      nodeList.add(child);
    }
  }

  List<Node> getChildList(char[] charArray) {
    List<Node> children = new ArrayList<>(charArray.length + 1);
    for (char c : charArray) {
      addIfChildExists(c, children);
      if (epsilonNodes != null) {
        for (Node emptyNode : epsilonNodes) {
          emptyNode.addIfChildExists(c, children);
        }
      }
    }
    return children;
  }


  List<Node> getChildList(char c) {
    List<Node> children = new ArrayList<>(2);
    addIfChildExists(c, children);
    if (epsilonNodes != null) {
      for (Node emptyNode : epsilonNodes) {
        emptyNode.addIfChildExists(c, children);
      }
    }
    return children;
  }

  boolean connectEpsilon(Node node) {
    if (epsilonNodes == null) {
      epsilonNodes = new Node[1];
      epsilonNodes[0] = node;
    } else {
      for (Node n : epsilonNodes) {
        if (n.equals(node)) {
          return false;
        }
      }
      epsilonNodes = Arrays.copyOf(epsilonNodes, epsilonNodes.length + 1);
      epsilonNodes[epsilonNodes.length - 1] = node;
    }
    return true;
  }

  public Node[] getEpsilonNodes() {
    return epsilonNodes;
  }

  Node addChild(int index, char c, int type) {
    Node node = nodes.get(c);
    if (node == null) {
      node = new Node(index, c, type);
      nodes.put(c, node);
    }
    return node;
  }

  Node addChild(int index, char c, int type, String word) {
    Node node = nodes.get(c);
    if (node == null) {
      node = new Node(index, c, type, word);
      nodes.put(c, node);
    } else {
      node.word = word;
      node.type = type;
    }
    return node;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Node node = (Node) o;
    return index == node.index;
  }

  @Override
  public int hashCode() {
    return index;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder("[" + chr);
    char[] characters = new char[nodes.size()];
    int[] keys = nodes.getKeys();
    for (int i = 0; i < characters.length; i++) {
      characters[i] = (char) keys[i];
    }
    Arrays.sort(characters);
    if (nodes.size() > 0) {
      sb.append(" children=").append(Arrays.toString(characters));
    }
    if (word != null) {
      sb.append(" word=").append(word);
    }
    sb.append("]");
    return sb.toString();
  }
}
