// -*- mode: java; c-basic-offset: 2; -*-
// Copyright 2016 MIT, All rights reserved
// Released under the Apache License, Version 2.0
// http://www.apache.org/licenses/LICENSE-2.0

package com.google.appinventor.components.runtime;

import com.google.appinventor.components.annotations.DesignerComponent;
import com.google.appinventor.components.annotations.DesignerProperty;
import com.google.appinventor.components.annotations.PropertyCategory;
import com.google.appinventor.components.annotations.SimpleEvent;
import com.google.appinventor.components.annotations.SimpleFunction;
import com.google.appinventor.components.annotations.SimpleObject;
import com.google.appinventor.components.annotations.SimpleProperty;
import com.google.appinventor.components.common.ComponentCategory;
import com.google.appinventor.components.common.PropertyTypeConstants;
import com.google.appinventor.components.common.YaVersion;
import com.google.appinventor.components.runtime.util.ErrorMessages;
import android.os.Handler;

/**
 * ![EV3 component icon](images/legoMindstormsEv3.png)
 *
 * A component that provides a high-level interface to a gyro sensor on a LEGO
 * MINDSTORMS EV3 robot.
 *
 * @author jerry73204@gmail.com (jerry73204)
 * @author spaded06543@gmail.com (Alvin Chang)
 */
@DesignerComponent(version = YaVersion.EV3_GYROSENSOR_COMPONENT_VERSION,
                   description = "A component that provides a high-level interface to a gyro sensor on a " +
                                 "LEGO MINDSTORMS EV3 robot.",
                   category = ComponentCategory.LEGOMINDSTORMS,
                   nonVisible = true,
                   iconName = "images/legoMindstormsEv3.png")
@SimpleObject
public class Ev3GyroSensor extends LegoMindstormsEv3Sensor implements Deleteable {
  private static final int DELAY_MILLISECONDS = 50;
  private static final int SENSOR_TYPE = 32;
  private static final int SENSOR_MODE_ANGLE = 0;
  private static final int SENSOR_MODE_RATE = 1;
  private static final String SENSOR_MODE_ANGLE_STRING = "angle";
  private static final String SENSOR_MODE_RATE_STRING = "rate";
  private static final String DEFAULT_SENSOR_MODE_STRING = SENSOR_MODE_ANGLE_STRING;

  private Handler eventHandler;
  private final Runnable sensorValueChecker;
  private int mode = SENSOR_MODE_ANGLE;
  private String modeString = SENSOR_MODE_ANGLE_STRING;
  private double previousValue = -1.0;
  private boolean sensorValueChangedEventEnabled = false;

  /**
   * Creates a new Ev3GyroSensor component.
   */
  public Ev3GyroSensor(ComponentContainer container) {
    super(container, "Ev3GyroSensor");

    eventHandler = new Handler();
    sensorValueChecker = new Runnable() {
      public void run() {
        String functionName = "";

        if (bluetooth != null && bluetooth.IsConnected()) {
          double currentValue = getSensorValue(functionName);

          if (previousValue < 0.0) {
            previousValue = currentValue;
            eventHandler.postDelayed(this, DELAY_MILLISECONDS);
            return;
          }

          // trigger events according to the conditions
          if (mode == SENSOR_MODE_RATE && Math.abs(currentValue) >= 1.0)
            SensorValueChanged(currentValue);
          else if (mode == SENSOR_MODE_ANGLE && Math.abs(currentValue - previousValue) >= 1.0)
            SensorValueChanged(currentValue);

          previousValue = currentValue;
        }

        eventHandler.postDelayed(this, DELAY_MILLISECONDS);
      }
    };
    eventHandler.post(sensorValueChecker);

    Mode(DEFAULT_SENSOR_MODE_STRING);
    SensorValueChangedEventEnabled(false);
  }

  /**
   * Returns the current angle or rotation speed based on current mode,
   * or -1 if the value cannot be read from sensor.
   */
  @SimpleFunction(description = "Returns the current angle or rotation speed based on current mode, " +
                                "or -1 if the value cannot be read from sensor.")
  public double GetSensorValue() {
    String functionName = "";
    return getSensorValue(functionName);
  }

  /**
   * Specifies the mode of the sensor.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_LEGO_EV3_GYRO_SENSOR_MODE,
                    defaultValue = DEFAULT_SENSOR_MODE_STRING)
  @SimpleProperty
  public void Mode(String modeName) {
    String functionName = "Mode";
    try {
      setMode(modeName);
    } catch(IllegalArgumentException e) {
      form.dispatchErrorOccurredEvent(this, functionName, ErrorMessages.ERROR_EV3_ILLEGAL_ARGUMENT, functionName);
    }
  }

  /**
   * Returns the mode of the sensor.
   */
  @SimpleProperty(description = "The sensor mode can be a text constant of either \"rate\" or \"angle\", " +
                                "which correspond to SetAngleMode or SetRateMode respectively.",
                  category = PropertyCategory.BEHAVIOR)
  public String Mode() {
    return modeString;
  }

  /**
   * Make the sensor read the angle.
   */
  @SimpleFunction(description = "Measures the orientation of the sensor.")
  public void SetAngleMode() {
    setMode(SENSOR_MODE_ANGLE_STRING);
  }

  /**
   * Make the sensor read the rotation rate.
   */
  @SimpleFunction(description = "Measures the angular velocity of the sensor.")
  public void SetRateMode() {
    setMode(SENSOR_MODE_RATE_STRING);
  }

  /**
   * Returns whether the SensorValueChanged event should fire when the sensor value changed.
   */
  @SimpleProperty(description = "Whether the SensorValueChanged event should fire when the sensor value changed.",
                  category = PropertyCategory.BEHAVIOR)
  public boolean SensorValueChangedEventEnabled() {
    return sensorValueChangedEventEnabled;
  }

  /**
   * Returns whether the SensorValueChanged event should fire when the sensor value changed.
   */
  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
                    defaultValue = "False")
  @SimpleProperty
  public void SensorValueChangedEventEnabled(boolean enabled) {
    sensorValueChangedEventEnabled = enabled;
  }

  /**
   * Called then the sensor value changed.
   */
  @SimpleEvent(description = "Called then the sensor value changed.")
  public void SensorValueChanged(double sensorValue) {
    EventDispatcher.dispatchEvent(this, "SensorValueChanged", sensorValue);
  }

  private double getSensorValue(String functionName) {
    return readInputSI(functionName,
                       0,
                       sensorPortNumber,
                       SENSOR_TYPE,
                       mode);
  }

  private void setMode(String newModeString) {
    if (SENSOR_MODE_ANGLE_STRING.equals(newModeString))
      mode = SENSOR_MODE_ANGLE;
    else if (SENSOR_MODE_RATE_STRING.equals(newModeString))
      mode = SENSOR_MODE_RATE;
    else
      throw new IllegalArgumentException();

    this.modeString = newModeString;
  }

  // Deleteable implementation
  @Override
  public void onDelete() {
    super.onDelete();
  }
}

@@ -6599,24 +6599,55 @@
 deString) %7B%0A
+    final int oldMode = mode;%0A%0A
     if (SENS
@@ -6863,24 +6863,84 @@
 ception();%0A%0A
+    if (oldMode != mode) %7B%0A      previousValue = -1;%0A    %7D%0A%0A
     this.mod

Reset previousValue to -1 on mode switch in Ev3GyroSensor (#2294)