public class MainController extends Activity {

    /* CONSTANTS*/
    public static final double MAX_MOTOR_POWER = 1023.0;
    public static final long INT_MAX = Integer.MAX_VALUE;
    public static final float MAX_SAFE_PITCH_ROLL = 45; // [deg].
    public static final float PID_DERIV_SMOOTHING = 0.5f;
    private static final String CONTROLLER = "MainController";
    /* END OF*/

    private static MainController instance;
    private Drone drone;
    private float meanThrust;
    private float yawAngleTarget;
    private float pitchAngleTarget;
    private float rollAngleTarget;
    private float altitudeTarget;
    private float timeWithoutPcRx;
    private float timeWithoutAdkRx;
    private DroneState droneState;
    private boolean regulatorEnabled;
    private boolean altitudeLockEnabled;
    PIDAngleController yawController;
    PIDAngleController pitchController;
    PIDAngleController rollController;
    private DroneState.DroneStateData droneStateData;
    private long previousTime;
    private boolean zeroStateFlag = false; // Flag indicate that droneState should be zeroed
    private FlyverMQ messageQueue;


    private LoggerService logger;

    public static MainController getInstance() {
        return instance;
    }

    public FlyverMQ getMessageQueue() {
        return messageQueue;
    }

    public float getYawAngleTarget() {
        return yawAngleTarget;
    }

    public void setYawAngleTarget(float yawAngleTarget) {
        this.yawAngleTarget = yawAngleTarget;
    }

    public float getPitchAngleTarget() {
        return pitchAngleTarget;
    }

    public void setPitchAngleTarget(float pitchAngleTarget) {
        this.pitchAngleTarget = pitchAngleTarget;
    }

    public float getRollAngleTarget() {
        return rollAngleTarget;
    }

    public void setRollAngleTarget(float rollAngleTarget) {
        this.rollAngleTarget = rollAngleTarget;
    }

    public float getMeanThrust() {
        return meanThrust;
    }

    public void setMeanThrust(float meanThrust) {
        this.meanThrust = meanThrust;
    }


    private void initLogger() {
        logger = new LoggerService(BaseApp.applicationContext, messageQueue);
        logger.Start();
        logger.LogData("EV_DEBUG", "MainController", "MainController initialized the Logger.");
    }

    public MainController(Drone drone) throws MainControllerInstanceExisting {
        if(instance != null) {
            throw new MainControllerInstanceExisting("Only one instance of the MainController can be existing at a time");
        }
        regulatorEnabled = true;
        this.drone = drone;

        try {
            messageQueue = FlyverMQ.getInstance();
        } catch (FlyverMQException e) {
            e.printStackTrace();
        }

        // altitudeRegulator = new PidRegulator(0.0f,  0.0f,  0.0f, PID_DERIV_SMOOTHING, 0.0f);

        yawAngleTarget = 0.0f;
        pitchAngleTarget = 0.0f;
        rollAngleTarget = 0.0f;
        altitudeTarget = 0.0f;
        instance = this;

    }

    public void start() {
        // Initializations.
        regulatorEnabled = true;
        altitudeLockEnabled = false;
        meanThrust = 0.0f;

        // Start the sensors.
        droneState = new DroneState();
        droneStateData = droneState.new DroneStateData();

        //initialize the controller parameters
        PIDSettings.setPIDSettings();
        startPIDControllers();

        try {
            new MainControllerConsumers().start();
        } catch (FlyverMQException e) {
            e.printStackTrace();
        }

        registerCallbacks();
        registerConsumers();

        //initLogger();
    }

    public void stop() {
        // Stop the main controller thread.
        // Stop the sensors.
        droneState.paused();
    }

    public DroneState.DroneStateData getSensorsData() {
        return droneStateData;
    }

    public void setMeanTrust(float trust) {
        meanThrust = trust;
    }

    public boolean getRegulatorsState() {
        return regulatorEnabled;
    }

    private int motorSaturation(double val) {
        if (val > MAX_MOTOR_POWER)
            return (int) MAX_MOTOR_POWER;
        else if (val < 0.0)
            return 0;
        else
            return (int) val;
    }

    /**
     * onConnectionEstablished is called when the whole systems starts
     * and int working order
     */
    public void onConnectionEstablished() {
        // Reset the orientation.
        droneState.setCurrentStateAsZero();
        // TODO: droneState.setCurrentStateAsZero works only on initialization, but can't be accessed after
    }

    /**
     * Called when the communication link between the drone and the RC is lost.
     * TODO: Break it into cases and implement algorithms such as Return To Home
     */
    public void onConnectionLost() {
        // Emergency stop of the quadcopter.
        emergencyStop("Connection Lost");
    }

    public void onIoioConnect(MicroController microController) {
        Log.e(CONTROLLER, "IOIO Connection established");

    }

    public void onIoioDisconnect() {
        Log.e(CONTROLLER, "IOIO Connection lost");
    }

    /**
     * Stops the drone.
     * NB! Currently means stopping the motors
     *
     * @param reason for logging the cause of the emergency
     */
    public void emergencyStop(String reason) {
        // TODO: Smart Landing
        Log.w("Emergency", reason);
        yawController.resetIntegrator();
        pitchController.resetIntegrator();
        rollController.resetIntegrator();
        setMeanThrust(0);
    }

    private void startPIDControllers() {
        float[][] pidValues = PIDSettings.getPidValues();
        yawController = new PIDAngleController(pidValues[0][0], pidValues[0][1], pidValues[0][2], PID_DERIV_SMOOTHING);
        pitchController = new PIDAngleController(pidValues[1][0], pidValues[1][1], pidValues[1][2], PID_DERIV_SMOOTHING);
        rollController = new PIDAngleController(pidValues[2][0], pidValues[2][1], pidValues[2][2], PID_DERIV_SMOOTHING);
    }

    private void registerCallbacks() {
        ServerCallback onValuesChange = new ServerCallback() {
            @Override
            public void run(String json) {
                setMeanTrust(Server.sCurrentStatus.getThrottle());
                setPitchAngleTarget(Server.sCurrentStatus.getPitch());
                setRollAngleTarget(Server.sCurrentStatus.getRoll());
                setYawAngleTarget(Server.sCurrentStatus.getYaw());
                if (Server.sCurrentStatus.isEmergency()) {
                    Log.e(CONTROLLER, "Emergency sequence initiated");
                    emergencyStop("Server command");
                }
            }
        };

        ServerCallback onPidYawChange = new ServerCallback() {
            @Override
            public void run(String json) {
                Log.e("PID", "PID Yaw coefficients have changed!!!");
                Status.PID pid = Server.sCurrentStatus.getPidYaw();
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_YAW_PROPORTIONAL.getValue(), String.valueOf(pid.getP()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_YAW_INTEGRAL.getValue(), String.valueOf(pid.getI()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_YAW_DERIVATIVE.getValue(), String.valueOf(pid.getD()));
                FlyverPreferences.commit();
                yawController.setCoefficients(pid.getP(), pid.getI(), pid.getD());
            }
        };

        ServerCallback onPidPitchChange = new ServerCallback() {
            @Override
            public void run(String json) {
                Log.e("PID", "PID Pitch coefficients have changed!!!");
                Status.PID pid = Server.sCurrentStatus.getPidPitch();
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_PITCH_PROPORTIONAL.getValue(), String.valueOf(pid.getP()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_PITCH_INTEGRAL.getValue(), String.valueOf(pid.getI()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_PITCH_DERIVATIVE.getValue(), String.valueOf(pid.getD()));
                FlyverPreferences.commit();
                pitchController.setCoefficients(pid.getP(), pid.getI(), pid.getD());
            }
        };

        ServerCallback onPidRollChange = new ServerCallback() {
            @Override
            public void run(String json) {
                Log.e("PID", "PID Roll coefficients have changed!!!");
                Status.PID pid = Server.sCurrentStatus.getPidRoll();
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_ROLL_PROPORTIONAL.getValue(), String.valueOf(pid.getP()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_ROLL_INTEGRAL.getValue(), String.valueOf(pid.getI()));
                FlyverPreferences.setPreference(PIDSettings.PIDKeys.PID_ROLL_DERIVATIVE.getValue(), String.valueOf(pid.getD()));
                FlyverPreferences.commit();
                rollController.setCoefficients(pid.getP(), pid.getI(), pid.getD());
            }
        };

        //Register callbacks to be called when the appropriate JSON is received
        Server.registerCallback(SharedIPCKeys.THROTTLE, onValuesChange);
        Server.registerCallback(SharedIPCKeys.YAW, onValuesChange);
        Server.registerCallback(SharedIPCKeys.COORDINATES, onValuesChange);
        Server.registerCallback(SharedIPCKeys.EMERGENCY, onValuesChange);
        Server.registerCallback(SharedIPCKeys.PIDPITCH, onPidPitchChange);
        Server.registerCallback(SharedIPCKeys.PIDROLL, onPidRollChange);
        Server.registerCallback(SharedIPCKeys.PIDYAW, onPidYawChange);
    }

    public void registerConsumers() {

        FlyverMQConsumer droneStateListener = new FlyverMQConsumer() {
            @Override
            public void dataReceived(FlyverMQMessage message) {
                float yawForce, pitchForce, rollForce, altitudeForce, currentYaw, currentPitch, currentRoll;

                if (!zeroStateFlag) {
                    droneState.setCurrentStateAsZero();
                    zeroStateFlag = true;
                }

                // Get the sensors data.
                droneStateData = (DroneState.DroneStateData) message.data;

                currentYaw = droneStateData.yaw;
                currentPitch = droneStateData.pitch;
                currentRoll = droneStateData.roll;

                long currentTime = droneStateData.time;
                float dt = ((float) (currentTime - previousTime)) / 1000000000.0f; // [s].
                previousTime = currentTime;

                // Check for dangerous situations.
                if (regulatorEnabled) {
                    // If the quadcopter is too inclined, emergency stop it.
                    if (Math.abs(currentPitch) > MAX_SAFE_PITCH_ROLL ||
                            Math.abs(currentRoll) > MAX_SAFE_PITCH_ROLL) {
                        //TODO: Setup safety max rol/pitch values
                        emergencyStop("Safe pitch or safe roll exceeded");
                    }
                }

                // Compute the motors powers.
                if (regulatorEnabled && meanThrust > 1.0) {
                    // Compute the "forces" needed to move the quadcopter to the
                    // set point.
                    yawForce = yawController.getInput(yawAngleTarget, currentYaw, dt);
                    pitchForce = pitchController.getInput(pitchAngleTarget, currentPitch, dt);
                    rollForce = rollController.getInput(rollAngleTarget, currentRoll, dt);
                /*
                if(altitudeLockEnabled)
                    altitudeForce = altitudeRegulator.getInput(altitudeTarget, currentAltitude, dt);
                else */
                    altitudeForce = meanThrust;
                    drone.updateSpeeds(yawForce, pitchForce, rollForce, altitudeForce);

                } else {
                    drone.setToZero();
                    yawController.resetIntegrator();
                    pitchController.resetIntegrator();
                    rollController.resetIntegrator();
                    yawForce = 0.0f;
                    pitchForce = 0.0f;
                    rollForce = 0.0f;
                    altitudeForce = 0.0f;
                }
            }

            @Override
            public void unregistered() {

            }

            @Override
            public void paused() {

            }

            @Override
            public void resumed() {

            }
        };
        try {
            FlyverMQ.getInstance().registerConsumer(droneStateListener, "dronestate.raw");
        } catch (FlyverMQException e) {
            e.printStackTrace();
        }
    }
} public class MainControllerInstanceExisting extends Exception {
    public MainControllerInstanceExisting(String msg ) {
        super(msg);
    }
} public class OutputsList {

    static OutputsList instance;

    LinkedList<IOIOOutput> outputs = new LinkedList<>();

    public void addOutput(IOIOOutput ioioOutput) {
        outputs.add(ioioOutput);
    }

    public LinkedList<IOIOOutput> getOutputs() {
        return outputs;
    }

    public static OutputsList getInstance() throws OutputsListException {
        if(instance == null) {
            throw new OutputsListException("Outputs list not created. Call OutputsList.create() first");
        }
        return instance;
    }

    public static void create() {
        instance = new OutputsList();
    }
} public class QuadCopterXIOIO extends QuadCopterX{

    private final int PWMFrequency = 200; // Frequcy of the PWM signal

    private int minPWMSignalPulse = 1000; // The minimum length of the pulse of the PWM signal
    private int maxPWMSignalPulse = 2023; // // The maximum length of the pulse of the PWM signal



    private PwmOutput motorFC; // Front clockwise motor
    private PwmOutput motorFCC; // Front counterclockwise motor
    private PwmOutput motorRC; // Rear clockwise motor
    private PwmOutput motorRCC; // Rear counterclockwise motor

    private int motorFCPin;
    private int motorFCCPin;
    private int motorRCPin;
    private int motorRCCPin;

    public QuadCopterXIOIO(int motorFCPin, int motorFCCPin, int motorRCPin, int motorRCCPin){
        this.motorFCPin = motorFCPin;
        this.motorFCCPin = motorFCCPin;
        this.motorRCPin = motorRCPin;
        this.motorRCCPin = motorRCCPin;

        new QuadCopterXMotors();
    }

private class QuadCopterXMotors extends IOIOOutput{


    @Override
    public void setup(IOIO ioio_) throws ConnectionLostException {


        motorFC = ioio_.openPwmOutput(motorFCPin, PWMFrequency);
        motorFCC = ioio_.openPwmOutput(motorFCCPin, PWMFrequency);
        motorRC = ioio_.openPwmOutput(motorRCPin, PWMFrequency);
        motorRCC = ioio_.openPwmOutput(motorRCCPin, PWMFrequency);

    }

    @Override
    public void process(IOIO ioio_) throws ConnectionLostException, InterruptedException {

        motorFC.setPulseWidth(motorPowers.fc + minPWMSignalPulse);
        motorFCC.setPulseWidth(motorPowers.fcc + minPWMSignalPulse);
        motorRC.setPulseWidth(motorPowers.rc + minPWMSignalPulse);
        motorRCC.setPulseWidth(motorPowers.rcc + minPWMSignalPulse);


    }
}

    public void setMinPWMSignalPulse(int minPWMSignalPulse) {
        this.minPWMSignalPulse = minPWMSignalPulse;
    }

    public void setMaxPWMSignalPulse(int maxPWMSignalPulse) {
        this.maxPWMSignalPulse = maxPWMSignalPulse;
    }

    public int getMinPWMSignalPulse() {
        return minPWMSignalPulse;
    }

    public int getMaxPWMSignalPulse() {
        return maxPWMSignalPulse;
    }
} public class AirQualityMonitorProducer extends FlyverMQProducer implements FlyverMQConsumer {

    /* Constants */
    public static String TOPIC = "airquality.mapped";
    public static String LOCATIONTOPIC = "LocationServices";
    public static String AIRQUALITYTOPIC = "sensors.airquality";
    public static String FILENAME = "airQualityData.csv";
    public static int DATABUFFERSIZE = 20;

    /* End of */

    AirQualityMonitorDataContainer airQualityMonitorDataContainer;
    DroneLocation droneLocation;
    boolean locationMarker;
    int airQualityData;
    ArrayList <String> data = new ArrayList<String>();

    public AirQualityMonitorProducer() {
        super("airquality.mapped");

        MainController.getInstance().getMessageQueue().registerConsumer(this, LOCATIONTOPIC);
        MainController.getInstance().getMessageQueue().registerConsumer(this, AIRQUALITYTOPIC);
        try {
            register(false);
        } catch (ProducerAlreadyRegisteredException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void dataReceived(FlyverMQMessage message) {

        if (message.topic.equals(LOCATIONTOPIC)){
            droneLocation = (DroneLocation) message.data;
            locationMarker = true;
        }
        else if (message.topic.equals(AIRQUALITYTOPIC)){
            airQualityData = (Integer) message.data;
            if(droneLocation != null && locationMarker ) {
                buildData();
                locationMarker = false;
            }

        }
    }

    private void buildData(){


        airQualityMonitorDataContainer = new AirQualityMonitorDataContainer(airQualityData, droneLocation, System.currentTimeMillis());
//        airQualityMonitorDataContainer.setAirQuality(airQualityData);
//        airQualityMonitorDataContainer.setDroneLocation(droneLocation);
//        airQualityMonitorDataContainer.timeStamp = System.currentTimeMillis();

        FlyverMQMessage message = new FlyverMQMessage.MessageBuilder().setCreationTime(System.nanoTime()).
                setMessageId(13000).
                setTopic(TOPIC).
                setPriority((short) 2).
                setTtl(12341).
                setData(airQualityMonitorDataContainer).
                build();

        addMessage(message);
        android.util.Log.i("airq",airQualityMonitorDataContainer.toString());
        writeToBuffer(airQualityMonitorDataContainer.toString());
    }

    private boolean isExternalStorageWritable() {
        String state = Environment.getExternalStorageState();
        if (Environment.MEDIA_MOUNTED.equals(state)) {
            return true;
        }
        return false;
    }


    public String getFilePath(){
        String path = "";
        if (isExternalStorageWritable()) {
//            path = MainController.getInstance().getMainActivityRef().getExternalFilesDir(null).getAbsolutePath();
            path = Environment.getExternalStorageDirectory().toString().concat("/co.flyver/").concat(FILENAME);
//            path = path + "/" + FILENAME;
        }

        return path;
    }

    /**
     * the writeToBuffer takes every string from the data
     * Builds up the DATABUFFERSIZE and then writes to the file
     */
    public void writeToBuffer (String stringData){

        data.add(stringData);

        if(data.size()>=DATABUFFERSIZE){
            try {
                writeToFile(data);
            } catch (IOException e) {
                e.printStackTrace();
            }
            data.clear();
        }


    }

    public void writeToFile(ArrayList<String> data) throws IOException {

        File file = new File (getFilePath());

        PrintWriter pw = new PrintWriter(new FileWriter(file,true));

        try {
            for(String d:data){
                pw.print(d);
            }
        }
        finally {
            pw.close();
        }



    }
    @Override
    public void paused() {

    }

    @Override
    public void resumed() {

    }

    @Override
    public void registered() {

    }

    @Override
    public void unregistered() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onResume() {

    }
} public class AirQualitySensor extends IOIOSensor {

    private final float SENSITIVITY_COEFFICIENT = 3f; // Increases the sensitivity of the sensor


    private int sensorReading;
    private int sensorReadingMark;
    private int sensorPin;
    private long initTimeStamp;
    private boolean timeMark;
    private float rawSensorReading;
    AnalogInput analogInput;

    /*
         This type of sensors needs about 3 minutes to heat up and produce stable values
         initTimeStamp will store the time since the app started
         timeMark will be used as an initial wait for the sensor to heat up
    */


    public AirQualitySensor(String TOPIC, int sensorPin) {
        super(TOPIC);
        this.sensorPin = sensorPin;
        initTimeStamp = System.currentTimeMillis();
    }

    @Override
    public void setup(IOIO ioio_){
        try {
            analogInput = ioio_.openAnalogInput(sensorPin);

        } catch (ConnectionLostException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void process(IOIO ioio_) {
        try {

            rawSensorReading = analogInput.read();

            if(rawSensorReading > 0.0) { // Check if there is a reading

                if (timeMark) {
                    sensorReading = (int) (rawSensorReading * SENSITIVITY_COEFFICIENT * 100); //Read the input and convert it to 0-100 %
                } else {
                    if (System.currentTimeMillis() - initTimeStamp > 1000 * 60 * 3) { // Wait 3 minutes before reading data //correct it!
                        timeMark = true;
                    }
                }
                if (sensorReading <= 100) { // Saturate for values over 100
                    sendData(sensorReading);   // Send to mq
                }
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ConnectionLostException e) {
            e.printStackTrace();
        }
    }
} public class BatteryStatus extends IOIOSensor{

    private AnalogInput batteryInput;
    private float batteryVoltage;
    private int batteryPin;
    int batteryStatus;
    int currentStatus;
    float maxVoltage;
    float minVoltage;
    BatteryCells batteryCells;
    long timer;



    /* Constants */
    final float cellMaxVoltage = 4.2f;
    final float cellMinVoltage = 3.3f;
    private final  float dividerCoefficient = 6.94f;
    private int CHECKING_TIME = 3*1000; // Check for battery status every 3 sec
    /* End of */

    public BatteryStatus(String TOPIC, int batteryPin, BatteryCells batteryCells) {
        super(TOPIC);
        this.batteryPin = batteryPin;
        this.batteryCells = batteryCells;

         maxVoltage = cellMaxVoltage*batteryCells.getValue()/dividerCoefficient;
         minVoltage = cellMinVoltage*batteryCells.getValue()/dividerCoefficient;
         timer = System.currentTimeMillis();

    }

    @Override
    public void setup(IOIO ioio_) throws ConnectionLostException {
        batteryInput = ioio_.openAnalogInput(batteryPin);
    }

    @Override
    public void process(IOIO ioio_) throws ConnectionLostException, InterruptedException {
        batteryVoltage = batteryInput.getVoltage();
        calculateStatus();
    }

    private void calculateStatus(){
        currentStatus = (int)(((batteryVoltage - minVoltage) / (maxVoltage - minVoltage))*100);

        if (currentStatus!=batteryStatus){
            batteryStatus = currentStatus;

            if (System.currentTimeMillis() - timer > CHECKING_TIME){
                sendData(batteryStatus); // Send to message queue
                timer = System.currentTimeMillis();
            }

        }
    }
    public enum BatteryCells {
        TWO(2),
        THREE(3),
        FOUR(4),
        FIVE(5),
        SIX(6);

        private int count;
        private BatteryCells(int count) {
            this.count = count;
        }
        public int getValue() {
            return count;
        }
    }
} public class SensorsList {
    static SensorsList instance;

    LinkedList<IOIOSensor> sensors = new LinkedList<>();

    public void addSensor(IOIOSensor ioioSensor) {
        sensors.add(ioioSensor);
    }

    public LinkedList<IOIOSensor> getSensors() {
        return sensors;
    }

    public static SensorsList getInstance() throws SensorsListException {
        if(instance == null) {
            throw new SensorsListException("Sensors list not created. Call SensorsList.create() first");
        }
        return instance;
    }

    public static void create() {
        instance = new SensorsList();
    }
} public class LocationServicesProvider extends FlyverMQProducer implements com.google.android.gms.location.LocationListener,GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener {

    /* Constants */
    public static final String TOPIC = "LocationServices";

    private static final long INTERVAL = 1000*5; // AirQuality Monitor requires less updates
    private static final long FASTEST_INTERVAL = 1000*3;
    private static final long ONE_MIN = 1000 * 60;
    private static final long REFRESH_TIME = ONE_MIN * 5;
    private static final float MINIMUM_ACCURACY = 50.0f;

    /* End of */

    private DroneLocation droneLocation;
    private LocationRequest locationRequest;
    private GoogleApiClient googleApiClient;
    private Location location;
    private FusedLocationProviderApi fusedLocationProviderApi;
    private Context context;

    public LocationServicesProvider() {
        super(TOPIC);

        locationRequest = LocationRequest.create();
        locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
        locationRequest.setInterval(INTERVAL);
        locationRequest.setFastestInterval(FASTEST_INTERVAL);
        context = BaseApp.applicationContext;

        googleApiClient = new GoogleApiClient.Builder(context)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();

        fusedLocationProviderApi = LocationServices.FusedLocationApi;

        droneLocation = new DroneLocation();

        if (googleApiClient != null) {
            googleApiClient.connect();
        }
        try {
            register(false);
        } catch (ProducerAlreadyRegisteredException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void registered() {

    }

    @Override
    public void unregistered() {

    }

    @Override
    public void onPause() {

    }

    @Override
    public void onResume() {

    }

    @Override
    public void onLocationChanged(Location location) {

        droneLocation.setLocation(location);

        FlyverMQMessage message = new FlyverMQMessage.MessageBuilder().setCreationTime(System.nanoTime()).
                setMessageId(13000).
                setTopic(TOPIC).
                setPriority((short) 2).
                setTtl(12341).
                setData(droneLocation).
                build();

        addMessage(message);
    }

    @Override
    public void onConnected(Bundle bundle) {
        fusedLocationProviderApi.requestLocationUpdates(googleApiClient, locationRequest, this);
    }

    @Override
    public void onConnectionSuspended(int i) {
        fusedLocationProviderApi.removeLocationUpdates(googleApiClient,this);
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }
} public class SensorsWrapper extends FlyverMQProducer implements SensorEventListener {

    /* Constants */
    public static final float ALTITUDE_SMOOTHING = 0.95f;
    public static final String TOPIC = "sensors.raw";

    @Override
    public void registered() {

    }

    @Override
    public void unregistered() {

    }

    private float[] rotationVec = new float[3];
    private float[] rotationMatrix = new float[9];
    private float[] yawPitchRollVec = new float[3];

    private long lastTime = 0;
    Sensor rotationSensor;
    Sensor pressureSensor;
    SensorManager sensorManager;
    boolean paused = false;

    public long getLastTime() {
        return lastTime;
    }

    public SensorsWrapper(Context context) {
        super(TOPIC);
        sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        pressureSensor = sensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE);
        try {
            register(false);
        } catch (ProducerAlreadyRegisteredException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor == rotationSensor) {
            // Get the time and the rotation vector.
            lastTime = event.timestamp;
            System.arraycopy(event.values, 0, rotationVec, 0, 3);

            // Convert the to "yaw, pitch, roll".
            SensorManager.getRotationMatrixFromVector(rotationMatrix, rotationVec);
            SensorManager.getOrientation(rotationMatrix, yawPitchRollVec);
            FlyverMQMessage message = new FlyverMQMessage.MessageBuilder().setCreationTime(System.nanoTime()).
                    setMessageId(13532).
                    setTopic(TOPIC).
                    setPriority((short) 1).
                    setTtl(12341).
                    setData(Arrays.toString(yawPitchRollVec)).
                    build();

            addMessage(message);
        }
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }

    @Override
    public void onPause() {
        if (paused) {
            return;
        }
        paused = true;
        sensorManager.unregisterListener(this);
    }

    @Override
    public void onResume() {

    }

    public void start() {
        paused = false;
        sensorManager.registerListener(this, rotationSensor, SensorManager.SENSOR_DELAY_FASTEST);
        sensorManager.registerListener(this, pressureSensor, SensorManager.SENSOR_DELAY_FASTEST);
    }
} public class NanoHTTPDServer extends NanoHTTPD implements FlyverMQConsumer {
    WebSocketResponseHandler responseHandler;
    public static final String MIME_JS = "application/javascript";
    public static final String MIME_CSS = "text/css";
    public static final String MIME_PNG = "image/png";
    public NanoHTTPDServer() {
        super(8080);
        responseHandler = new WebSocketResponseHandler(WebSocketsWrapper.getWebSocketFactory());
        try {
            FlyverMQ.getInstance().registerConsumer(this, "airquality.mapped");
        } catch (FlyverMQException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Response serve(IHTTPSession session) {
        Response ws = responseHandler.serve(session);
        if (ws == null) {
            final String sourcePath = "/co.flyver/webpage";
            String mime_type = NanoHTTPD.MIME_HTML;
            Method method = session.getMethod();
            String uri = session.getUri();
            Map<String, String> files = new HashMap<>();
            BufferedReader bufferedReader;
            try {
                session.parseBody(files);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ResponseException e) {
                e.printStackTrace();
            }
            if (method.toString().equalsIgnoreCase("GET")) {
                File sdcard = Environment.getExternalStorageDirectory();
                File resource = new File(sdcard, sourcePath.concat("/index.html"));
                InputStream stream = null;
                if (uri.endsWith(".html")) {
                    mime_type = NanoHTTPD.MIME_HTML;

                } else if (uri.endsWith(".js")) {
                    mime_type = MIME_JS;
                    resource = new File(sdcard, sourcePath.concat(uri));

                } else if (uri.endsWith(".css")) {
                    resource = new File(sdcard, sourcePath.concat(uri));
                    mime_type = MIME_CSS;
                } else if(uri.endsWith(".png")) {
                    resource = new File(sdcard, sourcePath.concat(uri));
                    mime_type = MIME_PNG;
                }

                try {
                    stream = new FileInputStream(resource);
//                    String line;
//                    while ((line = bufferedReader.readLine()) != null) {
//                        html.append(line);
//                        html.append("\n");
//                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }

                return new Response(Response.Status.OK, mime_type, stream);
            }
            if (method.toString().equalsIgnoreCase("POST")) {
                switch (files.get("postData")) {
                    case "file": {
                        File file = new File(Environment.getExternalStorageDirectory().getPath().concat("/co.flyver/airQualityData.csv"));
                        StringBuilder sb = new StringBuilder();
                        try {
                            BufferedReader br = new BufferedReader(new FileReader(file));
                            String line;
                            while ((line = br.readLine()) != null) {
                                sb.append(line.concat("|"));
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                        return new Response(Response.Status.OK, MIME_PLAINTEXT, sb.toString());
                    }
                    case "reset": {
                        File file = new File(Environment.getExternalStorageDirectory().getPath().concat("/co.flyver/airQualityData.csv"));
                        file.delete();
                    }
                    default: {
                        return new Response(Response.Status.OK, MIME_PLAINTEXT, "DEFAULT!");
                    }
                }
            }
            return new Response(Response.Status.NOT_FOUND, MIME_HTML, "UNAVAILABLE!!!");
        } else {
            return ws;
        }
    }

    @Override
    public void dataReceived(FlyverMQMessage message) {
        try {
            if(!WebSocketsWrapper.openSockets.isEmpty()) {
                WebSocketsWrapper.openSockets.getFirst().send(JSONUtils.serialize(message.data));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void unregistered() {

    }

    @Override
    public void paused() {

    }

    @Override
    public void resumed() {

    }
} public class FlyverMQ {


    /**
     * Helper class
     * Used to easily associate producers with an internal queue for their events
     * Constructor can be passed only producer, and the queue will be dynamic LinkedList,
     * or producer and size, and the queue will be CapacityQueue, which automatically removes
     * the oldest objects, when it's capacity is reached
     */
    private class SimpleMQProducerWrapper {
        private FlyverMQProducer producer;

        public FlyverMQProducer getProducer() {
            return producer;
        }

        public BlockingQueue<FlyverMQMessage> getQueue() {
            return queue;
        }

        private BlockingQueue<FlyverMQMessage> queue;

        SimpleMQProducerWrapper(FlyverMQProducer producer) {
            this.producer = producer;
            queue = new LinkedBlockingQueue<>();
        }

        SimpleMQProducerWrapper(FlyverMQProducer producer, int size) {
            this.producer = producer;
            queue = new CapacityQueue<>(size);
        }

        //prevent instantiating with an empty constructor
        private SimpleMQProducerWrapper() {
        }

        public int getEventCount() {
            return queue.size();
        }
    }


    /**
     * Helper class wrapping around asynchronous consumers
     * Used to associate them with an internal semaphore
     * as to avoid concurency issues
     */
    protected class SimpleMQAsyncConsumerWrapper {
        private Semaphore semaphore = new Semaphore(1);
        private FlyverMQConsumer consumer;
        private String topic;

        public Semaphore getSemaphore() {
            return semaphore;
        }

        public FlyverMQConsumer getConsumer() {
            return consumer;
        }

        public SimpleMQAsyncConsumerWrapper(FlyverMQConsumer consumer, String topic) {
            this.consumer = consumer;
            this.topic = topic;
        }

        public String getTopic() {
            return topic;
        }
    }

    /**
     * Helper class, that wraps the real-time consumers
     * with an internal queue for the messages they are subscribed to,
     * as to guarantee delivery and processing of every message received
     */
    private class SimpleMQRTConsumerWrapper {
        private FlyverMQConsumer consumer;
        private Thread worker;
        private LinkedList<FlyverMQMessage> queue;
        private ExecutorService executorService = Executors.newSingleThreadExecutor();
        boolean empty = true;

        private SimpleMQRTConsumerWrapper() {
            //prevent instantiating with empty constructor
        }

        SimpleMQRTConsumerWrapper(FlyverMQConsumer consumer) {
            this.consumer = consumer;
            worker = new Thread(new Runnable() {
                @Override
                public void run() {
                    doWork();
                }
            });
            worker.start();
        }

        /**
         * Adds a message to the queue, and if it's empty,
         * notifies the worker thread, associated with the wrapper,
         * that there are messages ready for processing
         *
         * @param message
         */
        public void addMessage(FlyverMQMessage message) {
            queue.add(message);
            if (empty) {
                empty = false;
                this.notify();
            }
        }

        public int getEventCount() {
            return queue.size();
        }

        /**
         * Loops until all messages in the queue have been processed
         * If no more messages are left, it waits until notified
         * that more messages have been added
         */
        private void doWork() {
            //noinspection InfiniteLoopStatement
            while (true) {
                final FlyverMQMessage message = queue.pop();
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        consumer.dataReceived(message);
                    }
                });
                if (queue.isEmpty()) {
                    empty = true;
                }
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * Helper class intended for running in a worker thread
     * it polls the producers and gets
     * the messages that are on top of their respective queue,
     * then broadcasts them to every client, that is registered
     * for the message's topic. The client's dataReceived(SimpleMQMessage)
     * method is executed in a different thread, managed by a ThreadPool implemented
     * via ExecutorService
     */
    private class SimpleMQWorker implements Runnable {
        FlyverMQMessage message;
        ExecutorService executorService = Executors.newFixedThreadPool(50);

        @Override
        public void run() {
            //noinspection InfiniteLoopStatement
            while (true) {
                //loop over all registered producers
                try {
                    topicsGuard.acquire();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (String topic : topics.keySet()) {
                    //get the topmost event of the queue, associated with the current producer
                    //sometimes tries to poll past the index of the queue
                    //swallow the exception, and try on the next iteration again
                    try {
                        message = topics.get(topic).poll();
                        if (message == null) {
                            continue;
                        }
                    } catch (NoSuchElementException e) {
                        //e.printStackTrace();
                        continue;
                    }
                    //get the linked list containing all consumers registered for the topic
                    //and broadcast the message to all of them
                    if (asyncConsumers.get(message.topic) == null) {
                        Log.e("MQ", "No consumers present for topic ".concat(message.topic));
                        continue;
                    }
                    for (SimpleMQAsyncConsumerWrapper wrapper : asyncConsumers.get(message.topic)) {
                        //post the client's dataReceived method to the ThreadPool
                        if(!wrapper.topic.equals(message.topic)) {
                            Log.e(TAG, "message intended for topic ".concat(message.topic).concat(" is sent to ".concat(wrapper.getTopic())));
                        }
                        sendMsg(wrapper, message);
                    }
                }
                topicsGuard.release();
            }
        }

        private void sendMsg(final SimpleMQAsyncConsumerWrapper wrapper, final FlyverMQMessage msg) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        wrapper.getSemaphore().acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    wrapper.getConsumer().dataReceived(msg);
                    wrapper.getSemaphore().release();
                }
            });
        }
    }

    private static FlyverMQ instance;
    private final String TAG = "SimpleMQ";
    private final String REGISTEREDMSG = "Producer already registered: ";
    private final String NOTOPICMSG = "No such topic exists : ";
    protected Map<String, LinkedList<FlyverMQProducer>> producers = new HashMap<>();
    protected Map<String, LinkedList<SimpleMQAsyncConsumerWrapper>> asyncConsumers = new HashMap<>();
    protected Map<String, LinkedList<FlyverMQConsumer>> rtConsumers = new HashMap<>();
    protected LinkedList<FlyverMQCallback> registeredComponentCallbacks = new LinkedList<>();
    protected Map<String, BlockingQueue<FlyverMQMessage>> topics = new HashMap<>();
    private FlyverMQSocketServer socketServer;
    private Semaphore topicsGuard = new Semaphore(1);

    public FlyverMQ() {
    }

    public static FlyverMQ getInstance() throws FlyverMQException {
        if (instance == null) {
            throw new FlyverMQException("FlyverMQ is not instantiated");
        }
        return instance;
    }

    public static void start() {
        if(instance == null) {
            instance = new FlyverMQ().init();
        }
    }

    /**
     * Initializes the internals of the message queue and starts it's worker threads
     *
     * @return this, as to allow chaining
     */
    private FlyverMQ init() {
        final FlyverMQ flyverMQref = this;
        Thread networkIO = new Thread(new Runnable() {
            @Override
            public void run() {
                socketServer = new FlyverMQSocketServer(flyverMQref);
            }
        });
        networkIO.start();
        Thread worker = new Thread(new SimpleMQWorker());
        worker.setDaemon(true);
        worker.start();
        return this;
    }

    /**
     * Register an object, implementing the SimpleMQCallback interface
     * which is used to monitor changes in the producers registration and deregistration
     * from the components
     *
     * @param callback SimpleMQCallback, implementing onRegistered and onUnregistered methods
     * @return this, as to allow chaining
     */
    public FlyverMQ producersStatusCallback(FlyverMQCallback callback) {
        registeredComponentCallbacks.add(callback);
        return this;
    }

    /**
     * Register an Object implementing the SimpleMQProducer interface to a topic
     * All messages by this producer are enqueued for later processing
     *
     * @param producer       Class implementing SimpleMQProducer interface
     * @param topic          String defining the topic of the producer
     * @param removeExisting Set to true to remove previously registered producer for the topic
     * @return this, to allow chaining
     * @throws ProducerAlreadyRegisteredException - Throws the already registered exception if a producer is already registered,
     *                                            and the removeExisting flag is set to false
     */
    public FlyverMQ registerProducer(FlyverMQProducer producer, String topic, boolean removeExisting) throws ProducerAlreadyRegisteredException {
        if (topicExists(topic) && !removeExisting) {
            throw new ProducerAlreadyRegisteredException(REGISTEREDMSG.concat(topic));
        }
        if (topicExists(topic)) {
            try {
                unregisterProducer(topic);
            } catch (NoSuchTopicException e) {
                e.printStackTrace();
            }
        }

        if (!producers.containsKey(topic)) {
            producers.put(topic, new LinkedList<FlyverMQProducer>());
        } else {
            producers.get(topic).add(producer);
        }
        if (!topics.containsKey(topic)) {
            try {
                topicsGuard.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            topics.put(topic, new LinkedBlockingQueue<FlyverMQMessage>());
            topicsGuard.release();
        }
        for (FlyverMQCallback callback : registeredComponentCallbacks) {
            callback.producerRegistered(topic);
        }
        Log.d(TAG, "Producer registered ".concat(topic));
        return this;
    }

    /**
     * Register an Object implementing the SimpleMQProducer interface to a topic
     * All messages by this producer are enqueued for later processing
     * The internal queue of the SimpleMQ implementing object is limited to the number
     * passed as the fourth parameter. Any events that are passed over that limit
     * discard the oldest events, and are enqueued
     *
     * @param producer       Class implementing SimpleMQProducer interface
     * @param topic          String defining the topic of the producer
     * @param removeExisting Set to true to remove previously registered producer for the topic
     * @param maxQueueEvents int, the limit of the queue
     * @return this, to allow chaining
     * @throws ProducerAlreadyRegisteredException - Throws the already registered exception if a producer is already registered,
     *                                            and the removeExisting flag is set to false
     */
    public FlyverMQ registerProducer(FlyverMQProducer producer, String topic, boolean removeExisting, int maxQueueEvents) throws ProducerAlreadyRegisteredException {
        if (!removeExisting && topicExists(topic)) {
            throw new ProducerAlreadyRegisteredException(REGISTEREDMSG.concat(topic));
        }
        if (topicExists(topic)) {
            try {
                unregisterProducer(topic);
            } catch (NoSuchTopicException e) {
                e.printStackTrace();
            }
        }
        if (!producers.containsKey(topic)) {
            producers.put(topic, new LinkedList<FlyverMQProducer>());
        } else {
            producers.get(topic).add(producer);
        }
        if (!topics.containsKey(topic)) {
            try {
                topicsGuard.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            topics.put(topic, new LinkedBlockingQueue<FlyverMQMessage>());
            topicsGuard.release();
        }
        for (FlyverMQCallback callback : registeredComponentCallbacks) {
            callback.producerRegistered(topic);
        }
        Log.d(TAG, "Producer registered ".concat(topic));
        return this;
    }

    /**
     * Removes the producer for a supplied topic from the list of producers
     * If no producer is present for the supplied topic, an exception is thrown
     *
     * @param topic the topic for which the producer is to be removed
     * @return this, to allow chaining
     * @throws NoSuchTopicException - thrown if no producer is registered for the requested topic
     */
    public FlyverMQ unregisterProducer(String topic) throws NoSuchTopicException {
        if (!topicExists(topic)) {
            throw new NoSuchTopicException(NOTOPICMSG.concat(topic));
        } else {
//            producers.get(topic).unregistered();
            for (FlyverMQProducer producer : producers.get(topic)) {
                producer.unregistered();
            }
            producers.remove(topic);
            for (FlyverMQCallback callback : registeredComponentCallbacks) {
                callback.producerUnregistered(topic);
            }
            return this;
        }
    }

    /**
     * Register a consumer for a topic.
     * More than one consumer may be registered for a single topic
     *
     * @param consumer Object implementing the SimpleMQConsumer interface
     * @param topic    String defining the topic that the consumer should receive
     * @return - this, to allow chaining
     */
    public FlyverMQ registerConsumer(FlyverMQConsumer consumer, String topic) {
        if (!asyncConsumers.containsKey(topic)) {
            asyncConsumers.put(topic, new LinkedList<SimpleMQAsyncConsumerWrapper>());
            asyncConsumers.get(topic).add(new SimpleMQAsyncConsumerWrapper(consumer, topic));
        } else {
            asyncConsumers.get(topic).add(new SimpleMQAsyncConsumerWrapper(consumer, topic));
        }
        return this;
    }

    /**
     * Removes all enqueued message for the supplied topic.
     * Throws exception if no producers are registered for the supplied topic
     *
     * @param topic string defining the topic that is to be cleared
     * @return this, to allow chaining
     * @throws NoSuchTopicException - thrown if the topic has no registered producers
     */
    public FlyverMQ clearTopic(String topic) throws NoSuchTopicException {
        if (topicExists(topic)) {
            topics.get(topic).clear();
        } else {
            throw new NoSuchTopicException(NOTOPICMSG.concat(topic));
        }
        return this;
    }

    /**
     * Returns the count of all events in the queue, associated with the topic
     * Throws exception if no such topic is present
     *
     * @param topic String defining the topic for which the event count is requested
     * @return int, count of the events in the queue
     * @throws NoSuchTopicException - thrown if no such topic is registered
     */
    public int getEventCount(String topic) throws NoSuchTopicException {
        if (!topicExists(topic)) {
            throw new NoSuchTopicException(NOTOPICMSG.concat(topic));
        } else {
            return topics.get(topic).size();
        }
    }

    /**
     * Adds message to the message queue associated with a topic
     *
     * @param producer - producer of the data
     * @param data     string with the data contained in the message
     * @return boolean, true if adding the message to the queue is successful, false otherwise
     */
    protected boolean addMessage(FlyverMQProducer producer, FlyverMQMessage data) throws NoSuchTopicException {
        if (!topics.containsKey(producer.topic)) {
            throw new NoSuchTopicException(NOTOPICMSG + producer.topic);
        }
        return topics.get(producer.topic).add(data);
    }

    /**
     * Get the first message of the queue for a topic
     *
     * @param topic    String defining the topic that should provide the message
     * @param blocking boolean, if processing the message is synchronous or not
     * @return SimpleMQMessage, the message on top of the queue
     * @throws NoSuchTopicException - thrown if no such  topic is in existence
     */
    public FlyverMQMessage getMessage(String topic, boolean blocking) throws NoSuchTopicException {
        if (!topicExists(topic)) {
            throw new NoSuchTopicException(NOTOPICMSG.concat(topic));
        }
        return topics.get(topic).poll();
    }

    /**
     * Get the first N messages on the top of the queue
     *
     * @param topic    String defining the topic that should provide the messages
     * @param blocking boolean, if processing the message is synchronous or not
     * @param count    int, the number N of the requested messages
     * @return LinkedList containing the requested messages
     * @throws NoSuchTopicException - thrown if no such topic is in existence
     */
    public LinkedList<FlyverMQMessage> getMessages(String topic, boolean blocking, int count) throws NoSuchTopicException {
        if (!topicExists(topic)) {
            throw new NoSuchTopicException(NOTOPICMSG + topic);
        }
        LinkedList<FlyverMQMessage> messages = new LinkedList<>();
        for (int i = 0; i < count; i++) {
            FlyverMQMessage message = topics.get(topic).poll();
            if (message == null) {
                return messages;
            }
            messages.add(message);
        }
        return messages;
    }

    /**
     * Get all the topics, that have registered producers to them
     * can match against strings and java-formatted regular expressions alike
     *
     * @param topic String for the topic that is being searched.
     *              If passed null value or wildcard "*" returns all registered topics
     *              else returns all topics starting with the passed string
     * @return - LinkedList containing the matched topics
     */
    protected LinkedList<String> listTopics(String topic) {
        LinkedList<String> topics = new LinkedList<>();
        if (topic == null || topic.equals("*")) {
            for (String s : producers.keySet()) {
                topics.add(s);
            }
        } else {
            Pattern pattern = Pattern.compile("\\^".concat(topic).concat(".+"));
            Matcher matcher;
            for (String s : producers.keySet()) {
                matcher = pattern.matcher(s);
                if (matcher.matches()) {
                    topics.add(s);
                }
            }
        }
        return topics;
    }

    /**
     * Check if the supplied topic has any registered producers to it
     *
     * @param topic string defining the topic
     * @return boolean, true if the topic has any active producers, false otherwise
     */
    public boolean topicExists(String topic) {
        return producers.containsKey(topic);
    }

} public class WebpageUtils {
    private final static String webpageLocation = "/co.flyver/webpage/";

    public static String getWebpageLocation() {
        return Environment.getExternalStorageDirectory().toString().concat(webpageLocation);
    }

    /**
     * Checks if the flyver webpage files have been copied to sdcard/co.flyver/webpage/
     * Copies them if not
     * @param assetManager the asset manager associated with the application context
     */
    public static void deployWebpage(AssetManager assetManager) {
        String path = getWebpageLocation();
        File webpageDir = new File(path);
        //TODO:: commented for debugging purposes, uncomment later
        if (!webpageDir.exists()) {
            webpageDir.mkdirs();
            copyWebpage(path, assetManager);
        }
    }

    /**
     * Copies the files for the webpage from the assets dir, to the /co.flyver/webpage/ dir on the sdcard
     *
     * @param destinationPath
     */
    private static void copyWebpage(String destinationPath, AssetManager assetManager) {
        String[] files = null;
        try {
            files = assetManager.list("webpage");
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (files != null) {
            for (String file : files) {
                InputStream inputStream;
                OutputStream outputStream;

                try {
                    inputStream = assetManager.open("webpage/" + file);
                    outputStream = new FileOutputStream(destinationPath.concat(file));
                    byte[] buffer = new byte[1024];
                    int read;
                    while ((read = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, read);
                    }
                    inputStream.close();
                    outputStream.flush();
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
import android.app.Application;
import android.content.Context;
import co.flyver.flyvercore.maincontrollers.MainController;
import co.flyver.flyvercore.maincontrollers.MainControllerInstanceExisting;
import co.flyver.flyvercore.microcontrollers.IOIOOutputs.OutputsList;
import co.flyver.flyvercore.microcontrollers.IOIOOutputs.QuadCopterXIOIO;
import co.flyver.flyvercore.statedata.IOIOSensors.AirQuality.AirQualityMonitorProducer;
import co.flyver.flyvercore.statedata.IOIOSensors.AirQuality.AirQualitySensor;
import co.flyver.flyvercore.statedata.IOIOSensors.BatteryStatus;
import co.flyver.flyvercore.statedata.IOIOSensors.SensorsList;
import co.flyver.flyvercore.statedata.LocationServicesProvider;
import co.flyver.flyvercore.statedata.SensorsWrapper;
import co.flyver.httpserver.NanoHTTPDServer;
import co.flyver.utils.flyvermq.FlyverMQ;
import co.flyver.utils.webpage.WebpageUtils;
import fi.iki.elonen.ServerRunner;
package co.flyver.flyvercore.baseapp;



/**
 * This class serves as a single entry point in the system.
 * It's constructor and methods are called only once at the application's startup
 * and are not called again on activity changes.
 * Static initializations that do not depend on the application's context should be
 * carried out in the constructor.
 * Initializations that depend on the context are carried out in the onCreate() method.
 * Also this class exposes the global application context
 *
 * Created by Petar Petrov on 2/5/15.
 */
public class BaseApp extends Application {
    public static Context applicationContext;
    private static QuadCopterXIOIO drone;


    public BaseApp() {
        super();

        FlyverMQ.start();
        SensorsList.create();
        OutputsList.create();
        drone = new QuadCopterXIOIO(34,35,36,37);
        try {
            new MainController(drone).start();
        } catch (MainControllerInstanceExisting mainControllerInstanceExisting) {
            mainControllerInstanceExisting.printStackTrace();
        }
        ServerRunner.run(NanoHTTPDServer.class);
    }

    @Override
    public void onCreate() {
        super.onCreate();
        applicationContext = getApplicationContext();
        new SensorsWrapper(applicationContext).start();
        WebpageUtils.deployWebpage(applicationContext.getResources().getAssets());