package genesom;

/*
 *
 *   Idee: Beim Erzeugen eines neuen Knotens verbrauche 1 Link der Tiefe T-1
 *         verbrauche 2 Links der Tiefe T, so vorhanden,
 *         geneneriere k-(0-3) Knoten.
 *
 *
 */

import java.util.Arrays;
import java.util.Vector;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.ListIterator;


public class RadialTriangleGridGenerator
  extends Map
{


  public int noneighbors;
  public int nonode;
  protected Node root;

  public RadialTriangleGridGenerator() { }

  public RadialTriangleGridGenerator(int noneigh, int non) {
    noneighbors = noneigh;
    nonode = non;
  }

  public void setNonode(int nonode) { this.nonode = nonode; }
  public void setNoneighbors(int noneighbors) { this.noneighbors = noneighbors; }

  public void createNodes() {

    LinkedList[] available_nodes = new LinkedList[4];
    LinkedList ll;

    ListIterator li;

    available_nodes[0] = new LinkedList();   // T-1 (predecessors)
    available_nodes[1] = new LinkedList();   // T   (siblings)
    available_nodes[2] = new LinkedList();   // auxiliary predecessors
    available_nodes[3] = new LinkedList();   // auxiliary outer ring for pruning


    nodes = new Node[nonode];

    int[] noouter = new int[nonode];
    noouter[0] = 0;
    noouter[1] = noneighbors;

    Node node, secnode;
    Node.Posid posid;
    int depth = 1, currcnt = 0, node_pos, pred = 0,
        startind, nextpred = 0, nodescnt = 0;


    while(true) {

      node = new Node(noneighbors);

      available_nodes[3].addLast(node);

      node.setPhysicalPosition(new double[] {depth, currcnt});


      node.depth = startind = 0; // number of predecessors

      li = (ll = available_nodes[0]).listIterator();

      if(li.hasNext()) {                  // predecessors available?

        node.depth = ++startind;

        posid = (Node.Posid)li.next();
        li.remove();

        secnode = posid.node();

        node.setNeighbour(0, secnode);    // set first predecessor
        secnode.setNeighbour(posid.pos(), node);

        if(nodescnt > noneighbors) {


          node.depth += 1 + ((pred <= (nextpred >> 1)) ? -pred : pred - nextpred - 1);

          if(pred++ == 0) {               // true for first hit
            ++startind;
            posid = (Node.Posid)li.next();
            li.remove();
            available_nodes[2].addLast(posid);   // add as second predecessor
            nextpred = noneighbors-4-((secnode.depth <= 1) ? 1 : 2);
          }
          if(pred > nextpred) pred = 0;
        }
      }


      boolean notfirst = nodescnt > 0;

      // skip predecessors, add two siblings, add to predecessors
      //
      for(int i = startind; i < noneighbors; i++) {

        posid = new Node.Posid(node, i);

        available_nodes[(notfirst && i <= startind + 1) ? 1 : 0].addLast(posid); // add to siblings
      }



      if(currcnt == noouter[depth]) {  // completestage

        ll = available_nodes[2];

        if(ll.size() > 0) ll.addFirst(ll.removeLast());


// debug output >>>
/*
li = available_nodes[1].listIterator();

while(li.hasNext()) {

  posid = (Node.Posid)li.next();

  System.err.print(posid.node() + " " );
}
System.err.println();
System.err.println();

System.err.println(currcnt + " --- " + nodescnt);
*/
// <<< debug output


        li = (ll = available_nodes[1]).listIterator();

        li.next();        // skip first

        boolean loop = true;

        do {  // walk the chain

          posid = (Node.Posid)li.next();
          node = posid.node();
          node_pos = posid.pos();
          li.remove();

          if(!li.hasNext()) {
            li = ll.listIterator();  // access first (skipped) element now
            loop = false;
          }

          // connect to another predecessor if still unset
          if(nodescnt > noneighbors && node.getNeighbours()[1] == null) {

            posid = (Node.Posid)available_nodes[2].removeFirst();
            secnode = posid.node();

            node.setNeighbour(1, secnode);
            secnode.setNeighbour(posid.pos(), node);
          }


          posid = (Node.Posid)li.next();
          secnode = posid.node();
          li.remove();

          node.setNeighbour(node_pos, secnode);
          secnode.setNeighbour(posid.pos(), node);

        } while(loop);


        if(nodescnt >= nonode-1) {  // bail out

          // Main idea: generate complete neuron rings, then prune to
          // target number. Maintain high density ... Node.depth = max!
          // prune all nodes of same minimum depth uniformly according to
          // inverse CyclicIndex
          int findex = noouter[depth];

          Node.Posid[] posids = new Node.Posid[findex];


          for(int i = 0; i < findex; i++)
            posids[i] = new Node.Posid((Node)available_nodes[3].get(i),i);

          int mi = nodescnt - nonode, le = mi + 1, ri = mi - 1,
              indices[] = null;


          if(mi > 0) {  // only if pruning to be done

            Arrays.sort(posids, new Comparator() {
              public int compare(Object o1, Object o2) {
                int v1 = ((Node.Posid)o1).node().depth,
                    v2 = ((Node.Posid)o2).node().depth;
                return  (v1 < v2) ?  -1 : ((v1 > v2) ? 1 : 0);
              }
            });

            mi = posids[mi].node().depth;

            while(--le >= 0      && posids[le].node().depth == mi);
            while(++ri <  findex && posids[ri].node().depth == mi);

            indices = CyclicIndex.compute((--ri)-(++le)+1);
          }
          else {
            le = nonode;
            ri = -1;
          }

          findex = nodescnt - nonode + 1;


          for(int i = 0; i < findex; i++) { // prune neurons

            node = posids[(i < le) ? i : le + indices[indices.length-i+le-1]].node();

            Node ns[] = node.getNeighbours();

            for(int k = 0; k < noneighbors; k++) {

              if(ns[k] == null) continue;

              Node[] os = ns[k].getNeighbours();

              boolean copy = false;

              for(int j = 1; j < noneighbors; j++) {
                if(os[j] == node) copy = true;
                if(copy) os[j] = (j < noneighbors - 1) ? os[j+1] : null;
              }
            }

          }

          // rename units' ids
          for(int i = findex; i < posids.length; i++) {
            findex = nonode - posids.length + i;
            nodes[findex] = posids[(i > ri) ? i : le + indices[indices.length-i+le-1]].node();
            nodes[findex].id = findex;
          }

          break;  // while(true)
        }

        for(int i = available_nodes[3].size() - 1; i >= 0; i--)
          nodes[nodescnt-i] = (Node)available_nodes[3].removeFirst();

        pred = currcnt = 0;

        ++depth;
        noouter[depth] = (noneighbors-4) * noouter[depth-1] - noouter[depth-2];
      }

      ++currcnt;
      ++nodescnt;
    }

    // print();
  }



  public void print() {

    for(int i = 0; i < nodes.length; i++) {

      Node x = nodes[i];

      Node[] nodes = x.getNeighbours();

      System.err.print(x);
      for(int k = 0; k < nodes.length; k++) {
        if(nodes[k] != null) {
          System.err.print(" " + nodes[k]);
        }
      }
      System.err.println("\n");

    }
  }


  public void testInter() {

    long seed = System.currentTimeMillis();
    java.util.Random rand = new java.util.Random(seed);

System.err.println("# rand(testInter) = {"+ seed + "}");

    GridPosition gp = new GridPosition();

    Node n;

    for(int k = 0; k < 100; k++) {

      gp.n1 = gp.n2 = gp.n3 = nodes[(int)(rand.nextDouble()*nodes.length)];
      gp.lambda12 = gp.lambda13 = 0.;

      do {

        do {
          gp.lambda12 = rand.nextDouble();
          gp.lambda13 = rand.nextDouble();
        } while(gp.lambda12 + gp.lambda13 > 1.);


        gp.n1 = nodes[(int)(rand.nextDouble()*nodes.length)];
        gp.n2 = gp.n1.getNeighbours()[0];

        Node nn1[] = gp.n1.getNeighbours(),
             nn2[] = gp.n2.getNeighbours();


        n = null;

  forr:
        for(int i1 = 0; i1 < nn1.length; i1++) {

          if((n = nn1[i1]) == null || n == gp.n2) continue;

          for(int i2 = 0; i2 < nn2.length; i2++) if(n == nn2[i2]) break forr;

        }

        gp.n3 = n;

      } while(n == null);

      n = nodes[(int)(rand.nextDouble()*nodes.length)];

        System.err.println("#" + k + "[" + gp.n1.id + ";" + gp.n2.id + ";" + gp.n3.id + "] " + " $" + gp.lambda12 + " $" + gp.lambda13);

      System.err.println("#" + gp);

      GridPosition gpsav = new GridPosition(gp);

      boolean more_interpolation = true;

      System.err.println(gp);
      while(more_interpolation) {

        more_interpolation = interpolateNeuron(gp, n, 0.5);


        System.err.println("#" + gp.lambda12 + " $ " + gp.lambda13 + " . " + distance(gp, n));

        System.err.println(gp);

      }
      System.err.println();
      System.err.println();

      gp = gpsav;

      more_interpolation = true;
      System.err.println(gp);

      while(more_interpolation) {

        more_interpolation = interpolate(gp, n, 0.5);


        System.err.println("#" + gp.lambda12 + " $ " + gp.lambda13 + " . " + distance(gp, n));

        System.err.println(gp);

      }


      System.err.println();
      System.err.println();
    }
  }


  public static void main(String a[])
  {
    int noneigh = (a.length >= 1) ? new Integer(a[0]).intValue() : 6,
        nonode  = (a.length >= 2) ? new Integer(a[1]).intValue() : 19;


    new RadialTriangleGridGenerator(noneigh, nonode);
  }
}




