// convert screen coordinates of mouse into universe coordinates (may be different due to mouse factor)
float AbsMouseX(float mx) {
      return (mx - ds.zoomTranslateX)/ds.zoomFactor;
}

// convert screen coordinates of mouse into universe coordinates (may be different due to mouse factor)
float AbsMouseY(float my) {
      return (my - ds.zoomTranslateY)/ds.zoomFactor;
}

float mouseXMark = -1;
float mouseYMark = -1;
boolean multiSelecting = false;
boolean dragging = false;
int dragId = -1;
int pressTime;
int bondSphereId = -1;
boolean addingBond = false;
boolean deletingBond = false;

void mousePressed() {
  pressTime = millis();
  if (!runningSimulation) { return; }
  if (mouseButton == LEFT) { // && !movieMode
    if (keyPressed && " ".equals(str(key)) && !deletingBond) {
      SphereList spheres = currentState.getSpheres();
      double[] closestOutput = spheres.findClosest(AbsMouseX(mouseX), AbsMouseY(mouseY));
      double closestD = closestOutput[1];
      if (closestD <= ds.radiusShowMul * ff.radius) {
        int closestId = (int) closestOutput[0];
        if (!addingBond) {
          bondSphereId = closestId;
          addingBond = true;
          
        } else { // Bond spheres
          Sphere2D s1 = spheres.get(bondSphereId);
          addingBond = false;
          bondSphereId = -1;
          saveStateForUndo();
          setNewPair(s1, spheres.get(closestId), "cWW", currentState.sim);
        }
      } else {
        addingBond = false;
        bondSphereId = -1;
      }
    
    } else if (!menuVisible) {
      SphereList spheres = currentState.getSpheres();
      double[] closestOutput = spheres.findClosest(AbsMouseX(mouseX), AbsMouseY(mouseY));
      int closest = (int) closestOutput[0];
      double closestD = closestOutput[1];
      if (closestD <= ff.radius * ds.radiusShowMul) {
        if (attachedIds.containsKey(closest)) {
          dragging = true;
          dragId = closest;
        }
      }
      if (!dragging) {
        multiSelecting = true;
        mouseXMark = AbsMouseX(mouseX);
        mouseYMark = AbsMouseY(mouseY);
      }
    }
  }
  
  if (programLaunch) {
    programLaunch = false;
  }
}

void mouseDragged() {
  if (!runningSimulation) { return; }
  if (dragging) {
    SphereList spheres = currentState.getSpheres();
    double dx = AbsMouseX(mouseX) - spheres.get(dragId).x;
    double dy = AbsMouseY(mouseY) - spheres.get(dragId).y;
    for (Iterator<Integer> it = attachedIds.keySet().iterator(); it.hasNext(); ) {
      Sphere2D currentSphere = spheres.get(it.next());
      currentSphere.x += dx;
      currentSphere.y += dy;
    }
  }
  
  /*
  // Guido replacement code
  if (menuVisible) {
    for (Slider slider : sliders) {
      if (slider.x <= AbsMouseX(mouseX) && AbsMouseX(mouseX) <= slider.x+slider.width &&
          slider.y <= AbsMouseY(mouseY) && AbsMouseY(mouseY) <= slider.y+slider.height) {
        slider.dragged();
      }
    }
  }
  */
}

void mouseReleased() {
  int elapsedTime = millis() - pressTime;
  if (runningSimulation) {
    if (dragging) {
      dragging = false;
      saveStateForUndo();
    
    } else if (multiSelecting) {
      multiSelecting = false;
      //if (!movieMode) {
        if ( !(keyPressed && key == CODED && (keyCode == SHIFT || keyCode == ALT)) ) {
          attachedIds.clear();
        }
        
        SphereList spheres = currentState.getSpheres();
        float mouseXMark2 = AbsMouseX(mouseX);
        float mouseYMark2 = AbsMouseY(mouseY);
        
        // Make sure first mark is less than the second for the selection check:
        if (mouseXMark2 < mouseXMark) {
          float help = mouseXMark;
          mouseXMark = mouseXMark2;
          mouseXMark2 = help;
        }
        if (mouseYMark2 < mouseYMark) {
          float help = mouseYMark;
          mouseYMark = mouseYMark2;
          mouseYMark2 = help;
        }
    
        for (int i = 0; i < spheres.size(); ++i) {
          double x = spheres.get(i).x;
          double y = spheres.get(i).y;
          if ((x >= mouseXMark) && (x <= mouseXMark2) && (y >= mouseYMark) && (y <= mouseYMark2) && !attachedIds.containsKey(i)) {
            attachedIds.put(i, 1); // The mapped value "1" is a dummy placeholder; workaround to use HashMap instead of Set
            
            if (!(keyPressed && key == CODED && keyCode == ALT)) { // alt clicking target selects
              Sphere2D sphere = spheres.get(i);
              if (sphere.pairedWForce != null) {
                Sphere2D s2 = sphere;
                while (s2.hasHelixDown) {
                  s2 = s2.threeP;
                  if (!attachedIds.containsKey(s2.id)) {
                    attachedIds.put(s2.id, 1);
                  }
                }
                s2 = sphere;
                while (s2.hasHelixUp) {
                  s2 = s2.fiveP;
                  if (!attachedIds.containsKey(s2.id)) {
                    attachedIds.put(s2.id, 1);
                  }
                }
              }
            }
          }
        }
      //}
    }
    
    for (Slider slider : sliders) {
      slider.isPressed = false;
    }
  }
  if (elapsedTime < 170) { // make an adjustable preference?
    mouseClickedFunction();
  }
}

void mouseClickedFunction() {
  if (runningSimulation && mouseButton == LEFT && !menuVisible && !addingBond) { // && !movieMode
    
    SphereList spheres = currentState.getSpheres();
    double[] closestOutput = spheres.findClosest(AbsMouseX(mouseX), AbsMouseY(mouseY));
    int closest = (int) closestOutput[0];
    double closestD = closestOutput[1];
    
    if (deletingBond) {
      if (closestD <= ds.radiusShowMul * ff.radius) { // Deletebond
        saveStateForUndo();
        Sphere2D sphereD = spheres.get(closest);
        
        int pairId = currentState.sim.pairTable[sphereD.id];
        if (pairId >= 0) {
          currentState.sim.pairTable[sphereD.id] = -1;
          currentState.sim.pairTable[pairId] = -1;
        }
        
        unsetForcePair(sphereD, sphereD.pairedWForce);
        
        for (int i = 0; i < sphereD.bonds.length; ++i) {
          String bond = sphereD.bonds[i];
          if (bond != null && !"".equals(bond)) {
            sphereD.bonds[i] = null;
            spheres.get(i).bonds[closest] = null;
            
            String ncKey = closest < i ? closest + " " + i : i + " " + closest;
            currentState.sim.nonCanonicals.remove(ncKey);
          }
        }
      }
    } else if ( (attachedIds.size() == 0 && closestD <= 15) || (closestD <= ds.radiusShowMul * ff.radius) ) { // add residue
      Sphere2D sphere = spheres.get(closest);
      
      if (attachedIds.containsKey(closest) &&
          (keyPressed && key == CODED && (keyCode == SHIFT || keyCode == ALT))) { // deselect if shift/alt-clicking an already selected residue
        attachedIds.remove(closest);
        
        if (!(keyPressed && key == CODED && keyCode == ALT)) { // alt-clicking deselects only clicked on residue
          if (sphere.pairedWForce != null) { // Watson-Crick paired
            Sphere2D s2 = sphere;
            while (s2.hasHelixDown) {
              s2 = s2.threeP;
              if (attachedIds.containsKey(s2.id)) {
                attachedIds.remove(s2.id);
              }
            }
            
            // Check here to deselect dangly ends as well
            
            s2 = sphere;
            while (s2.hasHelixUp) {
              s2 = s2.fiveP;
              if (attachedIds.containsKey(s2.id)) {
                attachedIds.remove(s2.id);
              }
            }
          }
        }
      } else { // Select
        
        if ( !(keyPressed && key == CODED && (keyCode == SHIFT || keyCode == ALT)) ) {
          attachedIds.clear();
        }
        
        attachedIds.put(closest, 1); // 1 is a dummy value because we are using HashMap as a Set due to Processing's lack of support for Set
        
        if (!(keyPressed && key == CODED && keyCode == ALT)) { // alt-clicking selects only clicked on residue
          if (sphere.pairedWForce != null) { // Watson-Crick paired
            Sphere2D s2 = sphere;
            while (s2.hasHelixDown) {
              s2 = s2.threeP;
              if (!attachedIds.containsKey(s2.id)) {
                attachedIds.put(s2.id, 1);
              }
              if (!attachedIds.containsKey(s2.pairedWForce.id)) {
                attachedIds.put(s2.pairedWForce.id, 1);
              }
            }
            
            s2 = sphere;
            while (s2.hasHelixUp) {
              s2 = s2.fiveP;
              if (!attachedIds.containsKey(s2.id)) {
                attachedIds.put(s2.id, 1);
              }
              if (!attachedIds.containsKey(s2.pairedWForce.id)) {
                attachedIds.put(s2.pairedWForce.id, 1);
              }
            }
            
            if (!attachedIds.containsKey(sphere.pairedWForce.id)) {
              attachedIds.put(sphere.pairedWForce.id, 1);
            }
          }
        }
      }
    } else if (!(keyPressed && key == CODED && keyCode == SHIFT)) {
      attachedIds.clear();
    }
  }
}