/**
   * This recursive method is responsible for finding the blue styrofoam block and returning it to
   * the green/red zone. The search and detect process will begin when the robot has reached the
   * green/red zone.
   *
   * @param x The x position of where the robot will start the search for the blue styrofoam block
   * @param y The y position of where the robot will start search for the blue styrofoam block
   */
  public void doSearchAndDetect(double x, double y) { // Searching inside the green/red zone
    if (firstTry) {
      odox = odometer.getX();
      odoy = odometer.getY();
      odotheta = odometer.getTheta();
      dashboard.turnLeft(40);
      try {
        Thread.sleep(750);
      } catch (Exception e) {
      }
    } else if (Math.abs(odox - odometer.getX()) > 15 && Math.abs(odoy - odometer.getY()) > 15) {
      odox = odometer.getX();
      odoy = odometer.getY();
      odotheta = odometer.getTheta();
      dashboard.turnLeft(40);
      try {
        Thread.sleep(750);
      } catch (Exception e) {
      }
    }
    firstTry = false;
    // Initializing the heading angle when a robot detects something 35 cm away
    detectionAngle = odometer.getTheta();
    Point greenZone = new Point((int) x, (int) y); // Remembering green zone
    dashboard.turnLeft(70); // Beginning the 360 degrees scan
    boolean clawIsDown = false;

    // The boolean found never becomes true so the robot will keep spinning until
    // it finds something then moves to a different zone or it will complete 1 full
    // revolution from its starting angle then move to a different zone.
    while (!found) {
      // Condition for the robot to move to a different area to search since it has
      // completed one full revolution
      if (Math.abs(odox - odometer.getX()) < 10
          && Math.abs(odoy - odometer.getY()) < 10
          && Math.abs(odotheta - odometer.getTheta())
              < 5) { // Case where no objects in search radius
        if (directionCounter
            == 1) { // First time around, go to a point 15 cm south west of the green zone bottom
                    // left corner
          directionCounter++;
          navigation.travelTo(greenZone.getX() - 15, greenZone.getY() - 15, false);
        } else if (directionCounter
            == 2) { // Second time around, go below the middle of the green zone
          directionCounter++;
          navigation.travelTo(greenZone.getX() + 30, greenZone.getY() - 15, false);
        } else if (directionCounter == 3) { // Third time, go north easy of of the green zone
          directionCounter++;
          navigation.travelTo(greenZone.getX() + 30, greenZone.getY() + 45, false);

        } else if (directionCounter
            == 4) { // Last time around, go to the middle of the playing field
          directionCounter = 1;
          navigation.travelTo(180, 180, false);
        }
        // After traveling to the new scan zone, perform the search and scan
        doSearchAndDetect(greenZone.getX(), greenZone.getY());
      } else {
        // If the ultrasonic sensor senses something within 35 centimeters, go to the object
        if (usLow.getFilteredDistance() <= 35
            && Math.abs(odometer.getTheta() - badHeading) > 30) { // Range to go check out object
          detectionAngle = odometer.getTheta(); // Saving heading angle when the object is detected
          blockAngle = odometer.getTheta();
          previousDistance = usLow.getFilteredDistance();
          navigation.turnTo(
              detectionAngle
                  - 30.0); // Turning to past where object was detected so it can analyze the full
                           // block
          Stopwatch stopwatch = new Stopwatch();
          // Scanning whole object for 3 seconds to find shortest point of the object to robot
          while (stopwatch.elapsed() < (3 * Math.pow(10, 3))) {
            dashboard.turnRight(
                70); // Turning to the right to analyze object since it overturned initially
            if (usLow.getFilteredDistance() < previousDistance) {
              dashboard.stop();
              previousDistance = usLow.getFilteredDistance();
              blockAngle = odometer.getTheta();
            }
          }

          dashboard.stop();
          navigation.turnTo(blockAngle); // Turn to block
          // Remembering tacho count in case object is a wooden block
          // so it will back up to exactly the same spot
          originalTacho = dashboard.getRightTacho();
          Stopwatch watch = new Stopwatch();
          while (usLow.getFilteredDistance() >= 10) {
            // If robot hasn't reached object in 8 seconds, most likely nothing there and false
            // positive
            if (watch.elapsed() > 9 * Math.pow(10, 3)) {
              badHeading = odometer.getTheta();
              newTacho = dashboard.getRightTacho();
              dashboard.rotateMotor(-(newTacho - originalTacho), -(newTacho - originalTacho), 200);
              dashboard.stop();
              doSearchAndDetect(greenZone.getX(), greenZone.getY());
            }
            // Travel to the object by going forward
            dashboard.goForward(200);
          }
          // Making a check for a wooden block by using the top ultrasonic sensor
          if (usHigh.getFilteredDistance()
              < 25) { // Brown Block so back up distance traveled to the block
            dashboard.stop();
            badHeading =
                odometer
                    .getTheta(); // Saving the angle of the wooden block so it doesn't go back when
                                 // it sees it again
            newTacho = dashboard.getRightTacho();
            dashboard.rotateMotor(-(newTacho - originalTacho), -(newTacho - originalTacho), 200);
            dashboard.stop();
            doSearchAndDetect(greenZone.getX(), greenZone.getY());
          } else { // Go forward a bit more to get closer to the styrofoam block
            for (int i = 0; i < 200; i++) {
              dashboard.goForward(70);
            }
          }
          // 2nd check for brown block to make sure when closer to object
          if (usHigh.getFilteredDistance() < 25) {
            dashboard.stop();
            badHeading = odometer.getTheta();
            newTacho = dashboard.getRightTacho();
            dashboard.rotateMotor(-(newTacho - originalTacho), -(newTacho - originalTacho), 200);
            dashboard.stop();
            doSearchAndDetect(greenZone.getX(), greenZone.getY());
          } else { // Return to dropzone then drop off and re-do scan
            newTacho = dashboard.getRightTacho();
            // If doing search but the block is in the green/red zone, don't pick it up again
            if ((odometer.getX() > greenZone.getX() && odometer.getY() > greenZone.getY())
                && (odometer.getX() < greenZone.getX() + 30
                    && odometer.getY() < greenZone.getY() + 30)) {
              badHeading = odometer.getTheta();
              dashboard.rotateMotor(-(newTacho - originalTacho), -(newTacho - originalTacho), 200);
              dashboard.stop();
              doSearchAndDetect(greenZone.getX(), greenZone.getY());
            }
            dashboard.stop();
            // Reached a good distance from the styrofoam block so back up
            // and get ready for the juggle.
            Stopwatch secondWatch = new Stopwatch();
            while (secondWatch.elapsed() < 3 * Math.pow(10, 3)) {
              dashboard.goBackward(100);
            }
            dashboard.stop();
            if (!clawIsDown) {
              // Arms are currently up so drop them since the block is
              // detected and close by
              lifter.dropClaw();
              clawIsDown = true;
            }
            // Driving forward until the block is within 7 cm
            while (usLow.getFilteredDistance() >= 7) {
              dashboard.goForward(100);
            }
            juggle(); // Juggle to reposition the block so it is easier to pick
            dashboard.stop();
            // Drive forward 8 cm so the block will be picked up perfectly
            dashboard.goForward(8, false);
            // Remember where the block was picked up so the robot
            // will return here after dropping off in the designated zone
            previousPickUp.setLocation(odometer.getX(), odometer.getY());
            dashboard.stop();
            lifter.lift(); // Picking up the block
            isCarrying = true;
            // Driving back to the green zone (a bit further to account) for the arms
            // and at this distance, the block is dropped off in the middle of the zone
            navigation.travelTo(greenZone.getX() + 45, greenZone.getY() + 45, false);
            // If it is the nth*2 time, robot will stack.
            if (!firstIteration) {
              lifter.stackLift();
            }
            navigation.turnTo(180); // Turning so the block will end up in the middle of the zone
            dashboard.goForward(
                2,
                false); // Going forward 2 cm so even closer to block (used for stacking purposes)
            lifter.dropBlock();
            Stopwatch secondStop = new Stopwatch();
            // After dropping it off, back up then lift arms
            while (secondStop.elapsed() < 3.5 * Math.pow(10, 3)) {
              dashboard.goBackward(100);
            }
            dashboard.stop();
            lifter.postLift();
            firstIteration = !firstIteration; // Only making stacks of 2 so reverse boolean
            navigation.travelTo(
                greenZone.getX() - 15,
                greenZone.getY(),
                false); // Travel to side of green zone so blocks are not touched when navigating
                        // out of the zone
            navigation.travelTo(
                previousPickUp.getX(),
                previousPickUp.getY(),
                false); // Return to previous position of where styrofoam block was found
            doSearchAndDetect(
                greenZone.getX(), greenZone.getY()); // Do search and detect at the new position
          }
        }
      }
    }
  }
  public static void main(String[] args) {

    UltrasonicPoller frontPoller = new UltrasonicPoller("S4");
    UltrasonicPoller sidePoller = new UltrasonicPoller("S1");
    ColorSensorPoller blockPoller = new ColorSensorPoller("S2");
    ColorSensorPoller groundPoller = new ColorSensorPoller("S3");
    groundPoller.setMode(1);
    // start the block detector thread, which will be constantly checking with the light sensor
    // to see if there is a block.
    boolean wifiWorked = true;

    // *********************************WiFi module************************************//
    // Set up WiFi connection, require data from server, parse data and disconnect from server.
    WifiConnection conn = null;
    Transmission t = null;
    try {
      conn = new WifiConnection(SERVER_IP, TEAM_NUMBER);
    } catch (IOException e) {
      LCD.drawString("Connection failed", 0, 1);
      wifiWorked = false;
    }
    if (conn == null) {
      LCD.drawString("Unable to find Server", 0, 5);
      wifiWorked = false;
    } else {
      // Data received from the server is saved in "t".
      // Pass the data saved in t to the relevant class
      t = conn.getTransmission();
      // Display the data in t
      if (t == null) {
        LCD.drawString("Failed to read transmission", 0, 5);
        wifiWorked = false;
      } else {
        conn.printTransmission();
      }
      // Button.waitForAnyPress();
      LCD.clear();
    }
    // ********************************WiFi module ends******************************//

    if (wifiWorked) {
      // variables from WIFI
      int flagType = t.flagType;
      StartCorner startingCorner = t.startingCorner;
      int bottomLeftX = t.opponentHomeZoneBL_X;
      int bottomLeftY = t.opponentHomeZoneBL_Y;
      int topRightX = t.opponentHomeZoneTR_X;
      int topRightY = t.opponentHomeZoneTR_Y;
      int capturePointX = t.dropZone_X;
      int capturePointY = t.dropZone_Y;
      // variables HARDCODED
      //			int flagType = 1;
      //			StartCorner startingCorner = StartCorner.TOP_RIGHT;
      //			int bottomLeftX = 3;
      //			int bottomLeftY = 3;
      //			int topRightX = 5;
      //			int topRightY = 5;
      //			int capturePointX = 1;
      //			int capturePointY = 1;

      // ***********************Initialization Module******************************//
      // setup the odometer
      Odometer odo = new Odometer(leftMotor, rightMotor, 30, true);
      OdometerCorrector odoCorr = new OdometerCorrector(odo, groundPoller);

      // setup the wall avoider
      WallAvoider avoider = new WallAvoider(odo, frontPoller, sidePoller);

      // set up the navigator
      Navigation navi = new Navigation(odo, avoider, frontPoller, WHEEL_RADIUS, TRACK);

      // set up the detector
      BlockDetector blockDetector =
          new BlockDetector(
              blockPoller, navi, odo, frontPoller, verticalArmMotor, horizontalArmMotor, flagType);
      blockDetector.start();
      // set up the searcher
      BlockZoneSearcher flagSearcher =
          new BlockZoneSearcher(sidePoller, frontPoller, navi, odo, blockDetector);
      // set up the LCD
      LCDDisplay lcd = new LCDDisplay(odo, frontPoller, sidePoller, blockPoller, blockDetector);
      lcd.start();

      // set up the localization
      LightLocalizer lsl =
          new LightLocalizer(
              odo, groundPoller, navi, ROBOT_CENTRE_TO_LIGHTLOCALIZATION_SENSOR, startingCorner);
      USLocalizer usl =
          new USLocalizer(odo, navi, frontPoller, USLocalizer.LocalizationType.FULL_CIRCLE);
      // ***********************End of Initialization******************************//

      double angleForSearch;
      String searchDirection = "";
      int searchStartX;
      int searchStartY;
      int firstCornerX;
      int firstCornerY;
      double zoneBuffer;
      // we use our starting corner to determine where we want to travel to in order to search.
      if (startingCorner == StartCorner.BOTTOM_LEFT || startingCorner == StartCorner.BOTTOM_RIGHT) {
        searchDirection = "down";
        searchStartX = bottomLeftX;
        searchStartY = topRightY;
        firstCornerX = bottomLeftX;
        firstCornerY = bottomLeftY;
        zoneBuffer = -1 * TILE_WIDTH / 3.0;
        angleForSearch = 270;

      } else { // it is the top left or top right
        searchDirection = "up";
        searchStartX = topRightX;
        searchStartY = bottomLeftY;
        firstCornerX = topRightX;
        firstCornerY = topRightY;
        zoneBuffer = TILE_WIDTH / 3.0;
        angleForSearch = 90;
      }

      /*
       * Step 1: Ultrasonic Localization
       * 	Figure out where we are, roughly
       */
      // disable the side sensor for localization so that it doesn't interfere
      sidePoller.disableSensor();
      // set the errors on the navigation to be large for US localization
      navi.setCmError(0.5);
      navi.setDegreeError(4.0);
      // perform ultra-sonic localization
      usl.doLocalization();

      /*
       * Step 2: Light Localization
       * 	Figure out where we are, precisely
       */
      // set the errors back to smaller values
      navi.setCmError(0.5);
      navi.setDegreeError(2.0);
      // perform light-sensor localization
      lsl.doLocalization();

      /*
       * Step 3: Travel to first corner
       * 	Travel to the corner of the block zone in which we are going to relocalize
       */
      // enable the side poller for navigating
      sidePoller.enableSensor();
      // speed up the robot for navigation
      navi.setSlowSpeed(90);
      navi.setFastSpeed(160);
      // start odometry correction
      odoCorr.start();
      // navigation
      navi.travelToAndAvoid(TILE_WIDTH * firstCornerX, TILE_WIDTH * firstCornerY);

      /*
       * Step 4: Relocalize
       * 	Use the lines to relocalize (figure out where we are, again)
       */
      // perform second localization
      lsl.doRelocalization(TILE_WIDTH * firstCornerX, TILE_WIDTH * firstCornerY);

      /*
       * Step 5: Travel to second corner & rotate
       * 	Travel to the top (or bottom) corner of the block zone, and turn to searching position
       */
      // we first travel a bit away from the zone
      navi.travelTo(TILE_WIDTH * firstCornerX + zoneBuffer, TILE_WIDTH * firstCornerY + zoneBuffer);
      // we travel to the second corner with no avoidance (there can't be any blocks there, anyway)
      navi.travelTo(TILE_WIDTH * searchStartX + zoneBuffer, TILE_WIDTH * searchStartY);
      navi.turnTo(angleForSearch, true);

      /*
       * Step 6: Search for block
       * 	Start to search around the perimeter for the block
       */
      // lower errors
      navi.setCmError(0.4);
      navi.setDegreeError(3);
      // find dimensions of place to search
      int zoneWidth = topRightX - bottomLeftX;
      int zoneHeight = topRightY - bottomLeftY;
      // run searcher
      flagSearcher.searchZone(searchStartX, searchStartY, zoneWidth, zoneHeight, searchDirection);

      /*
       * Step 7: Drive to end position
       * 	We now have the block. Drive to the final position
       */
      if (blockDetector.isFlag()) {
        // we want to travel to the center of the blocks.
        navi.travelToAndAvoid(
            capturePointX * TILE_WIDTH + TILE_WIDTH / 2,
            capturePointY * TILE_WIDTH + TILE_WIDTH / 2); // wifi
      }

      while (Button.waitForAnyPress() != Button.ID_ESCAPE) ;
      System.exit(0);
    }
  }