import * as React from 'react';
import { Types, SortedCollection, Maybe, Compare } from './common';
import { Chain, Ago } from './components';
import { Row, Column } from './components/List';
import { Connection } from './Connection';
import { Persistent, PersistentObject, PersistentSet } from './persist';
import {
  bindState,
  State,
  Update,
  Node,
  ChainData,
  comparePinnedChains,
  StateSettings,
} from './state';
import { getHashData } from './utils';

import innerCircleIcon from './assets/inner-circle.svg';
import outerCircleIcon from './assets/outer-circle.svg';

import './App.css';

export default class App extends React.Component {
  private chainsCache: ChainData[] = [];
  // Custom state for finer control over updates
  private readonly appState: Readonly<State>;
  private readonly appUpdate: Update;
  private readonly settings: PersistentObject<StateSettings>;
  private readonly pins: PersistentSet<Types.NodeName>;
  private readonly sortBy: Persistent<Maybe<number>>;
  private readonly connection: Promise<Connection>;

  constructor(props: Record<string, unknown>) {
    super(props);

    //todo! Check what's important to set to true, false
    this.settings = new PersistentObject(
      'settings',
      {
        validator: true,
        location: true,
        implementation: true,
        networkId: true,
        peers: true,
        txs: true,
        upload: false,
        download: false,
        stateCacheSize: false,
        dbCacheSize: false,
        diskRead: false,
        diskWrite: false,
        blocknumber: true,
        blockhash: true,
        finalized: false,
        finalizedhash: false,
        blocktime: true,
        blockpropagation: true,
        blocklasttime: false,
        uptime: false,
        version: false,
        target_os: false,
        target_arch: false,
        cpu: false,
        cpu_hashrate_score: true,
        cpu_vendor: true,
        core_count: false,
        mem: true,
        memory: false,
        linux_distro: false,
        linux_kernel: false,
        memory_memcpy_score: true,
        disk_sequential_write_score: true,
        disk_random_write_score: true,
        is_virtual_machine: false,
      },
      (settings) => {
        const selectedColumns = this.selectedColumns(settings);

        this.sortBy.set(null);
        this.appUpdate({ settings, selectedColumns, sortBy: null });
      }
    );

    this.pins = new PersistentSet<Types.NodeName>('pinned_names', (pins) => {
      const { nodes } = this.appState;

      nodes.mutEachAndSort((node) => node.setPinned(pins.has(node.name)));

      this.appUpdate({ nodes, pins });
    });

    this.sortBy = new Persistent<Maybe<number>>('sortBy', null, (sortBy) => {
      const compare = this.getComparator(sortBy);

      this.appState.nodes.setComparator(compare);
      this.appUpdate({ sortBy });
    });

    const { tab = '' } = getHashData();

    this.appUpdate = bindState(this, {
      status: 'offline',
      best: 0 as Types.BlockNumber,
      finalized: 0 as Types.BlockNumber,
      blockTimestamp: 0 as Types.Timestamp,
      blockAverage: null,
      timeDiff: 0 as Types.Milliseconds,
      subscribed: null,
      chains: new Map(),
      nodes: new SortedCollection(Node.compare),
      settings: this.settings.raw(),
      pins: this.pins.get(),
      sortBy: this.sortBy.get(),
      selectedColumns: this.selectedColumns(this.settings.raw()),
      tab,
      chainStats: null,
    });
    this.appState = this.appUpdate({});

    const comparator = this.getComparator(this.sortBy.get());

    this.appState.nodes.setComparator(comparator);
    this.connection = Connection.create(
      this.pins,
      this.appState,
      this.appUpdate
    );

    setInterval(() => (this.chainsCache = []), 10000);
  }

  public render() {
    const { timeDiff, subscribed, status, tab } = this.appState;
    const chains = this.chains();
    const subscribedData = subscribed
      ? this.appState.chains.get(subscribed)
      : null;

    Ago.timeDiff = timeDiff;

    if (chains.length === 0) {
      return (
        <div className="App App-no-telemetry">
          <div className="App-no-telemetry-spinner">
            <img src={innerCircleIcon} />
            <img src={outerCircleIcon} />
          </div>
          <div className="App-no-telemetry-text">
            Waiting for Ghosties&hellip;
          </div>
        </div>
      );
    }

    return (
      <div className="App">
        <Chain
          appState={this.appState}
          appUpdate={this.appUpdate}
          connection={this.connection}
          settings={this.settings}
          pins={this.pins}
          sortBy={this.sortBy}
        />
      </div>
    );
  }

  public componentDidMount() {
    window.addEventListener('hashchange', this.onHashChange);
  }

  public componentWillUnmount() {
    window.removeEventListener('hashchange', this.onHashChange);
  }

  private onHashChange = () => {
    const { tab = '' } = getHashData();

    this.appUpdate({ tab });
  };

  private chains(): ChainData[] {
    if (this.chainsCache.length === this.appState.chains.size) {
      return this.chainsCache;
    }

    this.chainsCache = Array.from(this.appState.chains.values()).sort(
      (a, b) => {
        const pinned = comparePinnedChains(a.genesisHash, b.genesisHash);

        if (pinned !== 0) {
          return pinned;
        }

        return b.nodeCount - a.nodeCount;
      }
    );

    return this.chainsCache;
  }

  private selectedColumns(settings: StateSettings): Column[] {
    return Row.columns.filter(
      ({ setting }) => setting == null || settings[setting]
    );
  }

  private getComparator(sortBy: Maybe<number>): Compare<Node> {
    const columns = this.appState.selectedColumns;

    if (sortBy != null) {
      const [index, rev] = sortBy < 0 ? [~sortBy, -1] : [sortBy, 1];
      const column = columns[index];

      if (column != null && column.sortBy) {
        const key = column.sortBy;

        return (a, b) => {
          const aKey = key(a);
          const bKey = key(b);

          if (aKey < bKey) {
            return -1 * rev;
          } else if (aKey > bKey) {
            return 1 * rev;
          } else {
            return Node.compare(a, b);
          }
        };
      }
    }

    return Node.compare;
  }
}
