/*
 * Copyright (c) 2020 ETSI.  All rights reserved.
 */

import _ from 'lodash';
import { connect } from 'react-redux';
import React, { Component } from 'react';
import { StatusCodes } from 'http-status-codes';
import { TopAppBarFixedAdjust } from '@rmwc/top-app-bar';

import TopBarContainer from './top-bar-container';
import HomePageContainer from './home/home-page-container';
import SandboxPageContainer from './sandbox/sandbox-page-container';
import DashboardPageContainer from './dashboard/dashboard-page-container';
import Footer from '../components/footer';

// import SignInDialog from '../components/dialogs/sign-in-dialog';
import SignInOAuthDialog from '../components/dialogs/sign-in-oauth-dialog';
import SignInWaitDialog from '../components/dialogs/sign-in-wait-dialog';
import SessionTerminatedDialog from '../components/dialogs/session-terminated-dialog';
import HelpGettingStartedDialog from '../components/dialogs/help-getting-started-dialog';

import * as meepAuthSvcRestApiClient from '../../../../../js-packages/meep-auth-svc-client/src/index.js';
import * as meepPlatformCtrlRestApiClient from '../../../../../js-packages/meep-platform-ctrl-client/src/index.js';
import * as meepSandboxCtrlRestApiClient from '../../../../../js-packages/meep-sandbox-ctrl-client/src/index.js';
import * as meepMetricsEngineRestApiClient from '../../../../../js-packages/meep-metrics-engine-client/src/index.js';
import * as meepGisEngineRestApiClient from '../../../../../js-packages/meep-gis-engine-client/src/index.js';
import * as meepMonEngineRestApiClient from '../../../../../js-packages/meep-mon-engine-client/src/index.js';

import {
  HOST_PATH,
  PAGE_HOME,
  PAGE_SANDBOX,
  PAGE_DASHBOARD,
  DEFAULT_NO_NETWORK_FILE_SELECTED,
  MIN_SCREEN_WIDTH,
  MIN_SCREEN_HEIGHT,
  DIALOG_SIGN_IN,
  DIALOG_SIGN_IN_WAIT,
  DIALOG_SESSION_TERMINATED,
  DIALOG_HELP_GETTING_STARTED,
  ALERT_DEGRADED_NETWORK,
  STATUS_SIGNED_IN,
  STATUS_SIGNING_IN,
  STATUS_SIGNED_OUT
} from '../app-constants';

import {
  parseScenario
} from '../util/scenario-utils';

import {
  uiChangeCurrentDialog,
  uiChangeDialogErrorMsg,
  uiChangeHelpOnSignIn,
  uiChangeCurrentAlert,
  uiChangeCurrentPage,
  uiChangeSandboxName,
  uiSandboxChangeNetworkFileSelected,
  uiSandboxChangeNetworkInfo,
  uiSandboxChangeNbStationaryUe,
  uiSandboxChangeNbLowVelocityUe,
  uiSandboxChangeNbHighVelocityUe,
  uiSandboxChangeStationaryUeList,
  uiSandboxChangeLowVelocityUeList,
  uiSandboxChangeHighVelocityUeList,
  uiSandboxChangePauseButton,
  uiSandboxChangeApiDetailedData,
  uiChangeSignInStatus,
  uiChangeSignInUsername,
  uiChangeSignInUserRole,
  uiSandboxChangeUpdateUeInProgressCount,
  uiSandboxChangeActivationInProgressCount,
  uiSandboxChangeActivationInProgressScenarioName
} from '../state/ui';

import {
  sboxChangeScenario,
  sboxChangeTable,
  sboxChangeMapUeList,
  sboxChangeMapPoaList,
  sboxChangeMapComputeList,
  sboxChangeApiTable
} from '../state/sbox';

const SBOX_ERR_COUNT_MAX = 5;
const SBOX_CREATE_TIMEOUT = 30000; // 30 sec
const SBOX_REFRESH_INTERVAL = 1000; // 1 sec
const SESSION_KEEPALIVE_INTERVAL = 600000; // 10 min

// REST API Clients
var basepathAuthSvc = '';
var basepathPlatformCtrl = '';
var basepathSandboxCtrl = '';
var basepathMetricsEngineCtrl = '';
var basepathGisEngineCtrl = '';
var basepathMonEngine = '';

const apiTableMaxSize = 100;
var metricsQuery = {
  tags: [{
    name: 'logger_name',
    value: 'meep-loc-serv,meep-rnis,meep-wais'
  }],
  fields: ['id', 'endpoint', 'url', 'method', 'resp_code', 'resp_body', 'body', 'proc_time', 'logger_name', 'direction'],
  scope: {
    limit: apiTableMaxSize,
    duration: '2s'
  }
};

function importAll(r) {
  return r.keys().map(r);
}

/*eslint-disable */
//forcing imports of every file in img directory
const images = importAll(require.context('../../img', false, /\.(png|jpe?g|svg)$/));
/*eslint-enable */

class AppContainer extends Component {
  constructor(props) {
    super(props);
    this.creationSandboxTimer = null;
    this.sboxPageRefreshIntervalTimer = null;
    this.sandboxErrorCount = 0;
    this.activeScenarioName = '';
    this.sessionKeepaliveTimer = null;
    this.refreshApiConsole = false;
    this.refreshUePaths = false;
    this.uePathUpdateRequired = false;

    basepathAuthSvc = HOST_PATH + '/auth/v1';
    meepAuthSvcRestApiClient.ApiClient.instance.basePath = basepathAuthSvc.replace(/\/+$/, '');
    basepathPlatformCtrl = HOST_PATH + '/platform-ctrl/v1';
    meepPlatformCtrlRestApiClient.ApiClient.instance.basePath = basepathPlatformCtrl.replace(/\/+$/, '');
    basepathMonEngine = HOST_PATH + '/mon-engine/v1';
    meepMonEngineRestApiClient.ApiClient.instance.basePath = basepathMonEngine.replace(/\/+$/, '');
    this.meepAuthApi = new meepAuthSvcRestApiClient.AuthApi();
    this.meepScenarioConfigurationApi = new meepPlatformCtrlRestApiClient.ScenarioConfigurationApi();
    this.meepSandboxControlApi = new meepPlatformCtrlRestApiClient.SandboxControlApi();
    this.meepActiveScenarioApi = new meepSandboxCtrlRestApiClient.ActiveScenarioApi();
    this.meepEventsApi = new meepSandboxCtrlRestApiClient.EventsApi();
    this.meepMetricsEngineApi = new meepMetricsEngineRestApiClient.MetricsApi();
    this.meepGisAutomationApi = new meepGisEngineRestApiClient.AutomationApi();
    this.meepGeoDataApi = new meepGisEngineRestApiClient.GeospatialDataApi();
    this.meepMonEngineApi = new meepMonEngineRestApiClient.PodStatesApi();
  }

  componentWillMount() {
    // Initialize undefined states
    if (this.props.activationInProgressCount === undefined) {
      this.props.changeActivationInProgressCount(-1);
    }
    if (this.props.activationInProgressScenarioName === undefined) {
      this.props.changeActivationInProgressScenarioName('');
    }
    if (this.props.updateUeInProgressCount === undefined) {
      this.props.changeUpdateUeInProgressCount(-1);
    }
    if (this.props.stationaryUeList === undefined) {
      this.props.changeStationaryUeList([]);
    }
    if (this.props.lowVelocityUeList === undefined) {
      this.props.changeLowVelocityUeList([]);
    }
    if (this.props.highVelocityUeList === undefined) {
      this.props.changeHighVelocityUeList([]);
    }
    if (this.props.helpOnSignIn === undefined) {
      this.props.changeHelpOnSignIn(true);
    }

    // Handle OAuth login in progress
    if (this.props.signInStatus === STATUS_SIGNING_IN) {
      // Get Sandbox name from query params
      let params = (new URL(document.location)).searchParams;
      let sandboxName = params.get('sbox');
      if (sandboxName) {
        // Store Sandbox name
        this.props.changeSandboxName(sandboxName);

        // Get user name & role to configure frontend
        let userName = params.get('user');
        if (userName) {
          this.props.changeSignInUsername(userName);
        }
        let userRole = params.get('role');
        if (userRole) {
          this.props.changeSignInUserRole(userRole);
        }

        // Reset network
        this.props.changeNetworkFileSelected(DEFAULT_NO_NETWORK_FILE_SELECTED);

        // Set current dialog to signing in
        this.props.changeCurrentDialog(DIALOG_SIGN_IN_WAIT);

        // Remove Query params from URL now that we have stored them
        window.history.replaceState({}, document.title, '/');
      } else {
        // Sign in failed
        this.logout();
        this.props.changeCurrentDialog(null);
        this.props.changeSignInStatus(STATUS_SIGNED_OUT);
      }
    }
  }

  componentDidMount() {
    document.title = 'ETSI MEC Sandbox';
    this.setBasePaths();
    this.startTimers();
    this.monitorTabFocus();
  }

  componentDidUpdate(prevProps) {
    // If number of moving UEs has increased, request a UE path refresh asap
    if (this.props.nbLowVelocityUe > prevProps.nbLowVelocityUe ||
      this.props.nbHighVelocityUe > prevProps.nbHighVelocityUe) {
      this.uePathUpdateRequired = true;
    }

    // Set new basepaths if sandbox name updated
    if (this.props.sandboxName !== prevProps.sandboxName) {
      this.setBasePaths();
    }
  }

  // Timers
  startTimers() {
    this.refreshApiConsole = true;
    this.startSandboxPageRefresh();
  }
  stopTimers() {
    this.stopSandboxPageRefresh();
  }

  startSandboxCreationTimer() {
    // Delay to let sandbox creation complete
    this.creationSandboxTimer = setTimeout(() => {
      this.createSandboxTimerExpiry();
    }, SBOX_CREATE_TIMEOUT);
  }
  stopSandboxCreationTimer() {
    if (this.creationSandboxTimer) {
      clearTimeout(this.creationSandboxTimer);
      this.creationSandboxTimer = null;
    }
  }

  // Sandbox page refresh
  startSandboxPageRefresh() {
    if (!this.sboxPageRefreshIntervalTimer) {
      this.sboxPageRefreshIntervalTimer = setInterval(
        () => {
          if (this.props.signInStatus === STATUS_SIGNED_IN) {
            this.checkSignedIn();
          } else if (this.props.signInStatus === STATUS_SIGNING_IN) {
            this.checkSandboxState();
          }

          if (this.props.page === PAGE_SANDBOX) {
            if (this.props.sandboxName) {
              if (this.props.activationInProgressCount !== -1) {
                this.props.changeActivationInProgressCount(this.props.activationInProgressCount - 1);
              } else {
                if (this.props.updateUeInProgressCount !== -1) {
                  this.props.changeUpdateUeInProgressCount(this.props.updateUeInProgressCount - 1);
                } else {
                  this.refreshConfigPane();
                }
              }
              this.refreshScenario();
              this.refreshMap();
              this.refreshMetricsTable();
            }
          }
        },
        SBOX_REFRESH_INTERVAL
      );
    }
  }
  stopSandboxPageRefresh() {
    if (this.sboxPageRefreshIntervalTimer) {
      clearInterval(this.sboxPageRefreshIntervalTimer);
      this.sboxPageRefreshIntervalTimer = null;
    }
    this.stopSandboxCreationTimer();
  }

  // Session Keep-alive
  startSessionKeepaliveTimer() {
    if (!this.sessionKeepaliveTimer) {
      // Send heartbeat
      this.sendKeepalive();

      // Start keepalive timer
      this.sessionKeepaliveTimer = setInterval(
        () => {
          // Send heartbeat
          this.sendKeepalive();
        },
        SESSION_KEEPALIVE_INTERVAL
      );
    }
  }
  stopSessionKeepaliveTimer() {
    if (this.sessionKeepaliveTimer) {
      clearInterval(this.sessionKeepaliveTimer);
      this.sessionKeepaliveTimer = null;
    }
  }

  monitorTabFocus() {
    var hidden, visibilityChange;
    if (typeof document.hidden !== 'undefined') {
      // Opera 12.10 and Firefox 18 and later support
      hidden = 'hidden';
      visibilityChange = 'visibilitychange';
    } else if (typeof document.msHidden !== 'undefined') {
      hidden = 'msHidden';
      visibilityChange = 'msvisibilitychange';
    } else if (typeof document.webkitHidden !== 'undefined') {
      hidden = 'webkitHidden';
      visibilityChange = 'webkitvisibilitychange';
    }

    const handleVisibilityChange = () => {
      if (document[hidden]) {
        this.stopTimers();
      } else {
        this.startTimers();
      }
    };

    // Warn if the browser doesn't support addEventListener or the Page Visibility API
    if (
      typeof document.addEventListener === 'undefined' ||
      hidden === undefined
    ) {
      // TODO: consider showing an alert
      // console.log('This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.');
    } else {
      // Handle page visibility change
      document.addEventListener(
        visibilityChange,
        handleVisibilityChange,
        false
      );
    }
  }

  setBasePaths() {
    basepathSandboxCtrl = HOST_PATH + '/' + this.props.sandboxName + '/sandbox-ctrl/v1';
    meepSandboxCtrlRestApiClient.ApiClient.instance.basePath = basepathSandboxCtrl.replace(/\/+$/, '');
    basepathMetricsEngineCtrl = HOST_PATH + '/' + this.props.sandboxName + '/metrics/v2';
    meepMetricsEngineRestApiClient.ApiClient.instance.basePath = basepathMetricsEngineCtrl.replace(/\/+$/, '');
    basepathGisEngineCtrl = HOST_PATH + '/' + this.props.sandboxName + '/gis/v1';
    meepGisEngineRestApiClient.ApiClient.instance.basePath = basepathGisEngineCtrl.replace(/\/+$/, '');
  }

  setMainContent(targetId) {
    this.props.changeCurrentPage(targetId);
  }

  createSandboxTimerExpiry() {
    if (this.props.signInStatus === STATUS_SIGNING_IN) {
      this.logout();
      this.props.changeCurrentDialog(null);
      this.props.changeSignInStatus(STATUS_SIGNED_OUT);
    }
  }

  closeDialog() {
    this.props.changeCurrentDialog(null);
  }

  signInProcedure() {
    if (this.props.signInStatus === STATUS_SIGNED_IN) {
      this.logout();
    } else {
      this.props.changeDialogErrorMsg('');
      this.props.changeCurrentDialog(DIALOG_SIGN_IN);
    }
  }

  /**
   * Callback function to receive the result of the loginUser operation.
   * @callback module:api/AuthApi~loginUserCallback
   * @param {String} error Error message, if any.
   * @param {module:model/Sandbox} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  loginCb(error, data) {
    if (error) {
      var errMsg;
      switch (error.status) {
      case StatusCodes.UNAUTHORIZED:
        errMsg = 'Invalid credentials. Please try again...';
        break;
      case StatusCodes.SERVICE_UNAVAILABLE:
        errMsg = 'All MEC Sandboxes are currently in use. Please try again later...';
        break;
      default:
        errMsg = 'Login failure. Please try again...';
        break;
      }
      this.props.changeDialogErrorMsg(errMsg);
      this.props.changeCurrentDialog(DIALOG_SIGN_IN);
      return;
    }

    // Get sandbox name on successful login
    var sandboxName = data ? data.name : '';

    // Handle sandbox creation error
    if (!sandboxName) {
      this.props.changeDialogErrorMsg('Failed to sign in, try again...');
      this.props.changeCurrentDialog(DIALOG_SIGN_IN);
      return;
    }

    // Signing in
    this.props.changeDialogErrorMsg('');

    // Set sandbox name to be used in API requests
    this.props.changeSandboxName(sandboxName);
    this.setBasePaths();

    // Reset 
    this.props.changeNetworkFileSelected(DEFAULT_NO_NETWORK_FILE_SELECTED);

    // Display dialog while waiting for sandbox creation to complete
    this.props.changeCurrentDialog(DIALOG_SIGN_IN_WAIT);
    this.props.changeSignInStatus(STATUS_SIGNING_IN);
    this.startSandboxCreationTimer();

    // Start session keepalive timer
    this.startSessionKeepaliveTimer();
  }

  submitUserSignInInfo(creds) {
    var formParams = {
      'username': creds.userName,
      'password': creds.password
    };

    this.props.changeSignInUsername(creds.userName);
    this.meepAuthApi.loginUser(formParams, (error, data, response) => {
      this.loginCb(error, data, response);
    });
  }

  signInOAuth(provider) {
    // Stop timers & Set state to signing in
    this.stopTimers();
    this.props.changeCurrentDialog(DIALOG_SIGN_IN_WAIT);
    this.props.changeSignInStatus(STATUS_SIGNING_IN);

    // Navigate to OAuth login endpoint
    window.location.href = HOST_PATH + '/auth/v1/login?provider=' + provider;
  }

  updateAutomation(pauseButtonEnabled) {
    this.meepGisAutomationApi.setAutomationStateByName('MOVEMENT', !pauseButtonEnabled);
    this.meepGisAutomationApi.setAutomationStateByName('MOBILITY', !pauseButtonEnabled);
  }

  clearUpConfigPane() {
    this.props.changeNetworkFileSelected(DEFAULT_NO_NETWORK_FILE_SELECTED);
    this.props.changeNetworkInfo('');
    this.props.changeNbHighVelocityUe(0);
    this.props.changeNbLowVelocityUe(0);
    this.props.changeNbStationaryUe(0);
    this.props.changeStationaryUeList([]);
    this.props.changeLowVelocityUeList([]);
    this.props.changeHighVelocityUeList([]);
    this.props.changeApiTable(null);
    this.props.changeApiDetailedData(null);
    this.props.changePauseButton(false);
    this.updateAutomation(false);
  }

  /**
   * Callback function to receive the result of the triggerWatchdog operation.
   * @callback module:api/AuthApi~triggerWatchdogCallback
   * @param {String} error Error message, if any.
   * @param data This operation does not return a value.
   * @param {String} response The complete HTTP response.
   */
  triggerWatchdogCb(error) {
    if (error) {
      return;
    }
  }

  sendKeepalive() {
    this.meepAuthApi.triggerWatchdog((error, data, response) => {
      this.triggerWatchdogCb(error, data, response);
    });
  }

  /**
   * Callback function to receive the result of the logout operation.
   * @callback module:api/AuthApi~logoutCallback
   * @param {String} error Error message, if any.
   * @param data This operation does not return a value.
   * @param {String} response The complete HTTP response.
   */
  logoutCb() {
    this.props.changeSignInStatus(STATUS_SIGNED_OUT);
    this.props.changeSandboxName('');
    // this.deleteSandbox(this.props.sandboxName);
    this.updateScenario(null);
    this.props.changeActivationInProgressCount(-1);
    this.props.changeActivationInProgressScenarioName('');

    this.props.changeMapUeList([]);
    this.props.changeMapPoaList([]);
    this.props.changeMapComputeList([]);

    if (this.props.page !== PAGE_HOME) {
      this.setMainContent(PAGE_HOME);
    }
  }

  logout() {
    this.stopSessionKeepaliveTimer();
    this.meepAuthApi.logout((error, data, response) => {
      this.logoutCb(error, data, response);
    });
  }

  /**
   * Callback function to receive the result of the getStates operation.
   * @callback module:api/MonEngineApi~getStatesCallback
   * @param {String} error Error message, if any.
   * @param {module:model/GetStates} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getStatesCb(error, data, response) {
    if (error) {
      if (error.status === StatusCodes.UNAUTHORIZED) {
        this.stopSandboxCreationTimer();
        this.props.changeCurrentDialog(null);
        this.props.changeSignInStatus(STATUS_SIGNED_OUT);
      }
      return;
    }
    for (var i in response.body.podStatus) {
      if (response.body.podStatus[i].logicalState !== 'Running') {
        return;
      }
    }

    // all pods runnings, sandbox is ready
    this.stopSandboxCreationTimer();

    this.props.changeSignInStatus(STATUS_SIGNED_IN);
    this.props.changeCurrentPage(PAGE_SANDBOX);

    this.props.changeCurrentDialog(null);
    if (this.props.helpOnSignIn) {
      this.props.changeCurrentDialog(DIALOG_HELP_GETTING_STARTED);
    }
  }

  checkSandboxState() {
    // On page reload, restart sandbox creation timer to handle failure case
    if (!this.creationSandboxTimer) {
      this.startSandboxCreationTimer();
    }

    var sandboxName = this.props.sandboxName;
    if (sandboxName !== '') {
      var queryParams = {
        'type': 'core',
        'sandbox': sandboxName,
        'long': false
      };

      this.meepMonEngineApi.getStates(queryParams, (error, data, response) => {
        this.getStatesCb(error, data, response);
      });
    }
  }

  /**
   * Callback function to receive the result of the getSandbox operation.
   * @callback module:api/SandboxControlApi~getSandboxCallback
   * @param {String} error Error message, if any.
   * @param {module:model/Sandbox} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getSandboxCb(error) {
    if (error) {
      // Log user out if sandbox has been destroyed
      if (this.props.signInStatus === STATUS_SIGNED_IN) {
        if ((error.status === StatusCodes.UNAUTHORIZED) || (error.status === StatusCodes.NOT_FOUND)) {
          this.logout();
          this.props.changeCurrentDialog(DIALOG_SESSION_TERMINATED);
        } else if (this.sandboxErrorCount++ > SBOX_ERR_COUNT_MAX) {
          this.props.changeCurrentAlert(ALERT_DEGRADED_NETWORK);
        }
      }
      return;
    }

    // Reset error counter on success
    this.sandboxErrorCount = 0;

    // Clear bad network alert, if set
    if (this.props.currentAlert === ALERT_DEGRADED_NETWORK) {
      this.props.changeCurrentAlert(null);
    }
  }

  checkSignedIn() {
    // On page reload, restart session keepalive timer
    if (!this.sessionKeepaliveTimer) {
      this.startSessionKeepaliveTimer();
    }

    this.meepSandboxControlApi.getSandbox(this.props.sandboxName, (error, data, response) => {
      this.getSandboxCb(error, data, response);
    });
  }

  /**
   * Callback function to receive the result of the getActiveScenario operation.
   * @callback module:api/ScenarioExecutionApi~getActiveScenarioCallback
   * @param {String} error Error message, if any.
   * @param {module:model/Scenario} data The data returned by the service call.
   */
  getActiveScenarioCb(error, data) {
    if ((error !== null) || (!data.deployment)) {
      if (error.status === 404) {
        this.updateScenario(null);
        this.props.changeApiTable(null);
        this.props.changeApiDetailedData(null);
      }
      return;
    }

    // Store & Process deployed scenario
    if (this.props.activationInProgressCount === -1) {
      this.updateScenario(data);
    } else {
      if (this.props.activationInProgressScenarioName === data.name) {
        this.updateScenario(data);
        this.props.changeActivationInProgressCount(-1);
      }
    }
  }

  // Change & process scenario
  updateScenario(scenario) {
    // Change scenario state
    this.props.changeScenario(scenario);
    if (scenario) {
      this.activeScenarioName = scenario.name;
    } else {
      if (this.activeScenarioName !== '') {
        this.activeScenarioName = '';
        //only clear to terminate a scenario, not to replace one already active with another one
        if (this.props.activationInProgressCount === -1) {
          this.clearUpConfigPane();
        }
      }
    }

    // Parse Scenario & update table
    var parsedScenario = parseScenario(scenario);
    this.props.changeTable(parsedScenario ? parsedScenario.table : null);
  }

  // Refresh Active scenario
  refreshScenario() {
    this.meepActiveScenarioApi.getActiveScenario({ minimize: 'true' }, (error, data) =>
      this.getActiveScenarioCb(error, data)
    );
  }

  syncUeButtons(mapUeList) {
    var nbHv = 0, nbLv = 0, nbZv = 0;
    var found = false;
    var i, j;

    if (!mapUeList || this.props.updateUeInProgressCount !== -1) {
      return;
    }

    for (j = 0; j < mapUeList.length; j++) {
      found = false;
      for (i = 0; i < this.props.highVelocityUeList.length; i++) {
        if (this.props.highVelocityUeList[i].name === mapUeList[j].assetName) {
          nbHv++;
          found = true;
        }
      }
      if (!found) {
        for (i = 0; i < this.props.lowVelocityUeList.length; i++) {
          if (this.props.lowVelocityUeList[i].name === mapUeList[j].assetName) {
            nbLv++;
            found = true;
          }
        }
      }
      if (!found) {
        for (i = 0; i < this.props.stationaryUeList.length; i++) {
          if (this.props.stationaryUeList[i].name === mapUeList[j].assetName) {
            nbZv++;
          }
        }
      }
    }
    this.props.changeNbLowVelocityUe(nbLv);
    this.props.changeNbHighVelocityUe(nbHv);
    this.props.changeNbStationaryUe(nbZv);
  }

  /**
   * Callback function to receive the result of the getAssetData operation.
   * @callback module:api/GeospatialDataApi~getAssetDataCallback
   * @param {String} error Error message, if any.
   * @param {module:model/GeoDataAssetList} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getUeAssetDataCb(error, data) {
    if (error !== null) {
      return;
    }
    var ueList = data.geoDataAssets ? _.sortBy(data.geoDataAssets, ['assetName']) : [];

    // Update UE list with new location & paths (if available)
    if (this.refreshUePaths) {
      // Update UE list with latest paths
      this.props.changeMapUeList(ueList);
      this.refreshUePaths = false;
    } else {
      // Update UE list using paths from previous UE list
      var prevUeList = this.props.map.ueList;
      var prevIndex = 0;
      for (var i = 0; i < ueList.length; i++) {
        var found = false;
        for (var j = prevIndex; j < prevUeList.length; j++) {
          if (ueList[i].assetName === prevUeList[j].assetName) {
            ueList[i].path = prevUeList[j].path;
            found = true;
            prevIndex = j + 1;
            break;
          }
        }
        // If new UE in list, refresh UE paths on next request
        if (!found) {
          this.uePathUpdateRequired = true;
        }
      }
      this.props.changeMapUeList(ueList);
    }

    // Sync UE button state with backend
    this.syncUeButtons(ueList);
  }

  /**
   * Callback function to receive the result of the getAssetData operation.
   * @callback module:api/GeospatialDataApi~getAssetDataCallback
   * @param {String} error Error message, if any.
   * @param {module:model/GeoDataAssetList} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  getPoaAssetDataCb(error, data) {
    if (error !== null) {
      return;
    }

    // Update POA list
    this.props.changeMapPoaList(data.geoDataAssets ? _.sortBy(data.geoDataAssets, ['assetName']) : []);
  }

  // /**
  //  * Callback function to receive the result of the getAssetData operation.
  //  * @callback module:api/GeospatialDataApi~getAssetDataCallback
  //  * @param {String} error Error message, if any.
  //  * @param {module:model/GeoDataAssetList} data The data returned by the service call.
  //  * @param {String} response The complete HTTP response.
  //  */
  // getComputeAssetDataCb(error, data) {
  //   if (error !== null) {
  //     return;
  //   }

  //   // Update Compute list
  //   this.props.changeMapComputeList(data.geoDataAssets ? _.sortBy(data.geoDataAssets, ['assetName']) : []);
  // }

  // Refresh Map
  refreshMap() {
    // If UE cound has increased, include UE paths in the next request
    if (this.uePathUpdateRequired) {
      this.refreshUePaths = true;
      this.uePathUpdateRequired = false;
    }
    this.meepGeoDataApi.getAssetData({ assetType: 'UE', excludePath: !this.refreshUePaths }, (error, data) =>
      this.getUeAssetDataCb(error, data)
    );
    this.meepGeoDataApi.getAssetData({ assetType: 'POA' }, (error, data) =>
      this.getPoaAssetDataCb(error, data)
    );
    // this.meepGeoDataApi.getAssetData({assetType: 'COMPUTE'}, (error, data) =>
    //   this.getComputeAssetDataCb(error, data)
    // );
  }

  /**
   * Callback function to receive the result of the get automation (movement) operation.
   * @callback module:api/ScenarioAutomationApi~automationCallback
   * @param {String} error Error message, if any.
   */
  getAutomationCb(error, data) {
    if (error) {
      return;
    }

    var currentPauseButton = !data.active;

    if (currentPauseButton !== this.props.pauseButton) {
      this.props.changePauseButton(currentPauseButton);
    }
  }

  refreshConfigPane() {

    if (this.activeScenarioName !== '') {
      this.meepGisAutomationApi.getAutomationStateByName('MOVEMENT', (error, data, response) =>
        this.getAutomationCb(error, data, response)
      );
    }
  }

  //get oldest data in the data that matches an entry in oldData
  mergeApiTableData(...arrays) {
    let jointArray = [];
    let resultArray = [];
    arrays.forEach(array => {
      if (array) {
        jointArray = [...array, ...jointArray];
      }
    });

    var len = jointArray.length;
    var assoc = {};

    while (len--) {
      var itemStr = jointArray[len].id + '-' + jointArray[len].loggerName;
      var item = jointArray[len];

      if (!assoc[itemStr]) {
        resultArray.unshift(item);
        assoc[itemStr] = true;
      }
    }
    return resultArray.slice(0, apiTableMaxSize);
  }

  /**
   * Callback function to receive the result of the postHttpQuery operation.
   * @callback module:api/MetricsApi~postHttpQuery
   * @param {String} error Error message, if any.
   * @param {module:model/HttpQuery} data The data returned by the service call.
   * @param {String} response The complete HTTP response.
   */
  postHttpQueryCb(error, data) {
    if (error !== null) {
      return;
    }

    // only update if values present, otherwise means no changes
    if (data) {
      data.values = this.mergeApiTableData(this.props.tableData.values, data.values);
      this.props.changeApiTable(data);
    }
  }

  refreshMetricsTable() {
    if (this.props.networkFileSelected !== DEFAULT_NO_NETWORK_FILE_SELECTED) {
      // If polling just started, refresh entire API console list
      if (this.refreshApiConsole) {
        metricsQuery.scope.duration = '1d';
        this.refreshApiConsole = false;
      } else {
        metricsQuery.scope.duration = '2s';
      }
      this.meepMetricsEngineApi.postHttpQuery(metricsQuery, (error, data, response) => {
        this.postHttpQueryCb(error, data, response);
      });
    }
  }

  renderPage() {
    switch (this.props.page) {
    case PAGE_HOME:
      return (
        <HomePageContainer
          style={{ width: '100%', height: '100%' }}
        />
      );

    case PAGE_SANDBOX:
      return (
        <SandboxPageContainer
          style={{ width: '100%' }}
          scenarioApi={this.meepScenarioConfigurationApi}
          activateApi={this.meepActiveScenarioApi}
          eventsApi={this.meepEventsApi}
          metricsApi={this.meepMetricsEngineApi}
          automationApi={this.meepGisAutomationApi}
        />
      );

    case PAGE_DASHBOARD:
      return (
        <DashboardPageContainer
          style={{ width: '100%' }}
        />
      );

    default:
      return null;
    }
  }

  renderDialogs() {
    return (
      <>

        {/* <SignInDialog
          title='Sign In'
          open={this.props.currentDialog === DIALOG_SIGN_IN}
          onClose={() => {
            this.closeDialog();
          }}
          onSubmit={creds => this.submitUserSignInInfo(creds)}
          errorMsg={this.props.dialogErrorMsg}
        /> */}
        <SignInOAuthDialog
          title='Sign in with'
          open={this.props.currentDialog === DIALOG_SIGN_IN}
          onSignIn={provider => this.signInOAuth(provider)}
          onClose={() => {
            this.closeDialog();
          }}
        />
        <SignInWaitDialog
          title='Initializing sandbox'
          open={this.props.currentDialog === DIALOG_SIGN_IN_WAIT}
          onClose={() => {
            this.closeDialog();
          }}
        />
        <SessionTerminatedDialog
          title='Session ended'
          open={this.props.currentDialog === DIALOG_SESSION_TERMINATED}
          onClose={() => {
            this.closeDialog();
          }}
        />
        <HelpGettingStartedDialog
          title='Getting started'
          open={this.props.currentDialog === DIALOG_HELP_GETTING_STARTED}
          onClose={() => {
            this.closeDialog();
          }}
        />
      </>
    );
  }

  render() {
    let width = window.innerWidth;
    let height = window.innerHeight;

    if ((width >= MIN_SCREEN_WIDTH) && (height >= MIN_SCREEN_HEIGHT)) {
      return (
        <div>

          {this.renderDialogs()}

          <TopBarContainer
            onClickSignIn={() => this.signInProcedure()}
          />
          <div style={{height: 32}}/>

          <TopAppBarFixedAdjust dense />

          {this.renderPage()}

          <Footer />
        </div>
      );
    } else {
      return (
        <div style={{margin: 20}}>
          <h1>ETSI MEC Sandbox</h1>
          <h3>Unfortunately, MEC Sandbox does not look great with a resolution of {width} x {height}</h3>
          <p>
            Please zoom out and refresh your browser or load the MEC Sandbox from a browser with a
            minimum width of <b>{MIN_SCREEN_WIDTH}</b> pixels.
          </p>
          <p>NOTE: keyboard zoom shortcut is <i>ctrl+- (minus)</i></p>
        </div>
      );
    }
  }
}

const mapStateToProps = state => {
  return {
    currentDialog: state.ui.currentDialog,
    currentAlert: state.ui.currentAlert,
    page: state.ui.page,
    helpOnSignIn: state.ui.helpOnSignIn,
    sandboxName: state.ui.sandboxName,
    mainDrawerOpen: state.ui.mainDrawerOpen,
    signInStatus: state.ui.signInStatus,
    signInUsername: state.ui.signInUsername,
    dialogErrorMsg: state.ui.dialogErrorMsg,
    networkFileSelected: state.ui.networkFileSelected,
    tableData: state.sbox.apiTable,
    highVelocityUeList: state.ui.highVelocityUeList,
    lowVelocityUeList: state.ui.lowVelocityUeList,
    stationaryUeList: state.ui.stationaryUeList,
    nbLowVelocityUe: state.ui.nbLowVelocityUe,
    nbHighVelocityUe: state.ui.nbHighVelocityUe,
    updateUeInProgressCount: state.ui.updateUeInProgressCount,
    activationInProgressCount: state.ui.activationInProgressCount,
    activationInProgressScenarioName: state.ui.activationInProgressScenarioName,
    map: state.sbox.map
  };
};

const mapDispatchToProps = dispatch => {
  return {
    // UI
    changeCurrentDialog: type => dispatch(uiChangeCurrentDialog(type)),
    changeDialogErrorMsg: msg => dispatch(uiChangeDialogErrorMsg(msg)),
    changeCurrentAlert: alert => dispatch(uiChangeCurrentAlert(alert)),
    changeCurrentPage: page => dispatch(uiChangeCurrentPage(page)),
    changeHelpOnSignIn: check => dispatch(uiChangeHelpOnSignIn(check)),
    changeSandboxName: name => dispatch(uiChangeSandboxName(name)),
    changeNetworkFileSelected: name => dispatch(uiSandboxChangeNetworkFileSelected(name)),
    changeNetworkInfo: value => dispatch(uiSandboxChangeNetworkInfo(value)),
    changeNbStationaryUe: value => dispatch(uiSandboxChangeNbStationaryUe(value)),
    changeNbLowVelocityUe: value => dispatch(uiSandboxChangeNbLowVelocityUe(value)),
    changeNbHighVelocityUe: value => dispatch(uiSandboxChangeNbHighVelocityUe(value)),
    changeStationaryUeList: list => dispatch(uiSandboxChangeStationaryUeList(list)),
    changeLowVelocityUeList: list => dispatch(uiSandboxChangeLowVelocityUeList(list)),
    changeHighVelocityUeList: list => dispatch(uiSandboxChangeHighVelocityUeList(list)),
    changePauseButton: checked => dispatch(uiSandboxChangePauseButton(checked)),
    changeApiDetailedData: row => dispatch(uiSandboxChangeApiDetailedData(row)),
    changeSignInStatus: status => dispatch(uiChangeSignInStatus(status)),
    changeSignInUsername: name => dispatch(uiChangeSignInUsername(name)),
    changeSignInUserRole: role => dispatch(uiChangeSignInUserRole(role)),
    // SBOX
    changeScenario: scenario => dispatch(sboxChangeScenario(scenario)),
    changeTable: table => dispatch(sboxChangeTable(table)),
    changeMapUeList: list => dispatch(sboxChangeMapUeList(list)),
    changeMapPoaList: list => dispatch(sboxChangeMapPoaList(list)),
    changeMapComputeList: list => dispatch(sboxChangeMapComputeList(list)),
    changeApiTable: value => dispatch(sboxChangeApiTable(value)),
    changeUpdateUeInProgressCount: count => dispatch(uiSandboxChangeUpdateUeInProgressCount(count)),
    changeActivationInProgressCount: count => dispatch(uiSandboxChangeActivationInProgressCount(count)),
    changeActivationInProgressScenarioName: name => dispatch(uiSandboxChangeActivationInProgressScenarioName(name))
  };
};

const ConnectedAppContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(AppContainer);

export default ConnectedAppContainer;
