/**
   * -----+-oo- ^ | ^ | | | Dist To Surface | | v | -+---------------- RSH | Oil Thickness |
   * -+---------------- | | ^ | | | Water Thickness v | v =====+================ RSH: RangeSensor
   * Height
   */
  private static void businessLogic(double waterThickness, double oilThickness) {
    //  log(">>> In BusinessLogic (" + waterLevel + ", " + oilLevel + ")");

    //  currentWaterLevel   = Math.round(waterLevel);
    oilThicknessValues.add(oilThickness);

    //  double smoothedOilValue = -1D;

    //  System.out.println("Business Logic - Water:" + waterLevel + ", oil:" + oilLevel);
    if (oilThicknessValues.size() >= windowWidth) {
      while (oilThicknessValues.size() > windowWidth) oilThicknessValues.remove(0);
      currentOilThickness = smoothOilThickness();
    }
    if (oilThicknessValues.size() >= windowWidth && currentOilThickness > 0) {
      //    log("Oil thick:" + oilThickness + ", Water:" + waterLevel);
      if (waterThickness <= 0.1 && currentOilThickness > 0.25) {
        log(
            "       >>>>>>>>>>>>>>>>>>>>>>>>>> Shutting OFF !!!! <<<<<<<<<<<<<<<<<<<<< W:"
                + waterThickness
                + ", O:"
                + currentOilThickness);
        // Switch the relay off?
        RelayManager.RelayState status = rm.getStatus("00");
        // log("Relay is:" + status);
        if (RelayManager.RelayState.ON.equals(status)) {
          log("Turning relay off!");
          try {
            rm.set("00", RelayManager.RelayState.OFF);
          } catch (Exception ex) {
            System.err.println(ex.toString());
          }
        }
        if (currentStatus.equals(ProcessStatus.ALL_OK)) {
          log("Oil thick:" + currentOilThickness + ", Water:" + waterThickness + " ");
          // Make a call
          String[] mess = {
            "Oil in the bilge of " + boatName + ": " + DF23.format(currentOilThickness) + " cm.",
            "Please reply CLEAN to this message when done with it."
          };
          //  String mess = "First warning to " + boatName;

          displayAppMess(
              " >>>>>>>>>> CALLING "
                  + phoneNumber_1); // + "Mess is a " + mess.getClass().getName() + "\n" + mess);
          sendSMS(phoneNumber_1, mess);
          currentStatus = ProcessStatus.MESSAGE_SENT_TO_CAPTAIN;
          WaitForCleanThread wfct = new WaitForCleanThread();
          wfct.start();
        }
      } else {
        System.out.println("                            ");
        System.out.println("                            ");
      }
    }
  }
  @Override
  public void message(ReadWriteFONA.SMS sms) {
    log("\nReceived messsage:");
    log("From:" + sms.getFrom());
    log(sms.getContent());
    if (sms.getContent().trim().equalsIgnoreCase("CLEAN")
        && (SIMULATOR.equals(sms.getFrom())
            || sms.getFrom().contains(phoneNumber_1)
            || sms.getFrom().contains(phoneNumber_2)
            || sms.getFrom().contains(phoneNumber_3))) {
      // Check, and Resume if OK
      if (currentOilThickness <= 0) {
        // Tell whoever has been warned so far
        boolean[] messLevel = {false, false, false};
        messLevel[SENT_TO_CAPTAIN] =
            (currentStatus == ProcessStatus.MESSAGE_SENT_TO_CAPTAIN
                || currentStatus == ProcessStatus.MESSAGE_SENT_TO_OWNER
                || currentStatus == ProcessStatus.MESSAGE_SENT_TO_AUTHORITIES);
        messLevel[SENT_TO_OWNER] =
            (currentStatus == ProcessStatus.MESSAGE_SENT_TO_OWNER
                || currentStatus == ProcessStatus.MESSAGE_SENT_TO_AUTHORITIES);
        messLevel[SENT_TO_AUTHORITIES] =
            (currentStatus == ProcessStatus.MESSAGE_SENT_TO_AUTHORITIES);
        String[] mess = {
          "Oil in the bilge of " + boatName + " has been cleaned",
          "Bilge pump can be used as before."
        };
        //  String mess =  "Oil in the bilge of " + boatName + " has been cleaned.";
        if (messLevel[SENT_TO_AUTHORITIES]) sendSMS(phoneNumber_3, mess);
        if (messLevel[SENT_TO_OWNER]) sendSMS(phoneNumber_2, mess);
        if (messLevel[SENT_TO_CAPTAIN]) sendSMS(phoneNumber_1, mess);

        currentStatus = ProcessStatus.ALL_OK;
        RelayManager.RelayState status = rm.getStatus("00");
        // log("Relay is:" + status);
        if (RelayManager.RelayState.OFF.equals(status)) {
          log("Turning relay back on as needed.");
          try {
            rm.set("00", RelayManager.RelayState.ON);
          } catch (Exception ex) {
            System.err.println(ex.toString());
          }
        }
      } else {
        // Reply, not clean enough.
        String[] mess = {
          "Sorry, the bilge is not clean enough.",
          "Try again to send a CLEAN message when this has been taken care of"
        };
        // String mess = "Not clean enough";
        sendSMS(phoneNumber_1, mess);
      }
    }
  }
 @Override
 public void onButtonPressed() {
   log(">>> Reset button has been pressed.");
   RelayManager.RelayState status = rm.getStatus("00");
   // log("Relay is:" + status);
   if (RelayManager.RelayState.OFF.equals(status)) {
     log("Turning relay back on.");
     try {
       rm.set("00", RelayManager.RelayState.ON);
     } catch (Exception ex) {
       System.err.println(ex.toString());
     }
   }
 }
  public static void main(String[] args) throws Exception {
    System.out.println(args.length + " parameter(s).");
    if (args.length > 0) {
      if (args[0].equals("-cal")) {
        calibration = true;
        ansiConsole = false;
      }
    }

    LelandPrototype lp = new LelandPrototype();

    if (!calibration) {
      props = new Properties();
      try {
        props.load(new FileInputStream("props.properties"));
      } catch (IOException ioe) {
        displayAppErr(ioe);
        //  ioe.printStackTrace();
      }

      try {
        windowWidth =
            Integer.parseInt(
                LelandPrototype.getAppProperties()
                    .getProperty("smooth.width", "10")); // For smoothing
        alfa =
            Double.parseDouble(
                LelandPrototype.getAppProperties().getProperty("low.pass.filter.alfa", "0.5"));
      } catch (NumberFormatException nfe) {
        nfe.printStackTrace();
      }

      try {
        cleaningDelay =
            Long.parseLong(props.getProperty("cleaning.delay", "86400")); // Default: one day
      } catch (NumberFormatException nfe) {
        nfe.printStackTrace();
      }

      try {
        nbSeenInARow = Integer.parseInt(props.getProperty("seen.in.a.row", "40"));
      } catch (NumberFormatException nfe) {
        nfe.printStackTrace();
      }

      try {
        rangeSensorHeight = Double.parseDouble(props.getProperty("range.sensor.height", "10"));
      } catch (NumberFormatException nfe) {
        nfe.printStackTrace();
      }

      try {
        fileLogger = new BufferedWriter(new FileWriter(LOG_FILE));
      } catch (Exception ex) {
        ex.printStackTrace();
      }

      if (ansiConsole) AnsiConsole.systemInstall();

      final ReadWriteFONA fona;

      if ("true".equals(props.getProperty("with.fona", "false"))) {
        System.setProperty("baud.rate", props.getProperty("baud.rate"));
        System.setProperty("serial.port", props.getProperty("serial.port"));

        fona = new ReadWriteFONA(lp);
        fona.openSerialInput();
        fona.startListening();
        while (!fonaReady) {
          System.out.println("Waiting for the FONA device to come up...");
          try {
            Thread.sleep(1000L);
          } catch (InterruptedException ie) {
          }
        }
        fona.requestBatteryState();
        fona.requestNetworkStatus();
        displayAppMess(">>> FONA Ready, moving on");
        smsProvider = fona;
      } else {
        System.out.println("Will simulate the phone calls.");
      }
      delay(1);

      wsUri = props.getProperty("ws.uri", "");
      phoneNumber_1 = props.getProperty("phone.number.1", "14153505547");
      phoneNumber_2 = props.getProperty("phone.number.2", "14153505547");
      phoneNumber_3 = props.getProperty("phone.number.3", "14153505547");
      boatName = props.getProperty("boat.name", "Never Again XXIII");

      try {
        rm = new RelayManager();
        rm.set("00", RelayManager.RelayState.ON);
      } catch (Exception ex) {
        System.err.println("You're not on the PI, hey?");
        ex.printStackTrace();
      }

      if (wsUri.trim().startsWith("ws://")) {
        log(">>> Connecting to the WebSocket server [" + wsUri + "]");
        initWebSocketConnection(wsUri);
      } else {
        log(">>> No WebSocket server");
        delay(1);
      }
    }

    final SevenADCChannelsManager sacm = (calibration ? null : new SevenADCChannelsManager(lp));
    final SurfaceDistanceManager sdm = new SurfaceDistanceManager(lp);
    sdm.startListening();

    final Thread me = Thread.currentThread();

    Runtime.getRuntime()
        .addShutdownHook(
            new Thread() {
              public void run() {
                // Cleanup
                System.out.println();
                if (sacm != null) sacm.quit();
                if (smsProvider != null) smsProvider.closeChannel();
                synchronized (me) {
                  me.notify();
                }
                gpio.shutdown();
                if (channelLogger != null) {
                  try {
                    for (BufferedWriter bw : channelLogger) {
                      bw.close();
                    }
                  } catch (Exception ex) {
                    ex.printStackTrace();
                  }
                }
                System.out.println("Program stopped by user's request.");
              }
            });

    if (!calibration) {
      if ("true".equals(props.getProperty("log.channels", "false"))) {
        channelLogger = new BufferedWriter[SevenADCChannelsManager.getChannel().length];
        for (int i = 0; i < SevenADCChannelsManager.getChannel().length; i++) {
          channelLogger[i] =
              new BufferedWriter(
                  new FileWriter(CHANNEL_PREFIX + CHANNEL_NF.format(i) + CHANNEL_SUFFIX));
        }
      }

      // CLS
      if (ansiConsole) AnsiConsole.out.println(EscapeSeq.ANSI_CLS);
    }
    synchronized (me) {
      System.out.println("Main thread waiting...");
      me.wait();
    }
    System.out.println("Done.");
  }