import processing.core.*; 
import processing.xml.*; 

import ddf.minim.*; 

import java.applet.*; 
import java.awt.Dimension; 
import java.awt.Frame; 
import java.awt.event.MouseEvent; 
import java.awt.event.KeyEvent; 
import java.awt.event.FocusEvent; 
import java.awt.Image; 
import java.io.*; 
import java.net.*; 
import java.text.*; 
import java.util.*; 
import java.util.zip.*; 
import java.util.regex.*; 

public class microrail extends PApplet {

  
int GAME_TITLE = 1;
int GAME_PLAY = 2;
int gameMode = GAME_TITLE;


boolean wonIt = false;
boolean doBackArrow = false;
int GOLD = color(255,212,0);

int SCREENCENTER = 250;


boolean hintShowing;



Puzzle currentPuzzle ;

ParentPuzzle parentPuzzle ;

   String[] text3_parent,text3,text5,text7;
PImage titleart;
public void setup(){
  titleart = loadImage("microrail.png");
    frameRate(20);
  smooth();
   size(500,500);
  
  loadSounds();
  startMusic();
   text3_parent = loadStrings("3x3_parent.txt");   
   text3 = loadStrings("3x3.txt");
   text5 = loadStrings("5x5.txt");
   text7 = loadStrings("7x7.txt");
   
   resetPuzzle();
   
  // currentPuzzle = new Puzzle("5 5 25 0 1 1 1 0 1 2 1 1 0 1 1 1 1 1 1 1 3 1 0 0 1 1 1 0 8 11 16 16 17 13 18 1 2 36 11 12 10 15 7 12 5 10 2 6 3 7 3 8 1 5 8 14 13 14 18 23 22 23 15 21 21 22 1 2 13 18 16 17 11 16");
//   currentPuzzle = new Puzzle("7 7 49 0 0 1 1 1 1 0 0 1 2 1 1 1 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 1 1 0 1 1 1 3 1 0 0 0 1 1 1 1 0 24 32 33 33 34 20 26 30 37 22 30 17 24 5 11 21 22 37 38 19 20 3 9 11 12 72 31 38 31 32 24 25 18 25 17 23 16 23 10 18 46 47 45 46 44 45 39 40 40 47 36 44 8 15 15 16 21 29 29 36 26 27 27 34 12 19 4 5 4 10 2 3 2 8 11 12 3 9 19 20 37 38 21 22 5 11 17 24 22 30 30 37 20 26 33 34 32 33");
}
boolean doHand;
public void draw(){

      doHand = false;
 
 
     
  
     
     background(100,200,100); 
  
    if(doBackArrow && isInArrow()){
     doHand = true;
    }
    if(gameMode == GAME_PLAY) currentPuzzle.draw();  
    else doTitle();
    
    
    
     stroke(0);
    // line(0,0,250,250);
    // line(500,0,250,250);
    
    if(doBackArrow){
       noStroke();
       //flash arrow between gold and gray, otherwise gray
       if(currentPuzzle.trainMoving){
         float r = lerp(200,255,.5f +currentPuzzle.moveVal/2);
         float g = lerp(200,215,.5f+currentPuzzle.moveVal/2);
         float b = lerp(200,0,.5f+currentPuzzle.moveVal/2);
         fill(r,g,b);
       } else fill(200);
      rect(40,40,20,20);
      triangle(40,30,
              40,70,
              20,50); 
              
       if(showArrowHint){
          fill(GOLD);
          textAlign(LEFT);
          text("CLICK TO RETURN",70,55);
       }       
              
    }
    
    if(currentPuzzle != parentPuzzle && ! seenInstructions){
       textAlign(LEFT);
       fill(0);
       text(
       "MICROTOWN MICRORAIL NEEDS YOU! CLICK ON THE GRAY LINES TO LAY TRACK BETWEEN NODES. CLICK AGAIN TO REMOVE.\n\n"+
       
       "THE GOAL IS TO CONSTRUCT A SINGLE RAIL LINE PASSING THROUGH "+
"ALL THE STATIONS, WITH THE BLUE DOTS AS ENDS OF THE LINE. "+
"[I.E. WHEN SOLVED, EVERY WHITE STATION WILL HAVE 2 RAILS CONNECTED "+
"TO IT, AND THE BLUE STATIONS WILL HAVE ONLY A SINGLE RAIL EACH]\n\n"+
       "CLICK 'HINT' FOR SUGGESTIONS.",
       
       20,335,440,150);
    }
    
    if(currentPuzzle != parentPuzzle && gameMode == GAME_PLAY){
       textAlign(CENTER);
       fill(200);
       rect(210,475,80,20);
      fill(0);
       text("HINT",250,490); 
       
       if(overHint()){
          doHand = true; 
       }
       
    }
    
    
    
  if(mousePressed && overHint() && gameMode == GAME_PLAY && currentPuzzle != parentPuzzle){
    currentPuzzle.showHint();
    if(!hintShowing) currentPuzzle.hintCount++;
    hintShowing = true;


  } else {
    hintShowing = false;
  }  
    
    if(doHand) cursor(HAND); 
     else cursor(ARROW);   
    
    if(wonIt && gameMode == GAME_PLAY ){
          fill(GOLD);
        textAlign(CENTER);
         text("EXCELLENT! YOU ARE THE KING OF THE MICRORAIL!\n(You used "+parentPuzzle.howManyHints()+" hints)\nPRESS SPACE TO PLAY AGAIN",250,30); 
 
    }
    
//     text(round(frameRate),0,480);
}

public boolean overHint(){
return  mouseX >= 210 && mouseX <=  290 && mouseY >= 475 && mouseY <= 495 ;
}

public boolean isInArrow(){
  return (mouseX > 20 && mouseX < 60 && mouseY > 30 && mouseY < 70);
}

public void restoreParent(){
    KidPuzzle kid = (KidPuzzle)currentPuzzle; 
    currentPuzzle.startZoom(SCREENCENTER,SCREENCENTER,parentPuzzle.getScreenX(kid.x,kid.y),parentPuzzle.getScreenY(kid.x,kid.y),120,30);
    currentPuzzle = parentPuzzle;
    //currentPuzzle.startZoom(CENTER,CENTER,CENTER,CENTER,400,120);
    doBackArrow = false; 
    showArrowHint = false;
}


public void mousePressed(){
  
  if(gameMode == GAME_TITLE) {
      gameMode = GAME_PLAY; 
    stopMusic();  
    startGame();
    return;
  }
  
  boolean handled = false;
  if(doBackArrow){
     if(isInArrow()){
         restoreParent();
         fx_outwoosh();
         handled = true;
         seenInstructions = true;
     }
  }
  
 
  if(! handled) currentPuzzle.checkClick(); 
}



public void doTitle(){
    image(titleart,0,0);
    textAlign(RIGHT);
 

}

public void keyPressed(){
  if(key == ' '){
   gameMode = GAME_TITLE;     
   resetPuzzle();
   if(!musicPlaying) startMusic();
  }
}


public void resetPuzzle(){
  parentPuzzle = new ParentPuzzle(text3_parent[PApplet.parseInt(random(text3_parent.length))]); 
  currentPuzzle = parentPuzzle;
}

boolean seenClickHere,seenInstructions,seenArrowHint,   showArrowHint;
public void startGame(){
  seenClickHere = false;
  seenInstructions = false;
  seenArrowHint = false;
  wonIt = false;
  showArrowHint = false;
}

class ParentPuzzle extends Puzzle{
  
  ArrayList<KidPuzzle> kids = new ArrayList<KidPuzzle>();
  
  ParentPuzzle(String s){
    super(s);
    scaleBase = 300;
    recalcScale();
  /*
    kids.add(new KidPuzzle( text3[int(random(text3.length))],0,1,this,nodes[1][0]));
    //5s:
    kids.add(new KidPuzzle( text3[int(random(text3.length))],1,0,this,nodes[0][1]));
    kids.add(new KidPuzzle( text3[int(random(text3.length))],1,1,this,nodes[1][1]));
    kids.add(new KidPuzzle( text3[int(random(text3.length))],1,2,this,nodes[2][1]));
    //7s:
    kids.add(new KidPuzzle( text3[int(random(text3.length))],2,0,this,nodes[0][2]));
    kids.add(new KidPuzzle( text3[int(random(text3.length))],2,1,this,nodes[1][2]));
    kids.add(new KidPuzzle( text3[int(random(text3.length))],2,2,this,nodes[2][2]));
*/

    kids.add(new KidPuzzle( text3[PApplet.parseInt(random(text3.length))],1,0,this,nodes[0][1]));

    kids.add(new KidPuzzle( text5[PApplet.parseInt(random(text5.length))],2,0,this,nodes[0][2]));


    kids.add(new KidPuzzle( text5[PApplet.parseInt(random(text5.length))],0,1,this,nodes[1][0]));
    //5s:

    kids.add(new KidPuzzle( text5[PApplet.parseInt(random(text5.length))],1,1,this,nodes[1][1]));
    //7s:
    kids.add(new KidPuzzle( text7[PApplet.parseInt(random(text7.length))],1,2,this,nodes[2][1]));
    kids.add(new KidPuzzle( text7[PApplet.parseInt(random(text7.length))],2,1,this,nodes[1][2]));
    kids.add(new KidPuzzle( text7[PApplet.parseInt(random(text7.length))],2,2,this,nodes[2][2]));
}
  
  public int howManyHints(){
    int hints = 0;
       for(KidPuzzle kid : kids){
          hints+= kid.hintCount;   
       }
       return hints;
  }
  
  
public void drawNodes(){
//make sure we draw any zooming kid last
   KidPuzzle zoomingKid = null;
   for(KidPuzzle kid : kids){
     if(kid.zooming > 0) zoomingKid = kid;
      else kid.drawKid(checkHoverKid(kid));
   }   
   if(zoomingKid != null) zoomingKid.drawKid(false);
   if(!true){
     int nodeX = getNodeXFromName(startingNodeName);     
     int nodeY = getNodeYFromName(startingNodeName);     
     float nodeScreenX = getScreenX(nodeX,nodeY);
     float nodeScreenY = getScreenY(nodeX,nodeY);
   stroke(0);
     line(0,0,nodeScreenX,nodeScreenY);
     
   }
   
   
}

public void checkClick(){
  super.checkClick();
  for(KidPuzzle kid : kids){
    //println( getScreenX(kid.x, kid.y)+"--"+);
     if(checkHoverKid(kid)){
        currentPuzzle = kid;
        seenClickHere = true;
        currentPuzzle.startZoom(kid.centerX,kid.centerY,250,250,kid.scaleBase,120);
        fx_inwhoosh();
        doBackArrow = true;
     } 
  }
}

public boolean checkHoverKid(KidPuzzle kid){
  return dist(mouseX,mouseY, getScreenX(kid.x, kid.y),getScreenY(kid.x, kid.y)) <  kid.scaleBase*.6f*ROWS;
}

//so this is to enforce how the parent has different rules, only connected nodes are allowed....
public boolean areLiveNodes(int x,int y,int ex,int ey){
    int startNodeContent = nodes[y][x];  
    int endNodeContent = nodes[ey][ex];  
    if(!isNotDeadNode(startNodeContent)) return false;
    if(!isNotDeadNode(endNodeContent)) return false;
    //So both aren't zeros, but one end needs to be a solved puzzle 
    
    if(isConnectedNode(startNodeContent) || isConnectedNode(endNodeContent) ) return true;
    //OR this node needs to be a fixed node...
    int n1 = getNodeName(x,y);
     int n2 = getNodeName(ex,ey);
    if(fixedLinks.get(n1+"_"+n2)!= null) return true;
    
    return false;
}

public boolean isConnectedNode(int content){
   return  content == SOLVENODE;
}
public boolean isNotDeadNode(int content){
   return content != DEADNODE; 
}

}


class KidPuzzle extends Puzzle{
    int x,y;
    ParentPuzzle parent;
    boolean isEndpoint = false;
    boolean isStartingEndpoint = false;

    KidPuzzle(String s,int px,int py,ParentPuzzle pp, int pNodeType){
       super(s); 
      x = px;
      y = py;
      scaleBase = 30;
      recalcScale();
      parent = pp;
      
      if(pNodeType >  1) isEndpoint = true;
      if(pNodeType ==  STARTNODE) isStartingEndpoint = true;
      
      nodeTag = getNodeName(x,y);
      //println("I AM "+nodeTag);
      
      centerX = parent.getScreenX(x,y);
      centerY = parent.getScreenY(x,y);
      
    }
    
    public void drawKid(boolean hiLite){
      if(isEndpoint) fill(80,160,160);
             else fill(80,160,80);
      noStroke();
       strokeWeight(2);
      if(hiLite){
          stroke(128);
          doHand = true;
                 strokeWeight(4);
          line(centerX,centerY, centerX + scaleBase*.5f*ROWS,centerY + scaleBase*.5f*ROWS);
                 strokeWeight(3);
      } else {
          if(trainMoving) stroke(GOLD);
      }
      ellipse(centerX,centerY , scaleBase*.6f*ROWS,scaleBase*.6f * ROWS);  
      draw();
      if(isStartingEndpoint && ! seenClickHere){
           drawClickHere();
      }


    }
    
    public void drawClickHere(){
      stroke(GOLD);
      float top = centerY + (scaleBase*.6f * ROWS)/2 + 10;
      
      line(centerX,top,centerX,top+20);
      line(centerX,top,centerX-10,top+10);
      line(centerX,top,centerX+10,top+10);
      fill(GOLD);
      textAlign(CENTER);
      text("CLICK HERE\nTO START\nMICROTOWN",centerX,top+40);
      
    }
    
    
}
int DEADNODE = 0;
int LIVENODE = 1;
int STARTNODE = 2;
int ENDNODE = 3;
int SOLVENODE = 4;




class Puzzle{
  
  
int COLS;// = 7;
int ROWS;// = 7;
  float scaleBase = 100;
  
  float xgapMUL = .6f;
float ygapMUL = .5f;
float nodesizeMUL = .2f;
float xgap = xgapMUL*scaleBase;
float ygap = ygapMUL*scaleBase;
float nodesize = nodesizeMUL*scaleBase;
  
  int startingNodeName = -1;
  int endingNodeName = -1;
  
      int hintCount = 0;
  
  ArrayList<Link> links = new ArrayList<Link>();
  HashMap<String,Link> allLinkMap = new HashMap<String,Link>();
  HashMap<String,Link> fixedLinks = new HashMap<String,Link>();
  HashMap<String,Link> solveLinks = new HashMap<String,Link>();
  
  int[][] nodes;
  
  int[][] fixed;
  int[][] solve;

  float centerX = 250;
  float centerY = 250;
  
  int nodeTag;//for kids only really
  

public Link findSolveLinkForNode(int nodeName){
  for(String linkName : solveLinks.keySet()){
    String[]parts = linkName.split("_");
    int partA = parseInt(parts[0]);
    int partB = parseInt(parts[1]);
    if(nodeName == partA || nodeName == partB) return solveLinks.get(linkName);
  }
  return null;
}

public Link findOtherSolveLink(int nodeName, Link oldLink){
  for(String linkName : solveLinks.keySet()){
    String[]parts = linkName.split("_");
    int partA = parseInt(parts[0]);
    int partB = parseInt(parts[1]);
    if(nodeName == partA || nodeName == partB){
     if(solveLinks.get(linkName)!= oldLink){
        return solveLinks.get(linkName); 
     }
    }
  }
  return null;
  
}


   Puzzle(String s){
     readPuzzle(s);
     store(fixed,fixedLinks);
     store(solve,solveLinks);
     addLinks();
     
   }
   
   float zooming = 0;
   float startZoomX;
   float startZoomY;
   float endZoomX;
   float endZoomY;
   float startZoom;
   float endZoom;
   
   public void draw(){
     
     if(zooming > 0){
         zooming-=.075f;
         scaleBase = lerp(endZoom,startZoom,zooming);
         centerX = lerp(endZoomX,startZoomX, zooming);
         centerY = lerp(endZoomY,startZoomY ,zooming);
     } else zooming = 0;
     
     recalcScale();
     drawLinks();
      drawNodes();
      
      animateTrain();
      
      if(this == currentPuzzle && hintCount != 0){
        textAlign(CENTER);
        fill(0);
        text("HINTS USED: "+hintCount,250,15);
      }

      
      
   }
  
   public void startZoom(float sx,float sy,float ex,float ey,float sz,float ez){
      startZoomX = sx;
     startZoomY = sy;
     endZoomX = ex;
      endZoomY = ey;
      startZoom = sz;
      endZoom = ez;
      zooming = 1;
   } 
  
  
  
  
public void readPuzzle(String s){
   String parts[] = s.split(" ");
   int ptr = 0;
   
   ROWS = parseInt(parts[ptr++]);
   COLS = parseInt(parts[ptr++]);
   int numNodes = parseInt(parts[ptr++]);

 // println(numNodes);
  
  nodes = new int[ROWS][];
  for(int y = 0; y < ROWS; y++){
    nodes[y] = new int[COLS];
    //print("ROW "+y+":");
    for(int x = 0; x < COLS; x++){
      nodes[y][x] = parseInt(parts[ptr++]);
      
      if(nodes[y][x] == STARTNODE){
        startingNodeName = getNodeName(x,y);
      }
      if(nodes[y][x] == ENDNODE){
        endingNodeName = getNodeName(x,y);
      }
     // print(nodes[y][x] +" ");
    }    
    //println();
  }
  int numLinkNodes = parseInt(parts[ptr++]);
  fixed = new int[numLinkNodes/2][];
 for(int i = 0; i < numLinkNodes / 2; i++){
     fixed[i] = new int[2];
    fixed[i][0] =  parseInt(parts[ptr++]);
    fixed[i][1] =  parseInt(parts[ptr++]);
 } 
  int numSolveNodes = parseInt(parts[ptr++]);
  solve = new int[numSolveNodes/2][];
 for(int i = 0; i < numSolveNodes / 2; i++){
     solve[i] = new int[2];
    solve[i][0] =  parseInt(parts[ptr++]);
    solve[i][1] =  parseInt(parts[ptr++]);
 } 

}


public void drawNodes(){
   for(int y = 0; y < ROWS; y++){
     noStroke();
     for(int x = 0;  x < COLS; x++){
       int thisNode = nodes[y][x];
       if(thisNode != 0){
             if(thisNode > 1) fill(64,64,200);
             else fill(255);
          ellipse(getScreenX(x,y),getScreenY(x,y) ,nodesize,nodesize);          
       }
     }
   }
}

public void printNodes(){
   for(int y = 0; y < ROWS; y++){
     if(y % 2 == 1) print(" ");
    for(int x = 0; x < COLS; x++){
       print(nodes[y][x] + " ");
    }
    println("");
   } 
}


public float getScreenX(float x, float y){
  float loc = centerX - ((xgap * COLS)/2);
  if(y % 2 == 1) loc += xgap / 2;
  loc += x*xgap;
  if(COLS == 5 || COLS == 9) loc += xgap/2; //HACK
  return loc;
}
public float getScreenY(float x, float y){
  float loc = centerY - ((ygap * ROWS)/2);
  loc += y * ygap +(ygap/2);
  return loc;
}

public void addLinks(){
   stroke(128);strokeWeight(2);
   //look for side to side possibles
  for(int y = 0; y < ROWS; y++){ for(int x = 0; x < COLS; x++){
      if(nodes[y][x] != 0){
          //side to side
          int ex = x + 1;
          int ey = y;
          tryToAddLink(x,y,ex,ey);
          //down slant
          ey = y+1;
          ex = x;
          if(y % 2 == 1)ex++; 
         tryToAddLink(x,y,ex,ey);
          //upslant
          ex = x-1;
          if(y % 2 == 1) ex++; 
           tryToAddLink(x,y,ex,ey);
     } 
       
     }
 }

}

public boolean areLiveNodes(int x,int y,int ex,int ey){
    int startNodeContent =nodes[y][x];  
    int endNodeContent =nodes[ey][ex];  
    if(startNodeContent != DEADNODE && endNodeContent != DEADNODE) return true;
    return false;
}


public void tryToAddLink(int x, int y, int ex, int ey){
      int n1 = getNodeName(x,y);
      int n2 = getNodeName(ex,ey);
      if(!( n1 < n2)){
         println("ERROR IN TRYTOADDLINK, NO *YOURE* OUT OF ORDER::"+n1+" "+n2); 
      }
      
      String name = n1+"_"+n2;
      if(allLinkMap.get(name) != null){
//         println(name + " is already a node");
        return; 
      }
      if(ex>=0 && ey>=0){
        if( ex < COLS && ey < ROWS ){
          if(areLiveNodes(x,y,ex,ey)){
           Link newLink = new Link(x,y,ex,ey);
           newLink.checkIfFixed(fixedLinks);
           links.add(newLink);
           allLinkMap.put(newLink.name,newLink);
          }
        }  
       }
     // else          print("sm ");      
  
}


public void drawLinks(){
   for(Link k : links){
      k.draw(this == currentPuzzle);
   } 
}

public void checkClick(){
  boolean needToCheckWin = false;
   for(Link k : links){
      if(k.checkClick(this == currentPuzzle)) needToCheckWin = true;
   } 
   if(needToCheckWin) {
     
     if(checkSolve()){
         startTrain(startingNodeName);
         puzzleWon();
     }}
}


Link trainLink = null;
int trainStartNode = -1;
int trainEndNode = -1;

float moveVal;
boolean trainMoving = false;

int trainReverseNodeName = -1;

public void puzzleWon(){
//   println("WOOT"); 
   fx_whistle();
   
   if(seenArrowHint == false){
    seenArrowHint = true;
   showArrowHint = true; 
   }
   
   //mark this one one as solved in the parent puzzle (needed for unlocking paths)
   if(currentPuzzle != parentPuzzle){
      int nodeNameAsKid = currentPuzzle.nodeTag;
      parentPuzzle.nodes[getNodeYFromName(nodeNameAsKid)][getNodeXFromName(nodeNameAsKid)] = SOLVENODE;
      parentPuzzle.printNodes();
      parentPuzzle.addLinks(); //redo and add any links we can do because it's all unlocked and stuff...
   } else {
      startMusic();
      wonIt = true;
   }
}
public void startTrain(int goalPointNode){
        trainLink = findSolveLinkForNode(goalPointNode);
        moveVal = 0;
        trainMoving = true;
        trainStartNode = goalPointNode;
        trainEndNode = trainLink.getOtherNode(trainStartNode);
        trainReverseNodeName = findOtherGoalPoint(goalPointNode);

        

}


public int findOtherGoalPoint(int goalPointNode){
if(goalPointNode == endingNodeName) return  startingNodeName;
return endingNodeName;
}

public void animateTrain(){
 
  if(trainMoving){
      moveVal += .08f;
      if(moveVal >= 1){
        moveVal = 0;
        if(trainEndNode != trainReverseNodeName){
            trainLink = findOtherSolveLink(trainEndNode,trainLink);
            trainStartNode = trainEndNode;
            trainEndNode = trainLink.getOtherNode(trainStartNode);
             
        } else {
           trainMoving = false; 
        }
      
      }

  if(!trainMoving){
   startTrain(trainReverseNodeName); 
  }
             float trainX = lerp(
              getScreenX(getNodeXFromName(trainStartNode),getNodeYFromName(trainStartNode)),
              getScreenX(getNodeXFromName(trainEndNode),getNodeYFromName(trainEndNode))
              ,moveVal);
          float trainY = lerp(
              getScreenY(getNodeXFromName(trainStartNode),getNodeYFromName(trainStartNode)),
              getScreenY(getNodeXFromName(trainEndNode),getNodeYFromName(trainEndNode))
              ,moveVal);
              
          if(oldTrainX == null){
             oldTrainX = new float[TRAINHISTORY];
            oldTrainY =  new float[TRAINHISTORY];
            for(int i = 0; i < TRAINHISTORY; i++){
                oldTrainX[i] = trainX;
                oldTrainY[i] = trainY;
            }
          } else {
             for(int i = TRAINHISTORY - 2; i >= 0; i--){
                oldTrainX[i+1] = oldTrainX[i]; 
                oldTrainY[i+1] = oldTrainY[i]; 
             }
             oldTrainX[0] = trainX;
             oldTrainY[0] = trainY;
             
            
          }
              
          fill(210,175,0);
              
          ellipse(oldTrainX[6],oldTrainY[6],scaleBase*.3f,scaleBase*.3f);
          fill(230,195,0);
          ellipse(oldTrainX[3],oldTrainY[3],scaleBase*.3f,scaleBase*.3f);
          fill(255,215,0);
          ellipse(oldTrainX[0],oldTrainY[0],scaleBase*.3f,scaleBase*.3f);
 
  
  } 
}


  float oldTrainX[];
  float oldTrainY[];
  int TRAINHISTORY = 10;

public void showHint(){
 
  Link badLink = null;
//look for something they clicked that they shouldn't have done
  for(Link k : links){
    if(k.mode == PICKED){
       if(solveLinks.get(k.name) == null){
          badLink = k;
       }
    }
  }
  if(badLink != null){
     badLink.xOut(); 
     return;
  }
  //ok, all is good, lets give them something they need to click..
   for(String s : solveLinks.keySet()){
    Link k = solveLinks.get(s);
    Link testLink = allLinkMap.get(k.name);
    if(testLink.mode != PICKED && fixedLinks.get(k.name) == null){
          badLink = testLink;
    }

  } 

  
  if(badLink != null){
     badLink.oUp(); 
     return;
  }
}

public boolean checkSolve(){
  ArrayList<Link> pickedLink = new ArrayList<Link>();
  //HashMap<String,Link> solveLinksCopy = (HashMap<String,Link>)solveLinks.clone();
  
  boolean isGoodSoFar = true;

  for(Link k : links){
    if(k.mode == PICKED || k.mode == FIXED){
      pickedLink.add(k);
      if(solveLinks.get(k.name) == null) isGoodSoFar = false;
    }
  }
  if(isGoodSoFar && pickedLink.size() == solveLinks.size()){
    return true;
  }  
   return false;
}


public void store(int[][] nodepairs,HashMap<String,Link> mapLinks){
  for(int[] pair : nodepairs){
      Link fixedLink = new Link(pair[0],pair[1]); 
     mapLinks.put(fixedLink.name,fixedLink);
  }  
}


public int getNodeName(int x, int y){
   return x + (y * COLS); 
}
public int getNodeXFromName(int n){
   return n % COLS; 
}
public int getNodeYFromName(int n){
   return n / COLS; 
}










int POSSIBLE = 1;
int FIXED = 2;
int SOLVE = 3;
int PICKED = 4;
class Link {
    int mode = POSSIBLE;
    String name;
    int x,y,ex,ey;
    
    float hoverRadius;
    

    
    Link(int px,int py, int pex, int pey){
      x = px; y = py;
      ex = pex; ey = pey;
      int n1 = getNodeName(x,y);
      int n2 = getNodeName(ex,ey);
      name = n1+"_"+n2;
      
      
      calcCenter();
    }
    
    public void checkIfFixed(HashMap<String,Link> fixedLinks){

      if(fixedLinks.get(name) != null){
        mode = FIXED; 
      }
      
    }
    public int getOtherNode(int firstNode){
      String[]parts = name.split("_");
      int partA = parseInt(parts[0]);
      int partB = parseInt(parts[1]);
      if(firstNode == partA) return partB;
      return partA;
    }
    
    Link(int pn1,int pn2){
     this(getNodeXFromName(pn1),getNodeYFromName(pn1),
         getNodeXFromName(pn2),getNodeYFromName(pn2)
     )  ;
      
    }
    public boolean matches(Link other){
     return name.equals(other.name); 
    }
    
    public void xOut(){
              cx = (getScreenX(x,y) + getScreenX(ex,ey)) / 2;
              cy = (getScreenY(x,y) + getScreenY(ex,ey)) / 2;
              strokeWeight(6);
              stroke(200,30,30);
              line(cx-15,cy-15,cx+15,cy+15);
              line(cx+15,cy-15,cx-15,cy+15);
    }
    public void oUp(){
              cx = (getScreenX(x,y) + getScreenX(ex,ey)) / 2;
              cy = (getScreenY(x,y) + getScreenY(ex,ey)) / 2;
              strokeWeight(6);
              stroke(200,30,30);
              noFill();
              ellipse(cx,cy,30,30);
    }
    
    public void draw(boolean isCurrent){
      calcCenter();
      strokeWeight(2);
      stroke(200);
      if(mode == FIXED) stroke(0);
      float p1x = getScreenX(x,y);
      float p1y = getScreenY(x,y);
      float p2x = getScreenX(ex,ey);
      float p2y = getScreenY(ex,ey);

      if(! isMouseOver(isCurrent) && mode == POSSIBLE) {
         strokeWeight(2);
        line(p1x,p1y,p2x,p2y); 
      } else {
        float alpha = 255;
        if(isMouseOver(isCurrent) && mode == POSSIBLE){
          alpha = 100;
          doHand = true;
        }
   

      pushMatrix();
      float a = atan2(p2y-p1y,p2x-p1x);
      translate(p1x,p1y);
      rotate(a);
      float full = scaleBase*.6f;
      
      if(mode == FIXED) strokeWeight(4);
      else strokeWeight(2);


      stroke(120,120,0,alpha);
      int parts = 7;
       for(int i = 1; i < parts; i++){
          float r = i* full / parts;
          line(r,-full/14,r,full/14);
       }

      if(mode == FIXED) strokeWeight(3);
      else strokeWeight(1);

      stroke(50,alpha);
      line(0,-full/20,full,-full/20);
      line(0,full/20,full,full/20);
   

       popMatrix();
      }

    }
    
    float cx, cy;
    public void calcCenter(){
      cx = (getScreenX(x,y) + getScreenX(ex,ey)) / 2;
      cy = (getScreenY(x,y) + getScreenY(ex,ey)) / 2;
      hoverRadius = (xgap + ygap) / 8;
    }
    
    public void drawCenter(){
 
      strokeWeight(1);
      stroke(0); 
      noFill();
      ellipse(cx,cy,hoverRadius*2,hoverRadius*2);
    }
    public boolean checkClick(boolean isCurrent){
      if(trainMoving) return false; //they solved, don't let them change a thing...
       if(isMouseOver(isCurrent)){
          if(mode == POSSIBLE){
            fx_scratch();
             mode = PICKED; 
             return true;
          } else {
            if(mode == PICKED){
               mode = POSSIBLE;
             return true;  
            }
          }
       } 
       return false;
    }
    public boolean isMouseOver(boolean isCurrent){
//      println("IZZY "+trainMoving);
      if(! isCurrent) return false;
      if(trainMoving) return false;
      return (dist(mouseX,mouseY,cx,cy) < hoverRadius);
         
       
    }
    
    
}



public void recalcScale(){
     xgap = xgapMUL * scaleBase; 
    ygap = ygapMUL * scaleBase; 
    nodesize = nodesizeMUL * scaleBase;
 
}





}//end puzzle class


Minim minim;

public void stop(){
  // always close Minim audio classes when you are done with them :-)
  closeSounds();
  minim.stop();
  super.stop();
}

AudioPlayer music;
AudioSnippet click;
AudioSnippet whistle;
AudioSnippet inwhoosh;
AudioSnippet outwoosh;


int SAMPLECOUNT = 4;

AudioSnippet[] scratch = new AudioSnippet[SAMPLECOUNT];

public void loadSounds(){
  minim = new Minim(this);
  music = minim.loadFile("trains_short.mp3");
 click = minim.loadSnippet("click.wav");
 whistle = minim.loadSnippet("whistle.mp3");
 inwhoosh = minim.loadSnippet("inwhoosh.mp3");
 outwoosh = minim.loadSnippet("outwoosh.mp3");
 
  for(int i = 0; i < SAMPLECOUNT; i++){
    scratch[i] = minim.loadSnippet("click.wav");  
  }
}
boolean musicPlaying;
public void startMusic(){
   music.loop();
  musicPlaying = true; 
}
public void stopMusic(){
   music.pause(); 
   music.rewind();
   musicPlaying = false;
}

public void fx_click() { fx(click); }
public void fx_whistle() { fx(whistle); }
public void fx_inwhoosh() { fx(inwhoosh); }
public void fx_outwoosh() { fx(outwoosh); }
public void fx(AudioSnippet a){
   a.pause();a.rewind();a.play(); 
}

int scratchPtr = 0;
public void fx_scratch(){
  AudioSnippet s = scratch[scratchPtr % SAMPLECOUNT];
  scratchPtr++;
  s.pause();
  s.rewind();
  s.play();
}

public void closeSounds(){
  click.close();
  whistle.close();
  inwhoosh.close();
  outwoosh.close();
  music.close();


   for(int i = 0; i < SAMPLECOUNT; i++){
    scratch[i].close();
  }
  
}




  static public void main(String args[]) {
    PApplet.main(new String[] { "--bgcolor=#F0F0F0", "microrail" });
  }
}
