/* eslint-disable radix */
/* eslint-disable max-classes-per-file */
import React, { Component } from 'react';
import { Redirect, withRouter } from 'react-router';
import { remote, ipcRenderer } from 'electron';
import TextareaAutosize from 'react-textarea-autosize';
import Store from 'electron-store';
import request from 'request';
import progress from 'progress-stream';
import path from 'path';
import os from 'os';
import fs from 'fs';
import { promisify } from 'util';
import native from '../../native/index.node';
import routes from '../constants/routes.json';
import { RPCConfig, Info } from './AppState';
import RPC from '../rpc';
import cstyles from './Common.module.css';
import styles from './LoadingScreen.module.css';
import Logo from '../assets/img/logobig.png';

const locateZcashParamsDir = () => {
  if (os.platform() === 'darwin') {
    return path.join(remote.app.getPath('appData'), 'ZcashParams');
  }

  if (os.platform() === 'linux') {
    return path.join(remote.app.getPath('home'), '.zcash-params');
  }

  return path.join(remote.app.getPath('appData'), 'ZcashParams');
};

type Props = {
  setRPCConfig: (rpcConfig: RPCConfig) => void,
  rescanning: boolean,
  setRescanning: boolean => void,
  setInfo: (info: Info) => void,
  openServerSelectModal: () => void
};

class LoadingScreenState {
  currentStatus: string;

  currentStatusIsError: boolean;

  loadingDone: boolean;

  rpcConfig: RPCConfig | null;

  url: string;

  walletScreen: number; // 0 -> no wallet, load existing wallet 1 -> show option 2-> create new 3 -> restore existing

  newWalletError: null | string; // Any errors when creating/restoring wallet

  seed: string; // The new seed phrase for a newly created wallet or the seed phrase to restore from

  birthday: number; // Wallet birthday if we're restoring

  getinfoRetryCount: number;

  constructor() {
    this.currentStatus = 'Loading...';
    this.currentStatusIsError = false;
    this.loadingDone = false;
    this.rpcConfig = null;
    this.url = '';
    this.getinfoRetryCount = 0;
    this.walletScreen = 0;
    this.newWalletError = null;
    this.seed = '';
    this.birthday = 0;
  }
}

class LoadingScreen extends Component<Props, LoadingScreenState> {
  constructor(props: Props) {
    super(props);

    const state = new LoadingScreenState();
    this.state = state;
  }

  componentDidMount() {
    const { rescanning } = this.props;

    if (rescanning) {
      this.runSyncStatusPoller();
    } else {
      (async () => {
        const success = await this.ensureZcashParams();
        if (success) {
          // Do it in a timeout, so the window has a chance to load.
          setTimeout(() => this.doFirstTimeSetup(), 100);
        }
      })();
    }
  }

  download = (url, dest, name, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', response => {
      if (response.statusCode !== 200) {
        return cb(`Response status was ${response.statusCode}`);
      }

      const totalSize = (parseInt(response.headers['content-length'], 10) / 1024 / 1024).toFixed(0);

      const str = progress({ time: 1000 }, pgrs => {
        this.setState({
          currentStatus: `Downloading ${name}... (${(pgrs.transferred / 1024 / 1024).toFixed(0)} MB / ${totalSize} MB)`
        });
      });

      sendReq.pipe(str).pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', err => {
      fs.unlink(dest);
      return cb(err.message);
    });

    file.on('error', err => {
      // Handle errors
      fs.unlink(dest); // Delete the file async. (But we don't check the result)
      return cb(err.message);
    });
  };

  ensureZcashParams = async () => {
    // Check if the zcash params dir exists and if the params files are present
    const dir = locateZcashParamsDir();
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir);
    }

    // Check for the params
    const params = [
      { name: 'sapling-output.params', url: 'https://params.zecwallet.co/params/sapling-output.params' },
      { name: 'sapling-spend.params', url: 'https://params.zecwallet.co/params/sapling-spend.params' }
    ];

    // eslint-disable-next-line no-plusplus
    for (let i = 0; i < params.length; i++) {
      const p = params[i];

      const fileName = path.join(dir, p.name);
      if (!fs.existsSync(fileName)) {
        // Download and save this file
        this.setState({ currentStatus: `Downloading ${p.name}...` });

        try {
          // eslint-disable-next-line no-await-in-loop
          await promisify(this.download)(p.url, fileName, p.name);
        } catch (err) {
          console.log(`error: ${err}`);
          this.setState({ currentStatus: `Error downloading ${p.name}. The error was: ${err}` });
          return false;
        }
      }
    }

    return true;
  };

  loadServerURI = () => {
    // Try to read the default server
    const store = new Store();
    const server = store.get('lightd/serveruri', 'https://lightwalletd.zecwallet.co:1443');

    const newstate = new LoadingScreenState();
    Object.assign(newstate, this.state);

    newstate.url = server;
    this.setState(newstate);
  };

  doFirstTimeSetup = async () => {
    this.loadServerURI();

    // Try to load the light client
    const { url } = this.state;

    // First, set up the exit handler
    this.setupExitHandler();

    // Test to see if the wallet exists
    if (!native.litelib_wallet_exists('main')) {
      // Show the wallet creation screen
      this.setState({ walletScreen: 1 });
    } else {
      try {
        const result = native.litelib_initialize_existing(url);
        console.log(`Intialization: ${result}`);
        if (result !== 'OK') {
          this.setState({
            currentStatus: (
              <span>
                Error Initializing Lightclient
                <br />
                {result}
              </span>
            ),
            currentStatusIsError: true
          });

          return;
        }

        this.getInfo();
      } catch (e) {
        console.log('Error initializing', e);
        this.setState({
          currentStatus: (
            <span>
              Error Initializing Lightclient
              <br />
              {`${e}`}
            </span>
          ),
          currentStatusIsError: true
        });
      }
    }
  };

  setupExitHandler = () => {
    // App is quitting, make sure to save the wallet properly.
    ipcRenderer.on('appquitting', () => {
      RPC.doSave();
      RPC.deinitialize();

      // And reply that we're all done after 100ms, to allow cleanup of the rust stuff.
      setTimeout(() => {
        ipcRenderer.send('appquitdone');
      }, 100);
    });
  };

  getInfo() {
    // Try getting the info.
    try {
      // Do a sync at start
      this.setState({ currentStatus: 'Syncing...' });

      // This will do the sync in another thread, so we have to check for sync status
      RPC.doSync();

      this.runSyncStatusPoller();
    } catch (err) {
      // Not yet finished loading. So update the state, and setup the next refresh
      this.setState({ currentStatus: err });
    }
  }

  runSyncStatusPoller = () => {
    const me = this;

    const { setRPCConfig, setInfo, setRescanning } = this.props;
    const { url } = this.state;

    const info = RPC.getInfoObject();

    // And after a while, check the sync status.
    const poller = setInterval(() => {
      const syncstatus = RPC.doSyncStatus();

      if (syncstatus.startsWith('Error')) {
        // Something went wrong
        this.setState({ currentStatus: syncstatus, currentStatusIsError: true });

        // And cancel the updater
        clearInterval(poller);
      } else {
        const ss = JSON.parse(syncstatus);

        if (ss.syncing === 'false') {
          // First, save the wallet so we don't lose the just-synced data
          RPC.doSave();

          // Set the info object, so the sidebar will show
          console.log(info);
          setInfo(info);

          setRescanning(false);

          // Configure the RPC, which will setup the refresh
          const rpcConfig = new RPCConfig();
          rpcConfig.url = url;
          setRPCConfig(rpcConfig);

          // And cancel the updater
          clearInterval(poller);

          // This will cause a redirect to the dashboard screen
          me.setState({ loadingDone: true });
        } else {
          // Still syncing, grab the status and update the status
          const p = ss.synced_blocks;
          const t = ss.total_blocks;
          const currentStatus = `Syncing ${p} / ${t}`;

          me.setState({ currentStatus });
        }
      }
    }, 1000);
  };

  createNewWallet = () => {
    const { url } = this.state;
    const result = native.litelib_initialize_new(url);

    if (result.startsWith('Error')) {
      this.setState({ newWalletError: result });
    } else {
      const r = JSON.parse(result);
      this.setState({ walletScreen: 2, seed: r.seed });
    }
  };

  startNewWallet = () => {
    // Start using the new wallet
    this.setState({ walletScreen: 0 });
    this.getInfo();
  };

  restoreExistingWallet = () => {
    this.setState({ walletScreen: 3 });
  };

  updateSeed = e => {
    this.setState({ seed: e.target.value });
  };

  updateBirthday = e => {
    this.setState({ birthday: e.target.value });
  };

  restoreWalletBack = () => {
    // Reset the seed and birthday and try again
    this.setState({ seed: '', birthday: 0, newWalletError: null, walletScreen: 3 });
  };

  doRestoreWallet = () => {
    const { seed, birthday, url } = this.state;
    console.log(`Restoring ${seed} with ${birthday}`);

    const allowOverwrite = true;

    const result = native.litelib_initialize_new_from_phrase(url, seed, parseInt(birthday), allowOverwrite);
    if (result.startsWith('Error')) {
      this.setState({ newWalletError: result });
    } else {
      this.setState({ walletScreen: 0 });
      this.getInfo();
    }
  };

  render() {
    const {
      loadingDone,
      currentStatus,
      currentStatusIsError,
      walletScreen,
      newWalletError,
      seed,
      birthday
    } = this.state;

    const { openServerSelectModal } = this.props;

    // If still loading, show the status
    if (!loadingDone) {
      return (
        <div className={[cstyles.verticalflex, cstyles.center, styles.loadingcontainer].join(' ')}>
          {walletScreen === 0 && (
            <div>
              <div style={{ marginTop: '100px' }}>
                <img src={Logo} width="200px;" alt="Logo" />
              </div>
              <div>{currentStatus}</div>
              {currentStatusIsError && (
                <div className={cstyles.buttoncontainer}>
                  <button type="button" className={cstyles.primarybutton} onClick={openServerSelectModal}>
                    Switch LightwalletD Server
                  </button>
                  <button
                    type="button"
                    className={cstyles.primarybutton}
                    onClick={() => {
                      this.setState({ walletScreen: 1 });
                      this.setState({ currentStatus: '', currentStatusIsError: false });
                      this.restoreExistingWallet();
                    }}
                  >
                    Restore Wallet From Seed
                  </button>
                </div>
              )}
            </div>
          )}

          {walletScreen === 1 && (
            <div>
              <div>
                <img src={Logo} width="200px;" alt="Logo" />
              </div>
              <div className={[cstyles.well, styles.newwalletcontainer].join(' ')}>
                <div className={cstyles.verticalflex}>
                  <div className={[cstyles.large, cstyles.highlight].join(' ')}>Create A New Wallet</div>
                  <div className={cstyles.padtopsmall}>
                    Creates a new wallet with a new randomly generated seed phrase. Please save the seed phrase
                    carefully, it&rsquo;s the only way to restore your wallet.
                  </div>
                  <div className={cstyles.margintoplarge}>
                    <button type="button" className={cstyles.primarybutton} onClick={this.createNewWallet}>
                      Create New
                    </button>
                  </div>
                </div>
                <div className={[cstyles.verticalflex, cstyles.margintoplarge].join(' ')}>
                  <div className={[cstyles.large, cstyles.highlight].join(' ')}>Restore Wallet From Seed</div>
                  <div className={cstyles.padtopsmall}>
                    If you already have a seed phrase, you can restore it to this wallet. This will rescan the
                    blockchain for all transactions from the seed phrase.
                  </div>
                  <div className={cstyles.margintoplarge}>
                    <button type="button" className={cstyles.primarybutton} onClick={this.restoreExistingWallet}>
                      Restore Existing
                    </button>
                  </div>
                </div>
              </div>
            </div>
          )}

          {walletScreen === 2 && (
            <div>
              <div>
                <img src={Logo} width="200px;" alt="Logo" />
              </div>
              <div className={[cstyles.well, styles.newwalletcontainer].join(' ')}>
                <div className={cstyles.verticalflex}>
                  {newWalletError && (
                    <div>
                      <div className={[cstyles.large, cstyles.highlight].join(' ')}>Error Creating New Wallet</div>
                      <div className={cstyles.padtopsmall}>There was an error creating a new wallet</div>
                      <hr />
                      <div className={cstyles.padtopsmall}>{newWalletError}</div>
                      <hr />
                    </div>
                  )}

                  {!newWalletError && (
                    <div>
                      <div className={[cstyles.large, cstyles.highlight].join(' ')}>Your New Wallet</div>
                      <div className={cstyles.padtopsmall}>
                        This is your new wallet. Below is your seed phrase. PLEASE STORE IT CAREFULLY! The seed phrase
                        is the only way to recover your funds and transactions.
                      </div>
                      <hr />
                      <div className={cstyles.padtopsmall}>{seed}</div>
                      <hr />
                      <div className={cstyles.margintoplarge}>
                        <button type="button" className={cstyles.primarybutton} onClick={this.startNewWallet}>
                          Start Wallet
                        </button>
                      </div>
                    </div>
                  )}
                </div>
              </div>
            </div>
          )}

          {walletScreen === 3 && (
            <div>
              <div>
                <img src={Logo} width="200px;" alt="Logo" />
              </div>
              <div className={[cstyles.well, styles.newwalletcontainer].join(' ')}>
                <div className={cstyles.verticalflex}>
                  {newWalletError && (
                    <div>
                      <div className={[cstyles.large, cstyles.highlight].join(' ')}>Error Restoring Wallet</div>
                      <div className={cstyles.padtopsmall}>There was an error restoring your seed phrase</div>
                      <hr />
                      <div className={cstyles.padtopsmall}>{newWalletError}</div>
                      <hr />
                      <div className={cstyles.margintoplarge}>
                        <button type="button" className={cstyles.primarybutton} onClick={this.restoreWalletBack}>
                          Back
                        </button>
                      </div>
                    </div>
                  )}

                  {!newWalletError && (
                    <div>
                      <div className={[cstyles.large].join(' ')}>Please enter your seed phrase</div>
                      <TextareaAutosize className={cstyles.inputbox} value={seed} onChange={e => this.updateSeed(e)} />

                      <div className={[cstyles.large, cstyles.margintoplarge].join(' ')}>
                        Wallet Birthday. If you don&rsquo;t know this, it is OK to enter &lsquo;0&rsquo;
                      </div>
                      <input
                        type="number"
                        className={cstyles.inputbox}
                        value={birthday}
                        onChange={e => this.updateBirthday(e)}
                      />

                      <div className={cstyles.margintoplarge}>
                        <button
                          type="button"
                          className={cstyles.primarybutton}
                          onClick={() => this.doRestoreWallet(false)}
                        >
                          Restore Wallet
                        </button>
                      </div>
                    </div>
                  )}
                </div>
              </div>
            </div>
          )}
        </div>
      );
    }

    return <Redirect to={routes.DASHBOARD} />;
  }
}

export default withRouter(LoadingScreen);
