package org.basex.data;

import static org.basex.core.Text.*;

import java.io.*;
import java.util.*;

import org.basex.io.in.DataInput;
import org.basex.io.out.DataOutput;
import org.basex.util.*;
import org.basex.util.hash.*;
import org.basex.util.list.*;

/**
 * This class stores a single namespace node.
 *
 * @author BaseX Team 2005-18, BSD License
 * @author Christian Gruen
 */
final class NSNode {
  /** Child nodes. */
  private NSNode[] nodes;
  /** Number of children. */
  private int size;
  /** Parent node. */
  private NSNode parent;
  /** Dense array with ids of prefix/namespace uri pairs. */
  private int[] values;
  /** Pre value. */
  private int pre;

  /**
   * Default constructor.
   * @param pre pre value
   */
  NSNode(final int pre) {
    this.pre = pre;
    values = new int[0];
    nodes = new NSNode[0];
  }

  /**
   * Constructor, specifying an input stream.
   * @param in input stream
   * @param parent parent reference
   * @throws IOException I/O exception
   */
  NSNode(final DataInput in, final NSNode parent) throws IOException {
    this.parent = parent;
    pre = in.readNum();
    values = in.readNums();
    size = in.readNum();
    nodes = new NSNode[size];
    for(int n = 0; n < size; ++n) nodes[n] = new NSNode(in, this);
  }

  /**
   * Writes a single node to disk.
   * @param out output stream
   * @throws IOException I/O exception
   */
  void write(final DataOutput out) throws IOException {
    out.writeNum(pre);
    out.writeNums(values);
    out.writeNum(size);
    for(int c = 0; c < size; ++c) nodes[c].write(out);
  }

  /**
   * Returns the specified child.
   * @param i index
   * @return child
   */
  NSNode child(final int i) {
    return nodes[i];
  }

  /**
   * Returns the number of children.
   * @return number of children
   */
  int children() {
    return size;
  }

  /**
   * Returns the pre value.
   * @return pre value
   */
  int pre() {
    return pre;
  }

  /**
   * Returns the parent node.
   * @return parent node
   */
  NSNode parent() {
    return parent;
  }

  /**
   * Returns the ids of prefix/namespace uri pairs.
   * @return prefix/namespace uri pairs
   */
  int[] values() {
    return values;
  }

  // Requesting Namespaces ========================================================================

  /**
   * Finds the namespace node that is located closest to the specified pre value.
   * @param p pre value
   * @param data data reference
   * @return node
   */
  NSNode find(final int p, final Data data) {
    // return this node if the pre values of all children are greater than the searched value
    final int s = find(p);
    if(s == -1) return this;

    final NSNode ch = nodes[s];
    final int cp = ch.pre;
    // return exact hit
    if(cp == p) return ch;
    // found node is preceding sibling
    if(cp + data.size(cp, Data.ELEM) <= p) return this;
    // continue recursive search
    return nodes[s].find(p, data);
  }

  /**
   * Locates a child node with the specified pre value.
   * <ul>
   *   <li> If the value is found, the position of the child node is returned.</li>
   *   <li> Otherwise, the position of the last child with a smaller pre value is returned.</li>
   *   <li> -1 is returned if all children have greater pre values.</li>
   * </ul>
   * @param p pre value
   * @return position of the child node
   */
  int find(final int p) {
    int l = 0, h = size - 1;
    while(l <= h) { // binary search
      final int m = l + h >>> 1, v = nodes[m].pre;
      if(v == p) return m;
      if(v < p) l = m + 1;
      else h = m - 1;
    }
    return l - 1;
  }

  /**
   * Returns the id of the namespace uri for the specified prefix.
   * @param prefix prefix reference
   * @return if of the namespace uri, or {@code 0} if none is found
   */
  int uri(final int prefix) {
    final int[] vls = values;
    final int vl = vls.length;
    for(int v = 0; v < vl; v += 2) if(vls[v] == prefix) return vls[v + 1];
    return 0;
  }

  // Updating Namespaces ==========================================================================

  /**
   * Deletes nodes in the specified range (p .. p + s - 1) and updates the following pre values.
   * @param p pre value
   * @param s number of nodes to be deleted, or actually the size of the pre
   * value which is to be deleted
   */
  void delete(final int p, final int s) {
    // find the node to deleted
    int d = find(p);
    // if the node is not directly contained as a child, either start at array index 0 or
    // proceed with the next node in the child array to search for descendants of pre
    if(d == -1 || nodes[d].pre != p) ++d;
    // first pre value which is not deleted
    final int upper = p + s;
    // number of nodes to be deleted
    int num = 0;
    // determine number of nodes to be deleted
    for(int i = d; i < size && nodes[i].pre < upper; ++i, ++num);
    // new size of child array
    size -= num;

    // if all nodes are deleted, just create an empty array
    if(size == 0) nodes = new NSNode[0];
    // otherwise remove nodes from the child array
    else if(num > 0) System.arraycopy(nodes, d + num, nodes, d, size - d);
  }

  /**
   * Adds the specified node into the child array, which is sorted by pre values.
   * @param node child node
   */
  void add(final NSNode node) {
    if(size == nodes.length)
      nodes = Array.copy(nodes, new NSNode[Array.newSize(size)]);

    // find inserting position
    int s = find(node.pre);
    if(s < 0 || node.pre != nodes[s].pre) s++;

    System.arraycopy(nodes, s, nodes, s + 1, size++ - s);
    nodes[s] = node;
    node.parent = this;
  }

  /**
   * Adds the specified prefix and URI reference.
   * @param prefix prefix reference
   * @param uri uri reference
   */
  void add(final int prefix, final int uri) {
    final int s = values.length;
    values = Arrays.copyOf(values, s + 2);
    values[s] = prefix;
    values[s + 1] = uri;
  }

  /**
   * Recursively deletes the specified namespace URI reference.
   * @param uri namespace URI reference
   */
  void delete(final int uri) {
    for(int c = 0; c < size; ++c) nodes[c].delete(uri);

    final int vl = values.length;
    for(int v = 0; v < vl; v += 2) {
      if(values[v + 1] != uri) continue;
      final int[] vals = new int[vl - 2];
      System.arraycopy(values, 0, vals, 0, v);
      System.arraycopy(values, v + 2, vals, v, vl - v - 2);
      values = vals;
      break;
    }
  }

  /**
   * Recursive shifting of pre values after delete operations.
   * @param start update location
   * @param diff value to subtract from pre value
   */
  void decrementPre(final int start, final int diff) {
    if(pre >= start + diff) pre -= diff;
    for(int c = 0; c < size; c++) nodes[c].decrementPre(start, diff);
  }

  /**
   * Increments the pre value by the specified size.
   * @param diff value to add to pre value
   */
  void incrementPre(final int diff) {
    pre += diff;
  }

  // Printing Namespaces ==========================================================================

  /**
   * Prints the node structure for debugging purposes.
   * @param tb token builder
   * @param level level
   * @param ns namespace reference
   * @param start start pre value
   * @param end end pre value
   */
  private void print(final TokenBuilder tb, final int level, final Namespaces ns, final int start,
      final int end) {

    if(pre >= start && pre <= end) {
      tb.add(NL);
      for(int i = 0; i < level; ++i) tb.add("  ");
      tb.add(toString() + ' ');
      final int[] vls = values;
      final int vl = vls.length;
      for(int i = 0; i < vl; i += 2) {
        if(i != 0) tb.add(' ');
        tb.add("xmlns");
        final byte[] p = ns.prefix(vls[i]);
        if(p.length != 0) tb.add(':');
        tb.add(p).add("=\"").add(ns.uri(vls[i + 1])).add('"');
      }
    }
    for(int c = 0; c < size; ++c) nodes[c].print(tb, level + 1, ns, start, end);
  }

  /**
   * Adds the namespace structure of a node to the specified table.
   * @param table table
   * @param start first pre value
   * @param end last pre value
   * @param ns namespace reference
   */
  void table(final Table table, final int start, final int end, final Namespaces ns) {
    final int vl = values.length;
    for(int i = 0; i < vl; i += 2) {
      if(pre < start || pre > end) continue;
      final TokenList tl = new TokenList();
      tl.add(values[i + 1]);
      tl.add(pre);
      tl.add(pre - parent.pre);
      tl.add(ns.prefix(values[i]));
      tl.add(ns.uri(values[i + 1]));
      table.contents.add(tl);
    }
    for(int i = 0; i < size; i++) nodes[i].table(table, start, end, ns);
  }

  /**
   * Adds namespace information for the specified node to a map.
   * @param map namespace map
   * @param ns namespace reference
   */
  void info(final TokenObjMap<TokenList> map, final Namespaces ns) {
    final int vl = values.length;
    for(int v = 0; v < vl; v += 2) {
      final byte[] pref = ns.prefix(values[v]), uri = ns.uri(values[v + 1]);
      TokenList prfs = map.get(uri);
      if(prfs == null) {
        prfs = new TokenList(1);
        map.put(uri, prfs);
      }
      if(!prfs.contains(pref)) prfs.add(pref);
    }
    for(int c = 0; c < size; ++c) nodes[c].info(map, ns);
  }

  /**
   * Prints the node structure.
   * @param ns namespace reference
   * @param start start pre value
   * @param end end pre value
   * @return string
   */
  String toString(final Namespaces ns, final int start, final int end) {
    final TokenBuilder tb = new TokenBuilder();
    print(tb, 0, ns, start, end);
    return tb.toString();
  }

  @Override
  public String toString() {
    return "Pre[" + pre + ']';
  }
}
