package org.basex.data;

import static org.basex.core.Text.*;
import java.io.IOException;
import java.util.Arrays;

import org.basex.io.in.DataInput;
import org.basex.io.out.DataOutput;
import org.basex.util.TokenBuilder;
import org.basex.util.list.IntList;

/**
 * This class stores a single namespace node.
 *
 * @author BaseX Team 2005-11, BSD License
 * @author Christian Gruen
 */
final class NSNode {
  /** Children. */
  NSNode[] ch;
  /** Number of children. */
  int size;
  /** Parent node. */
  NSNode par;
  /** References to Prefix/URI pairs. */
  int[] vals;
  /** Pre value. */
  int pre;

  /**
   * Default constructor.
   * @param p pre value
   */
  NSNode(final int p) {
    vals = new int[0];
    ch = new NSNode[0];
    pre = p;
  }

  /**
   * Constructor, specifying an input stream.
   * @param in input stream
   * @param p parent reference
   * @throws IOException I/O exception
   */
  NSNode(final DataInput in, final NSNode p) throws IOException {
    par = p;
    pre = in.readNum();
    vals = in.readNums();
    size = in.readNum();
    ch = new NSNode[size];
    for(int c = 0; c < size; ++c) ch[c] = 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(vals);
    out.writeNum(size);
    for(int c = 0; c < size; ++c) ch[c].write(out);
  }

  /**
   * Sorts the specified node into the child array.
   * @param n child node
   * @return node
   */
  NSNode add(final NSNode n) {
    int s = size;
    if(s == ch.length) {
      final NSNode[] tmp = new NSNode[Math.max(s << 1, 1)];
      System.arraycopy(ch, 0, tmp, 0, s);
      ch = tmp;
    }
    while(--s >= 0 && n.pre - ch[s].pre <= 0);
    System.arraycopy(ch, ++s, ch, s + 1, size++ - s);
    ch[s] = n;
    n.par = this;
    return n;
  }

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

  /**
   * Recursively deletes the specified namespace URI reference.
   * @param uri namespace URI reference
   */
  void delete(final int uri) {
    for(final NSNode n : ch) n.delete(uri);
    final IntList il = new IntList(vals.length);
    for(int v = 0; v < vals.length; v += 2) {
      if(vals[v + 1] != uri) {
        il.add(vals[v]);
        il.add(vals[v + 1]);
      }
    }
    if(il.size() != vals.length) vals = il.toArray();
  }

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

  /**
   * Finds the closest namespace node for the specified pre value.
   * @param p pre value
   * @return node
   */
  NSNode find(final int p) {
    final int s = fnd(p);
    return s == -1 ? this : ch[s].pre == p ? ch[s] : ch[s].find(p);
  }

  /**
   * Returns the namespace URI reference for the specified prefix.
   * @param p prefix reference
   * @return u uri reference or 0
   */
  int uri(final int p) {
    for(int v = 0; v < vals.length; v += 2) if(vals[v] == p) return vals[v + 1];
    return 0;
  }

  /**
   * Deletes nodes in the specified range (p .. p + sz - 1) and updates the
   * following pre values
   * @param p pre value
   * @param sz 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 sz) {
    // find the pre value which must be deleted
    int s = fnd(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(s == -1 || ch[s].pre != p) ++s;
    // first pre value which is not deleted
    final int upper = p + sz;
    // number of nodes to be deleted
    int num = 0;
    // determine number of nodes to be deleted
    for(int i = s; i < size && ch[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) ch = new NSNode[0];

    // otherwise remove nodes from the child array
    else if(num > 0) System.arraycopy(ch, s + num, ch, s, size - s);
  }

  /**
   * Finds a specific pre value in the child array utilizing binary search
   * and returns its position.
   * @param p pre value
   * @return node
   */
  int fnd(final int p) {
    int l = 0, h = size - 1;
    while(l <= h) { // binary search
      final int m = l + h >>> 1;
      final int v = ch[m].pre;
      if(v == p) return m;
      if(v < p) l = m + 1;
      else h = m - 1;
    }
    return l - 1;
  }

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

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

  /**
   * Prints the node structure for debugging purposes.
   * @param tb string builder
   * @param ns namespace reference
   * @param l level
   * @param s start pre value
   * @param e end pre value
   */
  private void print(final TokenBuilder tb, final int l, final Namespaces ns,
      final int s, final int e) {
    if(pre >= s && pre <= e) {
      tb.add(NL);
      for(int i = 0; i < l; ++i) tb.add("  ");
      tb.add("Pre[" + pre + "] ");
      for(int i = 0; i < vals.length; i += 2) {
        tb.add("xmlns");
        final byte[] p = ns.pref(vals[i]);
        if(p.length != 0) tb.add(':');
        tb.add(p);
        tb.add("=\"");
        tb.add(ns.uri(vals[i + 1]));
        tb.add("\" ");
      }
    }
    for(int c = 0; c < size; ++c) ch[c].print(tb, l + 1, ns, s, e);
  }

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