package genesom;


import java.util.Vector;
import java.util.Hashtable;
import java.io.FileWriter;
import java.io.PrintWriter;

abstract class Map
{
  protected double[] input;
  protected double inputmaxdistance;
  protected NodeVisitor visitor;
  protected Node[] nodes;
  protected int[][] distance_matrix;     // lower half matrix
  protected double inv_node_distance_max = -1.;
  protected Node lastwinner;


  public Map() {}

  protected void init() {
    createNodes();
    distanceMatrix();
    connect();
  }


  /////////////////////////////////////////////////////////////////
  abstract protected void createNodes();
  /////////////////////////////////////////////////////////////////


  public void setInput(double[] ip) { input = ip; }
  public void setInputMaxDistance(double imd) { inputmaxdistance = imd; }



  protected void connect() // make map known to neurons
  {
    for(int i = 0; i < nodes.length; i++)
      nodes[i].setMap(this);
  }


  protected void distanceMatrix()
  {
    distance_matrix = new int[nodes.length][];

    for(int i = nodes.length - 1; i >= 0; i--)
      distance_matrix[i] = new int[i+1];

    Object delim = new Object();
    Vector queue = new Vector();
    Node[] neighbours = null;

    for(int i = nodes.length - 1; i >= 0; i--) {

      for(int j = nodes.length - 1; j >= 0; j--)
        nodes[j].visited = false;

      nodes[i].visited = true;

      boolean hasneighbours = false;

      int depth = 1;
      queue.addElement(nodes[i]);
      queue.addElement(delim);

      while(!queue.isEmpty()) {

        Object qo = queue.remove(0);

        if(qo == delim) {

          if(!hasneighbours) { queue.removeAllElements(); break; }

          ++depth;
          queue.addElement(delim);
          hasneighbours = false;
          qo = queue.remove(0);
        }
        neighbours = ((Node)qo).getNeighbours();

        for(int j = neighbours.length - 1; j >= 0; j--) {

          if(neighbours[j] != null && !neighbours[j].getVisited()) {

            int k = neighbours[j].id;

            if(i > k)
              distance_matrix[i][k] = depth;
            else
              distance_matrix[k][i] = depth;

            if(depth > inv_node_distance_max) inv_node_distance_max = depth;

            queue.addElement(neighbours[j]);
            neighbours[j].visited = true;
            hasneighbours = true;
          }
        }
      }
    }
    inv_node_distance_max = 1. / inv_node_distance_max;

    if(false) { // added for Thomas
      try {
        PrintWriter pw = new PrintWriter(new FileWriter("nndistmatrix.dat"));
        for(int i = 0; i < nodes.length; i++) {
          for(int k = 0; k < nodes.length; k++) {
            pw.print((k<i) ? distance_matrix[i][k] : distance_matrix[k][i]);
            pw.print(" ");
          }
          pw.println();
        }
        pw.close();
      }
      catch(Exception e) {
        throw new RuntimeException(e);
      }
      finally { System.err.println("Matrix written!"); System.exit(1); }
    }
  }

/* XXX GridPath Implementation {{{ */

/* The GridPath implementation here is rather awkward:
 * it works for triangle meshes only but not with arbitrary maps.
 * A TriangleMashMap with the methods below should be provided to bridge
 * the gap between Map and TriangleGridGenerator.
/* Also two helper methods for corner and edge handling are provided. */

  protected static Node handle_edge(Node n1, Node n2, Node n3) {

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


for_edge:

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

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

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

    return n;
  }


  // n[] contains triangle, whereby n[0] = edge 2b handled, n[3] = target
  //
  protected Node[] handle_corner(Node[] n) {  // non-static for nodeDistance()

    Node u = n[0], t = n[3],
         us[] = u.getNeighbours();

    int min = Integer.MAX_VALUE, d;

    // find next  nearest
    for(int i = 0; i < us.length; i++)
      if(us[i] != null && (d = nodeDistance(us[i], t)) < min) {
        min = d;
        n[2] = us[i];
      }

    // find n[2].neighbour that points back to u
    us = n[2].getNeighbours();


    min = Integer.MAX_VALUE;

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

      if(us[i] == null) continue;

      Node[] uss = us[i].getNeighbours();

      for(int j = 0; j < uss.length; j++) {
        if(uss[j] == n[0]) {
          if((d = nodeDistance(us[i], t)) < min) {
            min = d;
            n[1] = us[i];
          }
        }
      }
    }

if(n[0] == null || n[1] == null || n[2] == null)
System.err.println("#corner:null!");

    return n;
  }



//
//  public double grid_distance(GridPosition gp, Node target) {
//
//    return (1. - gp.lambda12 - gp.lambda13) * nodeDistance(gp.n1, target) +
//                               gp.lambda12  * nodeDistance(gp.n2, target) +
//                               gp.lambda13  * nodeDistance(gp.n3, target);
//  }
//

  public double grid_distance(GridPosition gp, Node target) {

    double t,
           l12_2 = gp.lambda12 * gp.lambda12,
           l13_2 = gp.lambda13 * gp.lambda13,
           h1 = l12_2 + l13_2 + gp.lambda12 * gp.lambda13,   // cosine theorem
           h2 = (t = (1. - gp.lambda12)) * t + l13_2 - t * gp.lambda13,
           h3 = (t = (1. - gp.lambda13)) * t + l12_2 - t * gp.lambda12,
           h4 = h3 - (t = (h2 - h3 - 1.) * t) * .25,

           d1 = nodeDistance(gp.n1, target),
           d2 = nodeDistance(gp.n2, target),
           d3 = nodeDistance(gp.n3, target),
           d4 = (d2 == d3) ? d2 : Double.MAX_VALUE,
           min = Double.MAX_VALUE;

    int cas = 1;  // in order to sqrt() only once

    if((t = d1 + h1) < min) { min = t;  }
    if((t = d2 + h2) < min) { min = t; cas = 2; }
    if((t = d3 + h3) < min) { min = t; cas = 3; }
    if((t = d4 + h4) < min) { min = t; cas = 4; }

    switch(cas) {
      case 1:  d1 +=     Math.sqrt(h1); break;
      case 2:  d1 = d2 + Math.sqrt(h2); break;
      case 3:  d1 = d3 + Math.sqrt(h3); break;
      default: d1 = d4 + Math.sqrt(h4);
    }

    return d1;
  }


  public double distance(GridPosition gp, Node target) {
    return grid_distance(gp, target) * inv_node_distance_max;
  }


  public boolean interpolate(GridPosition from, Node to, double l) {

    boolean result = true;

    double d = grid_distance(from, to),
           r = d - (int)d,             // remainder within triangle
           f = (d*=l) - r;

    f -= (int)f; // remainder of effective way

    if(d > r) {
      d -= r;
      r = 1.;
    }
    else {   // d <= 1
      r = d;
      d = 0.;
    }

    for(int i = (int)d + (f > 0. ? 1 : 0); result && i >= 0; i--) {
      result &= interpolateNeuron(from, to, r); // interpolation carried out?
      r = (i == 1 && f >= 0.) ? f : 1.;
    }

    return result;
  }


  // interpolate [0:1] to next neuron or triangle edge
  public boolean interpolateNeuron(GridPosition from, Node to, double l) {

    if(from.equals(to)) return false;

    double d1 = nodeDistance(from.n1, to),
           d2 = nodeDistance(from.n2, to),
           d3 = nodeDistance(from.n3, to),

           diff12 = d2 - d1,
           diff13 = d3 - d1,

           h = (diff12 < 0. ? -diff12 : diff12) +
               (diff13 < 0. ? -diff13 : diff13);

//    if(h == 0.)
//      throw new RuntimeException("Invalid triangle: no preferred corner !");


    int corner = 1;

    double aim12, aim13, slambda;

    if(diff12 < 0. && diff13 < 0.) { // goto n2 and n3
      aim12 = 1. - from.lambda12;
      aim13 = 1. - from.lambda13;
      corner = 4;
    }
    else if(diff12 < 0.) { // goto n2
      aim12 = 1. - from.lambda12;
      aim13 = from.lambda13;
      corner = 2;
    }
    else if(diff13 < 0.) { // goto n3
      aim12 = from.lambda12;
      aim13 = 1. - from.lambda13;
      corner = 3;
    }
    else { // goto n1
      aim12 = from.lambda12;
      aim13 = from.lambda13;
    }

    if(l < 1.) {    // jump to triangle border

      if((h = aim12 + aim13) > -1e-10 && h < 1e-10) // ab durch die Mitte!
        aim12 = aim13 = .5 * (h = l);

      h = l / h;

      aim12 *= h * ((diff12 < 0.) ? -1. : 1.);
      aim13 *= h * ((diff13 < 0.) ? -1. : 1.);

      from.lambda12 -= aim12;
      from.lambda13 -= aim13;

    }
    else {   //   if(l >= 1.) {    // jump to triangle border

      switch(corner) {

        case 1:
          from.lambda12 = from.lambda13 = -1e-10;  // trigger reorientation
          break;

        case 2:
          from.lambda12 = 1. + 1e-10;
          from.lambda13 = 0. - 1e-10;
          break;

        case 3:
          from.lambda12 = 0. - 1e-10;
          from.lambda13 = 1. + 1e-10;
          break;

        default:

          l = from.lambda12 + from.lambda13;

          if(l > 0.) {
            l = 1.00000001 / l;   // trigger reorientation
            from.lambda12 *= l;
            from.lambda13 *= l;
          }
          else
            from.lambda12 = from.lambda13 = .5;
      }

      l = 1.;
    }

    slambda = ((from.lambda12 < 0.) ? -from.lambda12 : from.lambda12) +
              ((from.lambda13 < 0.) ? -from.lambda13 : from.lambda13);


    l *= .5;

// System.err.println("##  " + from.n1.id + "." + from.n2.id + "." + from.n3.id + ":" + from.lambda12 + ":" + from.lambda13 + " -> " + to.id + " ~ " + distance(from,to) + " - " + l);

    if(from.lambda12 + from.lambda13 < l) { // corner N1

      Node[] ns = handle_corner(new Node[] {from.n1, from.n2, from.n3, to});

      from.n1 = ns[0];
      from.n2 = ns[1];
      from.n3 = ns[2];

      if(from.lambda12 < 0.) from.lambda12 = -from.lambda12;
      if(from.lambda13 < 0.) from.lambda13 = -from.lambda13;

// System.err.print("# CN1");
    }

/******/

    if(from.lambda12 < 0.) {  // edge N1 - N3

      if(slambda > 1. - l) { // corner N3

        Node[] ns = handle_corner(new Node[] {from.n3, from.n1, from.n2, to});

        from.n1 = ns[0];
        from.n2 = ns[1];
        from.n3 = ns[2];

        if((from.lambda13 = 1.-from.lambda13) < 0.)
          from.lambda13 = -from.lambda13;

// System.err.print("# CN3");
      }
      else {
        from.n2 = handle_edge(from.n1, from.n3, from.n2);
// System.err.print("# N1-N3");
      }
      from.lambda12 = -from.lambda12;

      slambda = 0.;

if(from.n2 == null)
System.err.println("#!!NULL N2");
// else
// System.err.println();
    }

/******/

    if(from.lambda13 < 0.) {  // edge N1 - N2

      if(slambda > 1. - l) { // corner N2

        Node[] ns = handle_corner(new Node[] {from.n2, from.n1, from.n3, to});

        from.n1 = ns[0];
        from.n2 = ns[1];
        from.n3 = ns[2];

        if((from.lambda12 = 1.-from.lambda12) < 0.)
          from.lambda12 = -from.lambda12;

// System.err.print("# CN2");
      }
      else {
        from.n3 = handle_edge(from.n1, from.n2, from.n3);
// System.err.print("# N1-N2");
      }
      from.lambda13 = -from.lambda13;

      slambda = 0.;

if(from.n3 == null)
System.err.println("#!!NULL N3");
//else
//  System.err.println();
    }

/******/

    if(slambda > 1.) {  // edge N2 - N3

      from.n1 = handle_edge(from.n2, from.n3, from.n1);

// System.err.print("# N2-N3");
if(from.n1 == null)
System.err.println("#!!NULL N1");
// else
// System.err.println();


      double d = 2. - slambda;

      from.lambda12 = slambda - from.lambda12;
      from.lambda13 = slambda - from.lambda13;

      d /= from.lambda12 + from.lambda13;

      from.lambda12 *= d;
      from.lambda13 *= d;
    }


    h = grid_distance(from, to);

    if(h < l && h > -l) {  // warp to target -> last h max = 2*l
      from.n1 = to;
      from.n2 = to;
      from.n3 = to;
      from.lambda12 = 0.;
      from.lambda13 = 0.;
      return false;
    }

    return true;
  }

/* }}} GridPath Implementation */



  public Node[] getNodes() { return nodes; }

  public int nodeDistance(int first, int second)
  {
    return (first > second) ? distance_matrix[first][second] :
                              distance_matrix[second][first];
  }

  public int nodeDistance(Node first, Node second)
  {
    return first == null || second == null ? 0 :
           (first.id > second.id) ? distance_matrix[first.id][second.id] :
                                    distance_matrix[second.id][first.id];
  }

  // rather seldom operation...
  public int nodeDistanceMax() { return (int)(1. / inv_node_distance_max); }

  public void setVisitor(NodeVisitor nv) { visitor = nv; }

  public void visit(int inputnode)
  {
    for(int j = nodes.length - 1; j >= 0; j--)  // could be improved by table
      nodes[j].visited = false;

    visitor.setStartNode(nodes[inputnode]);
    visit(inputnode, inputnode);
  }


  protected void visit(int start_node, int current_node) {

    if(nodes[current_node].getVisited()) return;

    nodes[current_node].visited = true;

    if(visitor.at(nodes[current_node])) {

      Node[] neighbours = nodes[current_node].getNeighbours();

      for(int j = neighbours.length - 1; j >= 0; j--)
        if(neighbours[j] != null)
          visit(start_node, neighbours[j].id);
     }
   }

  public void visit() // all nodes
  {
    for(int i = nodes.length - 1; i >= 0; i--)
      visitor.at(nodes[i]);
  }

  public void setWinner(Node winner) { lastwinner = winner; }
  public Node getWinner() { return lastwinner; }

  public void print() {
    for(int i = 0; i < nodes.length; i++) {
      for(int j = 0; j < nodes.length; j++) {
        System.out.print("" + nodeDistance(i,j) + " ");
      }
      System.out.println();
    }
  }

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

      Node[] neig = nodes[i].getNeighbours();

      for(int j = 0; j < neig.length; j++) {
        if(neig[j] != null) {
          //System.out.print(nodes[i] + " " + nodes[i].context() + " ");
          System.out.println(nodes[i] + " " + nodes[i].context() + " ");
          System.out.println(neig[j] + " " + neig[j].context());
        }
      }
      System.out.println();
    }
    System.out.println("\n");
  }


  // XXX public GridPath getGridPath() { return this; }


  public void printConnexions()  // im c_som - Map-Format
  {

    System.out.println(nodes.length + " #Nodes");

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

      Node[] neighbours = nodes[i].getNeighbours();
      double[] pos = nodes[i].getPhysicalPosition();

      if(pos != null) {

        for(int j = 0; j < pos.length; j++) System.out.print(pos[j] + " ");

        System.out.println(neighbours.length);
      }
    }

    for(int i = 0; i < nodes.length; i++) {
      Node[] neighbours = nodes[i].getNeighbours();
      for(int j = neighbours.length - 1; j >= 0; j--) {
        if(neighbours[j] != null) {
          int k = neighbours[j].id;
          System.out.println(k);
        }
      }
    }
  }
}
