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 = .6;
float ygapMUL = .5;
float nodesizeMUL = .2;
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
  

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;
}

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;
   
   void draw(){
     
     if(zooming > 0){
         zooming-=.075;
         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);
      }

      
      
   }
  
   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;
   } 
  
  
  
  
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++]);
 } 

}


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);          
       }
     }
   }
}

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("");
   } 
}


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;
}
float getScreenY(float x, float y){
  float loc = centerY - ((ygap * ROWS)/2);
  loc += y * ygap +(ygap/2);
  return loc;
}

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);
     } 
       
     }
 }

}

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;
}


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 ");      
  
}


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

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;

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;
   }
}
void startTrain(int goalPointNode){
        trainLink = findSolveLinkForNode(goalPointNode);
        moveVal = 0;
        trainMoving = true;
        trainStartNode = goalPointNode;
        trainEndNode = trainLink.getOtherNode(trainStartNode);
        trainReverseNodeName = findOtherGoalPoint(goalPointNode);

        

}


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

void animateTrain(){
 
  if(trainMoving){
      moveVal += .08;
      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*.3,scaleBase*.3);
          fill(230,195,0);
          ellipse(oldTrainX[3],oldTrainY[3],scaleBase*.3,scaleBase*.3);
          fill(255,215,0);
          ellipse(oldTrainX[0],oldTrainY[0],scaleBase*.3,scaleBase*.3);
 
  
  } 
}


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

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;
  }
}

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;
}


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);
  }  
}


int getNodeName(int x, int y){
   return x + (y * COLS); 
}
int getNodeXFromName(int n){
   return n % COLS; 
}
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();
    }
    
    void checkIfFixed(HashMap<String,Link> fixedLinks){

      if(fixedLinks.get(name) != null){
        mode = FIXED; 
      }
      
    }
    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)
     )  ;
      
    }
    boolean matches(Link other){
     return name.equals(other.name); 
    }
    
    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);
    }
    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);
    }
    
    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*.6;
      
      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;
    void calcCenter(){
      cx = (getScreenX(x,y) + getScreenX(ex,ey)) / 2;
      cy = (getScreenY(x,y) + getScreenY(ex,ey)) / 2;
      hoverRadius = (xgap + ygap) / 8;
    }
    
    void drawCenter(){
 
      strokeWeight(1);
      stroke(0); 
      noFill();
      ellipse(cx,cy,hoverRadius*2,hoverRadius*2);
    }
    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;
    }
    boolean isMouseOver(boolean isCurrent){
//      println("IZZY "+trainMoving);
      if(! isCurrent) return false;
      if(trainMoving) return false;
      return (dist(mouseX,mouseY,cx,cy) < hoverRadius);
         
       
    }
    
    
}



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





}//end puzzle class