import React from 'react';
import axios from 'axios';
import './App.css';
import Box from './components/UI/Box/Box';
import Configuration from './containers/Configuration/Configuration';
import {connect} from 'react-redux';
import DependencyList from './components/DependencyList/DependencyList';
import DownloadProgress from './components/DownloadProgress/DownloadProgress';
import ErrorDetails from './components/ErrorDetails/ErrorDetails';
import Grid from './components/UI/Grid/Grid';
import Loading from './components/UI/Loading/Loading';
import Modal from './components/UI/Modal/Modal';
import MountItems from './containers/MountItems/MountItems';
import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay';
import Text from './components/UI/Text/Text';
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
import IPCContainer from './containers/IPCContainer/IPCContainer';

const Constants = require('./constants');
const Scheduler = require('node-schedule');

class App extends IPCContainer {
  constructor(props) {
    super(props);

    for (const provider of Constants.PROVIDER_LIST) {
      this.state[provider] = {
        AutoMount: false,
        AutoRestart: false,
        MountLocation: '',
      }
    }

    this.setRequestHandler(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
    this.setRequestHandler(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
    this.setRequestHandler(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
    this.setRequestHandler(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
    this.setRequestHandler(Constants.IPC_Get_State_Reply, this.onGetStateReply);
    this.setRequestHandler(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
    this.setRequestHandler(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
    this.setRequestHandler(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
    this.setRequestHandler(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);

    this.sendRequest(Constants.IPC_Get_State);
    Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
  }

  state = {
    AllowDownload: false,
    DisplayError: false,
    DisplayMainContent: false,
    Error: null,
    ErrorAction: null,
    ErrorCritical: false,
    DownloadActive: false,
    DownloadProgress: 0.0,
    DownloadingDependency: false,
    DownloadName: '',
    DownloadingRelease: false,
    DownloadingUpgrade: false,
    ExtractActive: false,
    LocationsLookup: {},
    MissingDependencies: [],
    Release: 2,
    ReleaseTypes: [
      'Release',   
      'RC',
      'Beta',
      'Alpha',
    ],
    InstalledVersion: 'none',
    UpgradeAvailable: false,
    UpgradeData: {},
    UpgradeDismissed: false,
    Version: -1,
    VersionAvailable: false,
    VersionLookup: {
      Alpha: [
        'unavailable'
      ],
      Beta: [
        'unavailable'
      ],
      RC: [
        'unavailable'
      ],
      Release: [
        'unavailable'
      ],
    }
  };

  checkVersionInstalled = () => {
    this.setState({
      AllowDownload: false,
    }, ()=> {
      const selectedVersion = this.getSelectedVersion();
      if (selectedVersion !== 'unavailable') {
        let dependencies = [];
        if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
          dependencies = this.state.LocationsLookup[selectedVersion].dependencies;
        }

        this.sendRequest(Constants.IPC_Check_Installed, {
          Dependencies: dependencies,
          Version: selectedVersion,
        });
      }
    });
  };

  closeErrorDisplay = () => {
    if (this.state.ErrorAction) {
      this.state.ErrorAction();
    }

    if (this.state.ErrorCritical) {
      this.sendRequest(Constants.IPC_Shutdown);
    } else {
      this.setState({
        DisplayError: false,
        Error: null,
      });
    }
  };

  extractFileNameFromURL = url => {
    const parts = url.split('/');
    return parts[parts.length - 1];
  };

  extractRelease = (data) => {
    if (data.Success) {
      const selectedVersion = this.getSelectedVersion();
      this.sendRequest(Constants.IPC_Extract_Release, {
        Source: data.Destination,
        Version: selectedVersion,
      });
    }

    this.setState({
      DownloadActive: false,
      DownloadProgress: 0.0,
      DownloadingRelease: false,
      ExtractActive: data.Success,
      DownloadName: '',
    });
  };

  getSelectedVersion = () => {
    return this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
  };

  grabReleases = () => {
    if (this.props.AppPlatform !== 'unknown') {
      this.sendRequest(Constants.IPC_Grab_Releases);
      this.sendRequest(Constants.IPC_Grab_UI_Releases);
    }
  };

  handleAutoMountChanged = (storageType, e) => {
    const state = {
      ...this.state[storageType],
      AutoMount: e.target.checked,
    };
    this.setState({
      [storageType]: state,
    }, ()=> {
      this.saveState();
    });
  };

  handleAutoRestartChanged = (storageType, e) => {
    const state = {
      ...this.state[storageType],
      AutoRestart: e.target.checked,
    };
    this.setState({
      [storageType]: state,
    }, ()=> {
      this.saveState();
    });
  };

  handleDependencyDownload = (url) => {
    this.setState({
      DownloadActive: true,
      DownloadingDependency: true,
      DownloadName: this.extractFileNameFromURL(url),
    }, ()=> {
      this.sendRequest(Constants.IPC_Download_File, {
        Filename: this.state.DownloadName,
        URL: url,
      });
    });
  };

  handleMountLocationChanged = (storageType, location) => {
    const state = {
      ...this.state[storageType],
      MountLocation: location,
    };
    this.setState({
      [storageType]: state,
    }, ()=> {
      this.saveState();
    });
  };

  handleReleaseChanged = (e) => {
    const val = parseInt(e.target.value, 10);
    const versionIndex = this.state.VersionLookup[this.state.ReleaseTypes[val]].length - 1;
    this.setState({
      Release: val,
      Version: versionIndex
    }, ()=> {
      this.saveState();
      this.checkVersionInstalled( );
    });
  };

  handleReleaseDownload = () => {
    const selectedVersion = this.getSelectedVersion();
    const fileName = selectedVersion + '.zip';
    this.setState({
      DownloadActive: true,
      DownloadingRelease: true,
      DownloadName: fileName,
    }, () => {
      this.sendRequest(Constants.IPC_Download_File, {
        Filename: this.state.DownloadName,
        URL: this.state.LocationsLookup[selectedVersion].urls[0],
      });
    });
  };

  handleUIDownload = () => {
    this.setState({
      DownloadActive: true,
      DownloadingUpgrade: true,
      DownloadName: 'UI Upgrade',
    }, ()=> {
      const url = this.state.UpgradeData.urls[0];
      this.sendRequest(Constants.IPC_Download_File, {
        Filename: this.props.Platform === 'win32' ? 'upgrade.exe' : this.extractFileNameFromURL(url),
        URL: url,
      });
    });
  };

  handleVersionChanged = (e) => {
    this.setState({
      Version: parseInt(e.target.value, 10),
    }, ()=> {
      this.saveState();
      this.checkVersionInstalled( );
    });
  };

  installDependency = data => {
    if (data.Success) {
      this.sendRequest(Constants.IPC_Install_Dependency, {
        Source: data.Destination,
        URL: data.URL,
      });
    }

    this.setState({
      DownloadActive: false,
      DownloadProgress: 0.0,
      DownloadingDependency: data.Success,
      DownloadName: '',
    });
  };

  installUpgrade = data => {
    if (data.Success) {
      const sha256 = this.state.LocationsLookup[this.props.AppPlatform][this.state.VersionLookup[this.props.AppPlatform][0]].sha256;
      const signature = this.state.LocationsLookup[this.props.AppPlatform][this.state.VersionLookup[this.props.AppPlatform][0]].sig;
      this.sendRequest(Constants.IPC_Install_Upgrade, {
        Sha256: sha256,
        Signature: signature,
        SkipVerification: !!data.SkipVerification,
        Source: data.Destination,
      });
    } else {
      this.setState({
        DownloadActive: false,
        DownloadProgress: 0.0,
        DownloadingUpgrade: false,
        DownloadName: '',
      });
    }
  };

  onCheckInstalledReply = (event, arg) => {
    const action = () => {
      const installedVersion = arg.data.Success && arg.data.Exists ? arg.data.Version : 'none';
      let versionAvailable = false;

      if (installedVersion !== 'none') {
        const latestVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]].length - 1;
        let version = this.state.Version;
        if (version === -1) {
          version = latestVersion;
        }
        versionAvailable = version !== latestVersion;
      }

      this.setState({
        AllowDownload: true,
        DownloadingDependency: false,
        MissingDependencies: arg.data.Dependencies,
        InstalledVersion: installedVersion,
        VersionAvailable: versionAvailable,
      });
    };

    if (arg.data.Success) {
      action();
    } else {
      this.setErrorState(arg.data.Error, action);
    }
  };
  
  onDownloadFileComplete = (event, arg) => {
    if (this.state.DownloadingRelease) {
      this.extractRelease(arg.data);
    } else if (this.state.DownloadingDependency) {
      this.installDependency(arg.data);
    } else if (this.state.DownloadingUpgrade) {
      this.installUpgrade(arg.data);
    } else {
      this.setState({
        DownloadActive: false,
        DownloadProgress: 0.0,
        DownloadName: '',
      });
    }
  };
  
  onDownloadFileProgress = (event, arg) => {
    this.setState({
      DownloadProgress: arg.data.Progress,
    });
  };
  
  onExtractReleaseComplete = (event, arg) => {
    this.sendRequest(Constants.IPC_Delete_File, {
      FilePath: arg.data.Source,
    });

    this.setState({
      ExtractActive: false,
    }, ()=> {
      this.checkVersionInstalled( );
    });
  };
  
  onGetStateReply = (event, arg) => {
    if (arg.data) {
      let state = {
        Release: arg.data.Release,
        Version: arg.data.Version,
      };

      for (const provider of Constants.PROVIDER_LIST) {
        let data = arg.data[provider] || this.state[provider];
        if (data.AutoMount === undefined) {
          data['AutoMount'] = false;
        }
        if (data.AutoRestart === undefined) {
          data['AutoRestart'] = false;
        }
        state[provider] = data;
      }

      this.setState(state, ()=> {
        this.grabReleases();
      });
    } else {
      this.grabReleases();
    }
  };
  
  onGrabReleasesReply = ()=> {
    const doUpdate = (locationsLookup, versionLookup) => {
      const latestVersion = versionLookup[this.state.ReleaseTypes[this.state.Release]].length - 1;
      let version = this.state.Version;
      if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
        version = latestVersion;
        this.saveState(version);
      }

      this.setState({
        DisplayMainContent: true,
        LocationsLookup: locationsLookup,
        Version: version,
        VersionAvailable: version !== latestVersion,
        VersionLookup: versionLookup,
      }, () => {
        this.checkVersionInstalled( );
      });
    };

    axios
      .get(Constants.RELEASES_URL)
      .then(response => {
        const versionLookup = {
          Alpha: response.data.Versions.Alpha[this.props.AppPlatform],
          Beta: response.data.Versions.Beta[this.props.AppPlatform],
          RC: response.data.Versions.RC[this.props.AppPlatform],
          Release: response.data.Versions.Release[this.props.AppPlatform],
        };
        const locationsLookup = {
          ...response.data.Locations[this.props.AppPlatform],
        };

        window.localStorage.setItem('releases', JSON.stringify({
          LocationsLookup: locationsLookup,
          VersionLookup: versionLookup
        }));

        doUpdate(locationsLookup, versionLookup);
      }).catch(error => {
        const releases = window.localStorage.getItem('releases');
        if (releases && (releases.length > 0)) {
          const obj = JSON.parse(releases);
          const locationsLookup = obj.LocationsLookup;
          const versionLookup = obj.VersionLookup;

          doUpdate(locationsLookup, versionLookup);
        } else {
          this.setErrorState(error, null, true);
        }
      });
  };
  
  onGrabUiReleasesReply = ()=> {
    axios
      .get(Constants.UI_RELEASES_URL)
      .then(response => {
        const data = response.data;
        if (data.Versions &&
          data.Versions[this.props.AppPlatform] &&
          (data.Versions[this.props.AppPlatform].length > 0) &&
          (data.Versions[this.props.AppPlatform][0] !== this.props.version)) {
          this.setState({
            UpgradeAvailable: true,
            UpgradeDismissed: false,
            UpgradeData: data.Locations[this.props.AppPlatform][data.Versions[this.props.AppPlatform][0]],
          });
        }
      }).catch(() => {
        this.setState({
          UpgradeAvailable: false,
          UpgradeData: {},
        });
      });
  };
  
  onInstallDependencyReply = (event, arg) => {
    if (arg.data.Success && arg.data.Source.toLowerCase().endsWith('.dmg')) {
      this.waitForDependencyInstall(arg.data.URL);
    } else {
      this.sendRequest(Constants.IPC_Delete_File, {
        FilePath: arg.data.Source,
      });
      this.checkVersionInstalled();
    }
  };
  
  onInstallUpgradeReply = (event, arg) => {
    this.sendSyncRequest(Constants.IPC_Delete_File, {
      FilePath: arg.data.Source,
    });

    this.setState({
      DownloadActive: false,
      DownloadProgress: 0.0,
      DownloadName: '',
    }, () => {
      if (!arg.data.Success) {
        this.setErrorState(arg.data.Error, () => {
          // TODO Prompt to verify
          if (arg.data.AllowSkipVerification) {
            this.installUpgrade( {
              SkipVerification: true,
              Source: arg.data.Source,
              Success: true,
            });
          }
        }, false);
      }
    });
  };
  
  saveState = version => {
    let state = {
      Release: this.state.Release,
      Version: version || this.state.Version,
    };
    for (const provider of Constants.PROVIDER_LIST) {
      state[provider] = this.state[provider];
    }

    this.sendRequest(Constants.IPC_Save_State, {
      State: state
    });
  };

  setErrorState = (error, action, critical) => {
    this.setState({
      DisplayError: true,
      Error: error,
      ErrorAction: action,
      ErrorCritical: critical,
    }, () => {
      this.sendRequest(Constants.IPC_Show_Window);
    });
  };

  updateCheckScheduledJob = () => {
    if (this.props.AppPlatform !== 'unknown') {
      this.grabReleases();
    }
  };

  waitForDependencyInstall = (url) => {
    const dep = this.state.MissingDependencies.find(d => {
      return d.download === url;
    });

    const i = setInterval(()=> {
      const ret = this.sendSyncRequest(Constants.IPC_Check_Dependency_Installed, {
        File: dep.file,
      });
      if (ret.data.Exists) {
        clearInterval(i);
        setTimeout(() => {
          this.checkVersionInstalled();
        }, 10000);
      }
    }, 3000);
  };

  render() {
    const selectedVersion = (this.state.Version === -1) ?
      'unavailable' :
      this.getSelectedVersion();

    const downloadEnabled = this.state.AllowDownload &&
      !this.props.MountsBusy &&
      !this.state.DownloadActive &&
      (selectedVersion !== 'unavailable') &&
      (selectedVersion !== this.state.InstalledVersion);

    const missingDependencies = (this.state.MissingDependencies.length > 0);
    const allowMount = this.state.InstalledVersion !== 'none' && !missingDependencies;

    const allowConfig = this.state.LocationsLookup[selectedVersion] &&
      this.state.LocationsLookup[selectedVersion].config_support;

    const allowSiaPrime =  this.state.LocationsLookup[selectedVersion] &&
      this.state.LocationsLookup[selectedVersion].siaprime_support;

    const noConsoleSupported =  this.state.LocationsLookup[selectedVersion] &&
      this.state.LocationsLookup[selectedVersion].no_console_supported;

    const showConfig = !missingDependencies &&
      this.props.DisplayConfiguration &&
      allowConfig;

    const showUpgrade = this.state.UpgradeAvailable &&
      !this.state.DisplayError &&
      !showConfig &&
      !this.state.DownloadActive &&
      !this.state.UpgradeDismissed;

    const showDependencies = !showUpgrade &&
      missingDependencies &&
      !this.state.DownloadActive;

    let errorDisplay = null;
    if (this.state.DisplayError) {
      errorDisplay = (
        <Modal critical>
          <ErrorDetails closed={this.closeErrorDisplay}
                        critical={this.state.ErrorCritical}
                        error={this.state.Error}/>
        </Modal>
      );
    }

    let configDisplay = null;
    if (showConfig) {
      configDisplay = (
        <Modal>
          <Configuration errorHandler={this.setErrorState}
                         version={selectedVersion} />
        </Modal>
      );
    }

    let dependencyDisplay = null;
    if (showDependencies) {
      dependencyDisplay = (
        <Modal>
          <DependencyList allowDownload={!this.state.DownloadingDependency}
                          dependencies={this.state.MissingDependencies}
                          onDownload={this.handleDependencyDownload}/>
        </Modal>
      );
    }

    let downloadDisplay = null;
    if (this.state.DownloadActive) {
      downloadDisplay = (
        <Modal>
          <DownloadProgress display={this.state.DownloadName}
                            progress={this.state.DownloadProgress}/>
        </Modal>
      );
    }

    let upgradeDisplay = null;
    if (showUpgrade) {
      upgradeDisplay = (
        <Modal>
          <UpgradeUI cancel={()=>this.setState({UpgradeDismissed: true})}
                     upgrade={this.handleUIDownload}/>
        </Modal>
      );
    }

    let mainContent = [];
    if (this.state.DisplayMainContent) {
      let key = 0;
      mainContent.push((
        <div key={'rvd_' + key++}
             style={{height: '32%'}}>
          <ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.props.MountsBusy}
                                 downloadClicked={this.handleReleaseDownload}
                                 downloadDisabled={!downloadEnabled}
                                 installedVersion={this.state.InstalledVersion}
                                 release={this.state.Release}
                                 releaseChanged={this.handleReleaseChanged}
                                 releaseExtracting={this.state.ExtractActive}
                                 releaseTypes={this.state.ReleaseTypes}
                                 version={this.state.Version}
                                 versionAvailable={this.state.VersionAvailable}
                                 versionChanged={this.handleVersionChanged}
                                 versions={this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]]}/>
        </div>
      ));

      if (allowMount) {
        let providerProps = {};
        for (const provider of Constants.PROVIDER_LIST) {
          const providerLower = provider.toLowerCase();
          providerProps[providerLower] = this.state[provider];
        }
        mainContent.push((
          <div key={'md_' + key++}>
            <MountItems {...providerProps}
                        allowConfig={allowConfig}
                        allowSiaPrime={allowSiaPrime}
                        noConsoleSupported={noConsoleSupported}
                        autoMountChanged={this.handleAutoMountChanged}
                        autoRestartChanged={this.handleAutoRestartChanged}
                        changed={this.handleMountLocationChanged}
                        errorHandler={this.setErrorState}
                        version={this.state.InstalledVersion}/>
          </div>
        ));
      }
    } else {
      mainContent = <Loading/>
    }

    return (
      <div className={'App'}>
        {errorDisplay}
        {dependencyDisplay}
        {upgradeDisplay}
        {downloadDisplay}
        {configDisplay}
        <div className={'AppContainer'}>
          <div className={'AppHeader'}>
            <Box>
              <Grid>
                <Text col={0}
                      colSpan={'remain'}
                      row={0}
                      rowSpan={'remain'}
                      text={'Repertory UI v' + this.props.version}
                      textAlign={'center'}
                      type={'Heading1'}/>
                <UpgradeIcon
                  available={this.state.UpgradeAvailable}
                  clicked={()=>this.setState({UpgradeDismissed: false})}
                  col={dimensions => dimensions.columns - 6}
                  colSpan={5}
                  row={1}
                  rowSpan={remain=>remain - 1}/>
              </Grid>
            </Box>
          </div>
          <div className={'AppContent'}>
            <Box dxStyle={{padding: '8px 8px 0px 8px'}}>
              {mainContent}
            </Box>
          </div>
        </div>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    AppPlatform: state.common.AppPlatform,
    DisplayConfiguration: state.mounts.DisplayConfiguration,
    MountsBusy: state.mounts.MountsBusy,
    Platform: state.common.Platform,
  };
};

export default connect(mapStateToProps)(App);
