package net.steppschuh.sensordatalogger;

import com.google.android.gms.wearable.Wearable;
import com.google.firebase.analytics.FirebaseAnalytics;

import android.app.DialogFragment;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.GridView;
import android.widget.TextView;

import net.steppschuh.datalogger.data.DataBatch;
import net.steppschuh.datalogger.data.DataChangedListener;
import net.steppschuh.datalogger.data.request.DataRequest;
import net.steppschuh.datalogger.data.request.DataRequestResponse;
import net.steppschuh.datalogger.data.request.SensorDataRequest;
import net.steppschuh.datalogger.logging.TimeTracker;
import net.steppschuh.datalogger.messaging.ReachabilityChecker;
import net.steppschuh.datalogger.messaging.handler.MessageHandler;
import net.steppschuh.datalogger.messaging.handler.SinglePathMessageHandler;
import net.steppschuh.datalogger.sensor.DeviceSensor;
import net.steppschuh.datalogger.status.ActivityStatus;
import net.steppschuh.datalogger.status.Status;
import net.steppschuh.datalogger.status.StatusUpdateHandler;
import net.steppschuh.datalogger.status.StatusUpdateReceiver;
import net.steppschuh.sensordatalogger.ui.SensorSelectionDialogFragment;
import net.steppschuh.sensordatalogger.ui.visualization.VisualizationCardData;
import net.steppschuh.sensordatalogger.ui.visualization.VisualizationCardListAdapter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class PhoneActivity extends AppCompatActivity implements DataChangedListener, ReachabilityChecker.NodeReachabilityUpdateReceiver, SensorSelectionDialogFragment.SelectedSensorsUpdatedListener {

    private static final String TAG = PhoneActivity.class.getSimpleName();

    private static final String KEY_SENSOR_DATA_REQUESTS = "sensorDataRequests";
    private static final String KEY_SELECTED_SENSORS = "selectedSensors";

    private PhoneApp app;
    private List<MessageHandler> messageHandlers;
    private ActivityStatus status = new ActivityStatus();
    private StatusUpdateHandler statusUpdateHandler;

    private FloatingActionButton floatingActionButton;
    private TextView logTextView;
    private GridView gridView;

    private VisualizationCardListAdapter cardListAdapter;
    private SensorSelectionDialogFragment sensorSelectionDialog;

    private Map<String, SensorDataRequest> sensorDataRequests = new HashMap<>();
    private Map<String, List<DeviceSensor>> selectedSensors = new HashMap<>();
    private Map<String, AlertDialog> reachabilityDialogs = new HashMap<>();

    private String lastResponseStatus;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // get reference to global application
        app = (PhoneApp) getApplicationContext();

        // initialize with context activity if needed
        if (!app.getStatus().isInitialized() || app.getContextActivity() == null) {
            app.initialize(this);
        }

        // setup stuff
        setupUi();
        setupMessageHandlers();
        setupStatusUpdates();
        setupAnalytics();
        setupTracking();

        // update status
        status.setInitialized(true);
        status.updated(statusUpdateHandler);

        // avoid empty state
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                if (sensorDataRequests.entrySet().size() == 0) {
                    // currently not requesting any data, open sensor selection
                    showSensorSelectionDialog();
                }
            }
        }, TimeUnit.SECONDS.toMillis(1));
    }

    private void setupUi() {
        setContentView(R.layout.activity_main);

        floatingActionButton = (FloatingActionButton) findViewById(R.id.floadtingActionButton);
        floatingActionButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                showSensorSelectionDialog();
            }
        });

        logTextView = (TextView) findViewById(R.id.logText);
        gridView = (GridView) findViewById(R.id.gridView);

        List<VisualizationCardData> visualizationCardData = new ArrayList<>();
        cardListAdapter = new VisualizationCardListAdapter(this, R.id.gridView, visualizationCardData);
        gridView.setAdapter(cardListAdapter);
    }

    private void setupMessageHandlers() {
        messageHandlers = new ArrayList<>();
        messageHandlers.add(getSetStatusMessageHandler());
        messageHandlers.add(getSensorDataRequestResponseMessageHandler());
    }

    private void setupStatusUpdates() {
        statusUpdateHandler = new StatusUpdateHandler();
        statusUpdateHandler.registerStatusUpdateReceiver(new StatusUpdateReceiver() {
            @Override
            public void onStatusUpdated(Status status) {
                app.getStatus().setActivityStatus((ActivityStatus) status);
                app.getStatus().updated(app.getStatusUpdateHandler());
            }
        });

        app.getGoogleApiMessenger().getStatusUpdateHandler().registerStatusUpdateReceiver(new StatusUpdateReceiver() {
            @Override
            public void onStatusUpdated(Status status) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        checkForConnectedButUnreachableNodes();
                    }
                }, ReachabilityChecker.REACHABILITY_TIMEOUT_DEFAULT);
            }
        });
    }

    private void setupAnalytics() {
        Bundle bundle = new Bundle();
        try {
            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            bundle.putString("version_code", String.valueOf(packageInfo.versionCode));
            bundle.putString("version_name", String.valueOf(packageInfo.versionName));
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Unable to get package info");
        }
        app.getAnalytics().logEvent(FirebaseAnalytics.Event.APP_OPEN, bundle);
    }

    private void setupTracking() {
        TimeTracker tracker = app.getTrackerManager().getTracker("renderDataBatch");
        tracker.setMaximumTrackingCount(100);
        tracker.registerTrackingListener(new TimeTracker.TrackingListener() {
            @Override
            public void onTrackingFinished(TimeTracker timeTracker) {
                //Log.d(TAG, "Tracking finished: " + timeTracker);
                //Log.d(TAG, "Last response status: " + lastResponseStatus);
                timeTracker.reset();
            }

            @Override
            public void onNewDurationTracked(TimeTracker timeTracker) {

            }
        });
    }

    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        try {
            // restore sensor data requests
            sensorDataRequests = (HashMap) savedInstanceState.getSerializable(KEY_SENSOR_DATA_REQUESTS);
            if (sensorDataRequests == null) {
                sensorDataRequests = new HashMap<>();
            }

            // restore selected sensors
            selectedSensors = (HashMap) savedInstanceState.getSerializable(KEY_SELECTED_SENSORS);
            if (selectedSensors == null) {
                selectedSensors = new HashMap<>();
            }
            Log.d(TAG, "Instance state restored");

            // Update end timestamps of data requests for selected sensors.
            // This is required because all requests have been terminated when
            // the activity stopped.
            for (Map.Entry<String, SensorDataRequest> sensorDataRequestEntry : sensorDataRequests.entrySet()) {
                List<DeviceSensor> sensors = selectedSensors.get(sensorDataRequestEntry.getKey());
                if (sensors == null || sensors.size() == 0) {
                    continue;
                }
                sensorDataRequestEntry.getValue().setEndTimestamp(DataRequest.TIMESTAMP_NOT_SET);
            }

            // send all available data requests
            sendSensorEventDataRequests();
        } catch (Exception ex) {
            Log.w(TAG, "Unable to restore instance state: " + ex.getMessage());
            ex.printStackTrace();
        }
        super.onRestoreInstanceState(savedInstanceState);
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putSerializable(KEY_SENSOR_DATA_REQUESTS, (HashMap) sensorDataRequests);
        outState.putSerializable(KEY_SELECTED_SENSORS, (HashMap) selectedSensors);
        Log.d(TAG, "Saved instance state");
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onStart() {
        super.onStart();

        // register message handlers
        for (MessageHandler messageHandler : messageHandlers) {
            app.registerMessageHandler(messageHandler);
        }
        Wearable.MessageApi.addListener(app.getGoogleApiMessenger().getGoogleApiClient(), app);

        // register reachability callback
        app.getReachabilityChecker().registerReachabilityUpdateReceiver(ReachabilityChecker.NODE_ID_ANY, this);

        // update status
        status.setInForeground(true);
        status.updated(statusUpdateHandler);

        // start data request
        sendSensorEventDataRequests();
    }

    @Override
    protected void onResume() {
        super.onResume();

        // update reachabilities of nearby nodes
        app.getReachabilityChecker().checkReachabilities(this);
    }

    @Override
    protected void onStop() {
        // stop data request
        stopRequestingSensorEventData();

        // let other devices know that the app won't be reachable anymore
        app.getGoogleApiMessenger().sendMessageToNearbyNodes(MessageHandler.PATH_CLOSING, Build.MODEL);

        // unregister reachability callback
        app.getReachabilityChecker().unregisterReachabilityUpdateReceiver(this);

        // unregister message handlers
        for (MessageHandler messageHandler : messageHandlers) {
            app.unregisterMessageHandler(messageHandler);
        }
        Wearable.MessageApi.removeListener(app.getGoogleApiMessenger().getGoogleApiClient(), app);

        // update status
        status.setInForeground(false);
        status.updated(statusUpdateHandler);
        super.onStop();
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // update grid padding
        int verticalMargin = (int) getResources().getDimension(R.dimen.activity_vertical_margin);
        int horizontalMargin = (int) getResources().getDimension(R.dimen.activity_horizontal_margin);
        gridView.setPadding(horizontalMargin, verticalMargin, horizontalMargin, verticalMargin);
        gridView.invalidate();
    }

    /**
     * Will be called when the reachability of a connected @Node
     * (e.g. wearable device) has changed
     */
    @Override
    public void onReachabilityUpdated(String nodeId, boolean isReachable) {
        Log.d(TAG, "Reachability of " + nodeId + " changed to: " + String.valueOf(isReachable));

        // generate a readable message
        String nodeName = app.getGoogleApiMessenger().getNodeName(nodeId);
        String message = isReachable ? getString(R.string.device_connected) : getString(R.string.device_disconnected);
        message = message.replace("[DEVICENAME]", nodeName);

        // notify the user with a @Snackbar
        View parentLayout = findViewById(android.R.id.content);
        Snackbar.make(parentLayout, message, Snackbar.LENGTH_LONG)
                .setDuration(Snackbar.LENGTH_LONG)
                .show();

        if (isReachable) {
            // update reachability dialog, if any
            AlertDialog reachabilityDialog = reachabilityDialogs.get(nodeId);
            if (reachabilityDialog != null && reachabilityDialog.isShowing()) {
                reachabilityDialog.dismiss();
                showSensorSelectionDialog();
            }
        }

        // re-send available data requests
        sendSensorEventDataRequests();
    }

    /**
     * Will be called when a @Node sends (sensor) @Data to this device
     */
    @Override
    public void onDataChanged(DataBatch dataBatch, String sourceNodeId) {
        renderDataBatch(dataBatch, sourceNodeId);
    }

    /*
     * Message Handlers
     */
    private MessageHandler getSetStatusMessageHandler() {
        return new SinglePathMessageHandler(MessageHandler.PATH_SET_STATUS) {
            @Override
            public void handleMessage(Message message) {
                String sourceNodeId = MessageHandler.getSourceNodeIdFromMessage(message);
                String statusJson = MessageHandler.getDataFromMessageAsString(message);
                Log.d(TAG, "Received status from: " + sourceNodeId + ": " + statusJson);
                logTextView.setText(statusJson);
            }
        };
    }

    private MessageHandler getSensorDataRequestResponseMessageHandler() {
        return new SinglePathMessageHandler(MessageHandler.PATH_SENSOR_DATA_REQUEST_RESPONSE) {
            @Override
            public void handleMessage(final Message message) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            // parse response data
                            final String sourceNodeId = MessageHandler.getSourceNodeIdFromMessage(message);
                            final String responseJson = MessageHandler.getDataFromMessageAsString(message);
                            final DataRequestResponse response = DataRequestResponse.fromJson(responseJson);

                            if (response.getDataBatches().size() > 0) {
                                long transmissionDuration = System.currentTimeMillis() - response.getEndTimestamp();
                                TimeTracker tracker = app.getTrackerManager().getTracker("renderDataBatch");
                                tracker.addDuration(TimeUnit.MILLISECONDS.toNanos(transmissionDuration));

                                StringBuilder sb = new StringBuilder();
                                sb.append("First data batch items: ");
                                sb.append(response.getDataBatches().get(0).getDataList().size());
                                sb.append(" / ");
                                sb.append(response.getDataBatches().get(0).getCapacity());

                                sb.append("\nSerialized bytes: ");
                                sb.append(responseJson.getBytes().length);

                                lastResponseStatus = sb.toString();
                            }

                            // render data in UI thread
                            Runnable notifyDataChangedRunnable = new Runnable() {
                                @Override
                                public void run() {
                                    for (DataBatch dataBatch : response.getDataBatches()) {
                                        onDataChanged(dataBatch, sourceNodeId);
                                    }
                                }
                            };
                            new Handler(Looper.getMainLooper()).post(notifyDataChangedRunnable);
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
                }).start();
            }
        };
    }

    private void requestStatusUpdateFromConnectedNodes() {
        try {
            Log.v(TAG, "Sending a status update request");
            app.getGoogleApiMessenger().sendMessageToNearbyNodes(MessageHandler.PATH_GET_STATUS, "");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Will create and show a dialog that allows the user to check the @DeviceSensor
     * on each connected node that he wants to stream
     */
    private void showSensorSelectionDialog() {
        try {
            if (sensorSelectionDialog != null) {
                Log.w(TAG, "Not showing sensor selection dialog, previous dialog is still set");
                return;
            }
            Log.d(TAG, "Showing sensor selection dialog");
            sensorSelectionDialog = new SensorSelectionDialogFragment();
            sensorSelectionDialog.setPreviouslySelectedSensors(selectedSensors);
            sensorSelectionDialog.show(getFragmentManager(), SensorSelectionDialogFragment.class.getSimpleName());
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Will be called when the sensor selection dialog has been closed after
     * sensors from all nodes have been selected
     */
    @Override
    public void onSensorsFromAllNodesSelected(Map<String, List<DeviceSensor>> selectedSensors) {
        Log.d(TAG, "Sensors from all nodes selected");
        this.selectedSensors = selectedSensors;

        // track selected sensors in analytics
        for (Map.Entry<String, List<DeviceSensor>> selectedSensorsEntry : selectedSensors.entrySet()) {
            for (DeviceSensor deviceSensor : selectedSensorsEntry.getValue()) {
                Bundle bundle = new Bundle();
                bundle.putString(FirebaseAnalytics.Param.ITEM_ID, String.valueOf(deviceSensor.getType()));
                bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, deviceSensor.getName());
                bundle.putString(FirebaseAnalytics.Param.ITEM_CATEGORY, deviceSensor.getStringType());
                app.getAnalytics().logEvent(FirebaseAnalytics.Event.VIEW_ITEM, bundle);
            }
        }
    }

    /**
     * Will be called when the sensor selection dialog has shown @DeviceSensor from
     * the specified node
     */
    @Override
    public void onSensorsFromNodeSelected(String nodeId, List<DeviceSensor> sensors) {
        StringBuilder sb = new StringBuilder("Selected sensors for " + nodeId + ":");
        for (DeviceSensor sensor : sensors) {
            sb.append("\n - " + sensor.getName());
        }
        Log.d(TAG, sb.toString());

        selectedSensors.put(nodeId, sensors);

        SensorDataRequest sensorDataRequest = SensorSelectionDialogFragment.createSensorDataRequest(sensors);
        sensorDataRequest.setSourceNodeId(app.getGoogleApiMessenger().getLocalNodeId());

        sensorDataRequests.put(nodeId, sensorDataRequest);

        sendSensorEventDataRequests();
        removeUnneededVisualizationCards();
    }

    /**
     * Will be called if the sensor selection dialog has been closed
     */
    @Override
    public void onSensorSelectionClosed(DialogFragment dialog) {
        Log.d(TAG, "Sensor selection closed");
        sensorSelectionDialog = null;
    }

    /**
     * Returns true if the app is requesting sensor data from
     * the local or any connected device
     */
    private boolean isRequestingSensorEventData() {
        for (Map.Entry<String, SensorDataRequest> sensorDataRequestEntry : sensorDataRequests.entrySet()) {
            if (sensorDataRequestEntry.getValue().getEndTimestamp() == DataRequest.TIMESTAMP_NOT_SET) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns true if the app is requesting sensor data from
     * the device with the specified node id
     */
    private boolean isRequestingSensorEventData(String nodeId) {
        SensorDataRequest request = sensorDataRequests.get(nodeId);
        if (request == null) {
            return false;
        }
        return request.getEndTimestamp() == DataRequest.TIMESTAMP_NOT_SET;
    }

    /**
     * Returns true if the app is requesting data by the specified sensor
     * from the device with the specified node id
     */
    private boolean isRequestingSensorEventData(String nodeId, String sensorName) {
        // check if the request has reached is end timestamp
        if (!isRequestingSensorEventData(nodeId)) {
            return false;
        }

        // check if the current sensor is selected
        boolean sensorIsRequested = false;
        for (DeviceSensor deviceSensor : selectedSensors.get(nodeId)) {
            if (!deviceSensor.getName().equals(sensorName)) {
                continue;
            }
            sensorIsRequested = true;
        }
        return sensorIsRequested;
    }

    /**
     * Sends all available sensor data requests to the assigned nodes
     */
    private void sendSensorEventDataRequests() {
        try {
            Log.v(TAG, "Updating sensor event data request");
            for (Map.Entry<String, SensorDataRequest> sensorDataRequestEntry : sensorDataRequests.entrySet()) {
                sendSensorEventDataRequest(sensorDataRequestEntry.getKey(), sensorDataRequestEntry.getValue());
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }


    private void sendSensorEventDataRequest(String nodeId, SensorDataRequest request) {
        try {
            StringBuilder sb = new StringBuilder("Sending sensor data request to " + nodeId);
            for (Integer sensorType : request.getSensorTypes()) {
                sb.append("\n - " + String.valueOf(sensorType));
            }
            Log.d(TAG, sb.toString());
            app.getGoogleApiMessenger().sendMessageToNode(MessageHandler.PATH_SENSOR_DATA_REQUEST, request.toJson(), nodeId);
        } catch (Exception ex) {
            Log.w(TAG, "Unable to send sensor data request: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Sets the end timestamps of all available sensor data requests to now
     * and sends them to the assigned nodes
     */
    private void stopRequestingSensorEventData() {
        if (!isRequestingSensorEventData()) {
            return;
        }
        try {
            Log.v(TAG, "Stopping to request sensor event data");
            for (Map.Entry<String, SensorDataRequest> sensorDataRequestEntry : sensorDataRequests.entrySet()) {
                sensorDataRequestEntry.getValue().setEndTimestamp(System.currentTimeMillis());
            }
            sendSensorEventDataRequests();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Creates or updates a visualization card and notifies the @cardListAdapter
     * in order to update the @ChartView with the provided @DataBatch
     */
    private void renderDataBatch(DataBatch dataBatch, String sourceNodeId) {
        try {
            // Don't render if the data isn't requested anymore.
            // This can happen if the request has been updated but data
            // has already been sent by the request receiver
            if (!isRequestingSensorEventData(sourceNodeId, dataBatch.getSource())) {
                return;
            }

            // get the visualization card
            String key = VisualizationCardData.generateKey(sourceNodeId, dataBatch.getSource());
            VisualizationCardData visualizationCardData = cardListAdapter.getVisualizationCard(key);

            // create a new card if not yet avaialable
            if (visualizationCardData == null) {
                String deviceName = app.getGoogleApiMessenger().getNodeName(sourceNodeId);
                visualizationCardData = new VisualizationCardData(key);
                visualizationCardData.setHeading(dataBatch.getSource());
                visualizationCardData.setSubHeading(deviceName);
                cardListAdapter.add(visualizationCardData);
                cardListAdapter.notifyDataSetChanged();
            }

            // update the card data
            DataBatch visualizationDataBatch = visualizationCardData.getDataBatch();
            if (visualizationDataBatch == null) {
                visualizationDataBatch = dataBatch;
                visualizationDataBatch.setCapacity(DataBatch.CAPACITY_UNLIMITED);
                visualizationCardData.setDataBatch(visualizationDataBatch);
            } else {
                visualizationDataBatch.addData(dataBatch.getDataList());
            }

            cardListAdapter.invalidateVisualization(visualizationCardData.getKey());
        } catch (Exception ex) {
            Log.w(TAG, "Unable to render data batch: " + ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Removes all visualizations from the card list adapter that are currently
     * not requested
     */
    private void removeUnneededVisualizationCards() {
        Map<String, VisualizationCardData> removableVisualizationCards = new HashMap<>();

        Map<String, VisualizationCardData> visualizationCards = cardListAdapter.getVisualizationCards();
        for (Map.Entry<String, VisualizationCardData> visualizationCardDataEntry : visualizationCards.entrySet()) {
            String nodeId = visualizationCardDataEntry.getKey();
            VisualizationCardData visualizationCard = visualizationCardDataEntry.getValue();

            // check if the data that the current card holds should be rendered
            if (!isRequestingSensorEventData(nodeId, visualizationCard.getDataBatch().getSource())) {
                removableVisualizationCards.put(nodeId, visualizationCard);
                continue;
            }
        }

        for (Map.Entry<String, VisualizationCardData> visualizationCardDataEntry : removableVisualizationCards.entrySet()) {
            Log.d(TAG, "Removing unneeded visualization card: " + visualizationCardDataEntry.getValue().getHeading());
            cardListAdapter.remove(visualizationCardDataEntry.getValue());
        }
    }

    /**
     * Checks if there are connected wearables that don't have the app running.
     */
    private void checkForConnectedButUnreachableNodes() {
        Log.d(TAG, "Looking for connected but unreachable nodes");
        List<String> notReachableNodeIds = app.getReachabilityChecker().getNotReachableNodeIds();
        for (String notReachableNodeId : notReachableNodeIds) {
            DataRequest dataRequest = sensorDataRequests.get(notReachableNodeId);
            AlertDialog reachabilityDialog = reachabilityDialogs.get(notReachableNodeId);
            if (dataRequest == null && reachabilityDialog == null) {
                showAppNotRunningDialog(notReachableNodeId);
                return;
            }
        }
    }

    /**
     * Creates and shows a dialog that informs the user that a device is connected
     * but not reachable because the app is currently not running
     */
    private void showAppNotRunningDialog(String nodeId) {
        Log.d(TAG, "Showing app not running dialog");
        String nodeName = app.getGoogleApiMessenger().getNodeName(nodeId);
        AlertDialog reachabilityDialog = new AlertDialog.Builder(this)
                .setTitle(getString(R.string.android_wear_connected))
                .setMessage(getString(R.string.device_connected_but_unreachable).replace("[DEVICENAME]", nodeName))
                .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        app.getGoogleApiMessenger().updateLastConnectedNodes();
                        app.getReachabilityChecker().checkReachabilities(null);
                    }
                })
                .setIcon(R.drawable.ic_watch_black_48dp)
                .create();

        reachabilityDialogs.put(nodeId, reachabilityDialog);
        reachabilityDialog.show();
    }

}