package genesom;

import java.util.StringTokenizer;
import java.util.Enumeration;

public class Node
{
  public final static String PERSISTENT_FIELD_SEPARATOR = "/";

  protected static int sid = -1;

  public static double proportion_pattern_context = 1.0;

  public /* final */ int id = ++sid;     // important: id -> array index
  public boolean visited;

  protected int init_id = -1;            // for loading persistent reps
  protected Map map;
  protected Node[] neighbours;           // k-dim
  protected double[] physical_position;  // usually 2,3-dim
  protected double[] weights;            // m-dim
  protected double[] weights_delta;      // m-dim
  protected GridPosition context;        // previously active region in map
  protected int depth;                   // not really depth but multi-purpose
  protected double update_arg;           // amout of delta update


  public static class Posid {

    Node node;
    int pos;

    public Posid(Node n, int num) { node = n; pos = num; }

    public int pos() { return pos; }

    public int id() { return node.id; }

    public Node node() { return node; }

  }


  public Node() { setNeighbours(null); }


  public Node(String persistent_representation) {

    StringTokenizer st = new StringTokenizer(persistent_representation,
                                             PERSISTENT_FIELD_SEPARATOR);

    if(st.countTokens() < 6)
      throw new RuntimeException("Node incomplete instantiation error from persistent representation " + persistent_representation);

    init_id = new Integer(st.nextToken()).intValue();


    StringTokenizer ft = new StringTokenizer(st.nextToken());

    int c = ft.countTokens();

    weights = new double[c];
    weights_delta = new double[c];

    for(int i = 0; i < c; i++)
      weights[i] = new Double(ft.nextToken()).doubleValue();


    context = new GridPosition(st.nextToken());


    ft = new StringTokenizer(st.nextToken());

    c = ft.countTokens();

    neighbours = new Node[c];

    for(int i = 0; i < c; i++)
      neighbours[i] = new Node().setInitID(new Integer(ft.nextToken()).intValue());

    depth = new Integer(st.nextToken()).intValue();


    ft = new StringTokenizer(st.nextToken());

    c = ft.countTokens();

    physical_position = new double[c];

    for(int i = 0; i < c; i++)
      physical_position[i] = new Double(ft.nextToken()).doubleValue();

    // more extensions ???
  }


  public Node(int noneigbors) {
    neighbours = new Node[noneigbors];
  }

  public Node(Node[] nodes) { setNeighbours(nodes); }


  public Node setInitID(int id) { init_id = id; return this; } // cascade

  public int getInitID() { return init_id; }


  public void setVisited(boolean v) { visited = v; }

  public boolean getVisited() { return visited; }



  public int addNeighbour(Node node)
  {
    Node[] tmp = new Node[neighbours.length + 1];
    System.arraycopy(neighbours, 0, tmp, 0, neighbours.length);
    tmp[neighbours.length] = node;
    neighbours = tmp;

    return neighbours.length - 1;
  }


  public int addNeighbours(Node[] nodes)
  {
    Node[] tmp = new Node[neighbours.length + nodes.length];
    System.arraycopy(neighbours, 0, tmp, 0, neighbours.length);
    System.arraycopy(nodes, 0, tmp, neighbours.length, nodes.length);
    neighbours = tmp;

    return neighbours.length - 1;
  }

//  public void removeNeighbour(int index) {} // XXX
//  public void removeNeighbours() { setNeighbours(null); }



  public int setNeighbours(Node[] nodes)
  {
    return (neighbours = (nodes == null) ? new Node[0] : nodes).length;
  }


  public void setNeighbour(int index, Node node)
  {
    neighbours[index] = node;
  }


  public Node[] getNeighbours() { return neighbours; }


  public Node neighbour(int index) { return neighbours[index]; }


  public Enumeration neighbours()
  {
    return new Enumeration() {
      int i = 0;
      public boolean hasMoreElements() { return i < neighbours.length; }
      public Object nextElement() { return neighbours[i++]; }
    };
  }


  public Posid[] freeNeigbours() {

    int i = neighbours.length, k = 0;

    Posid[] collect = new Posid[i];

    while(--i >= 0)
      if(neighbours[i] == null) collect[k++] = new Posid(this, i);

    Posid[] result = new Posid[k];

    for(i = 0; i < k; i++) result[i] = collect[i];

    return result;
  }


  public GridPosition context() { return context; }


  public void setPhysicalPosition(double[] pos)
  {
    physical_position = pos;
  }


  public double[] getPhysicalPosition() { return physical_position; }



  public void setMap(Map m) { map = m; }


// XXX public void set(double[] arg) { map.input = arg; }
// XXX public double[] get() { return map.input; }


  public double distance()
  {
    double sum = 0., t;

    for(int i = 0; i < weights_delta.length; i++)
      sum += (t = weights_delta[i]) * t;

    sum *= proportion_pattern_context /
             (map.inputmaxdistance > 0. ? map.inputmaxdistance : (map.inputmaxdistance = sum));

    // context handling: if lastwinner==null decide by pattern distance only
    if(proportion_pattern_context < 1.)
      sum += (1. - proportion_pattern_context) * map.distance(context, map.lastwinner);

    return sum;
  }


  public void updateWinner(double arg)
  {
    arg *= update_arg;

    for(int i = weights.length - 1; i >= 0; i--)
      weights[i] += arg * weights_delta[i];

    if(proportion_pattern_context < 1. && map.lastwinner != null)
      map.interpolate(context, map.lastwinner, arg);
  }


  void createWeights() {

    int l = map.input.length;

    weights = new double[l];
    weights_delta = new double[l];

    // XXX wise context initialization ??
    context = new GridPosition();
    context.n1 = context.n2 = context.n3 = this;
    context.lambda12 = context.lambda13 = 0.;
  }


  public void setWeightsDelta(double[] arg)
  {
    if(weights == null) createWeights();

    if(arg == null || arg.length != weights_delta.length)
      for(int i = weights_delta.length - 1; i >= 0; i--) weights_delta[i] = 0.;
    else
      System.arraycopy(arg, 0, weights_delta, 0, weights_delta.length);
  }


  public double[] getWeights() {
    if(weights == null) createWeights();
    return weights;
  }


  public void setWeights(double[] arg)
  {
    if(weights == null) createWeights();

    if(arg == null || arg.length != weights.length)
      for(int i = weights.length - 1; i >= 0; i--) weights[i] = 0.;
    else
      System.arraycopy(arg, 0, weights, 0, weights.length);
  }


  public void update(double arg)   // updates weights_delta
  {

    double sum = 0., t = 0.;

    for(int i = weights_delta.length - 1; i >= 0; i--)
      sum += (t = weights_delta[i] = map.input[i] - weights[i]) * t;

    if(sum > map.inputmaxdistance) map.inputmaxdistance = sum;

    update_arg = arg;
  }


  public String toString()
  {
    String s = "" + id;

    if(weights != null)
      for(int i = 0; i < weights.length; i++)
        s += " " + weights[i];

    return s;
  }


  public String toPersistentRepresentation() {

    StringBuffer sb = new StringBuffer();

    sb.append(id).append(PERSISTENT_FIELD_SEPARATOR);

    if(weights != null)
      for(int i = 0; i < weights.length; i++) {
        sb.append(weights[i]);
        if(i < weights.length - 1) sb.append(' ');
      }

    sb.append(PERSISTENT_FIELD_SEPARATOR);
    sb.append(context);
    sb.append(PERSISTENT_FIELD_SEPARATOR);

    if(neighbours != null)
      for(int i = 0, k = 0; i < neighbours.length; i++) {
        if(neighbours[i] != null) {
          if(k++ > 0) sb.append(' ');
          sb.append(neighbours[i].id);
        }
      }

    sb.append(PERSISTENT_FIELD_SEPARATOR);
    sb.append(depth);
    sb.append(PERSISTENT_FIELD_SEPARATOR);

    if(physical_position != null)
      for(int i = 0; i < physical_position.length; i++) {
        if(i > 0) sb.append(' ');
        sb.append(physical_position[i]);
      }

    sb.append(PERSISTENT_FIELD_SEPARATOR);
    sb.append(PERSISTENT_FIELD_SEPARATOR);
    sb.append(PERSISTENT_FIELD_SEPARATOR);

    return sb.toString();
  }

}
