import classNames from 'classnames';
import { ipcRenderer } from 'electron';
import * as React from 'react';

import { apiCmds, apiName } from '../../common/api-interface';
import { isLinux, isMac, isWindowsOS } from '../../common/env';
import { i18n } from '../../common/i18n-preload';

const screenRegExp = new RegExp(/^screen \d+$/gim);
const SCREEN_PICKER_NAMESPACE = 'ScreenPicker';
const ENTIRE_SCREEN = 'entire screen';

interface IState {
  sources: ICustomDesktopCapturerSource[];
  selectedSource: ICustomDesktopCapturerSource | undefined;
  selectedTab: tabs;
}

interface ICustomDesktopCapturerSource extends Electron.DesktopCapturerSource {
  fileName: string | null;
}

type tabs = 'screens' | 'applications';

const enum keyCode {
  pageDown = 34,
  rightArrow = 39,
  pageUp = 33,
  leftArrow = 37,
  homeKey = 36,
  upArrow = 38,
  endKey = 35,
  arrowDown = 40,
  enterKey = 13,
  escapeKey = 27,
}

type inputChangeEvent = React.ChangeEvent<HTMLInputElement>;

export default class ScreenPicker extends React.Component<{}, IState> {
  private isScreensAvailable: boolean;
  private isApplicationsAvailable: boolean;
  private readonly eventHandlers = {
    onSelect: (src: ICustomDesktopCapturerSource) => this.select(src),
    onToggle: (tab: tabs) => (_event: inputChangeEvent) => this.toggle(tab),
    onClose: () => this.close(),
    onSubmit: () => this.submit(),
  };
  private currentIndex: number;

  constructor(props) {
    super(props);
    this.state = {
      sources: [],
      selectedSource: undefined,
      selectedTab: 'screens',
    };
    this.currentIndex = 0;
    this.isScreensAvailable = false;
    this.isApplicationsAvailable = false;
    this.updateState = this.updateState.bind(this);
    this.handleKeyUpPress = this.handleKeyUpPress.bind(this);
    this.renderTabTitles = this.renderTabTitles.bind(this);
  }

  /**
   * Callback to handle event when a component is mounted
   */
  public componentDidMount(): void {
    ipcRenderer.on('screen-picker-data', this.updateState);
    document.addEventListener('keyup', this.handleKeyUpPress, true);
    if (isWindowsOS) {
      document.body.classList.add('ScreenPicker-window-border');
    }
  }

  /**
   * Callback to handle event when a component is unmounted
   */
  public componentWillUnmount(): void {
    ipcRenderer.removeListener('screen-picker-data', this.updateState);
    document.removeEventListener('keyup', this.handleKeyUpPress, true);
  }

  /**
   * Renders the component
   */
  public render(): JSX.Element {
    const { sources, selectedSource } = this.state;
    return (
      <div className='ScreenPicker ScreenPicker-content'>
        <div className='ScreenPicker-title'>
          <span>
            {i18n.t(
              `Choose what you'd like to share`,
              SCREEN_PICKER_NAMESPACE,
            )()}
          </span>
          <div
            className='ScreenPicker-x-button'
            onClick={this.eventHandlers.onClose}
          >
            <div className='content-button'>
              <i>
                <svg viewBox='0 0 48 48' fill='grey'>
                  <path d='M39.4,33.8L31,25.4c-0.4-0.4-0.9-0.9-1.4-1.4c0.5-0.5,1-1,1.4-1.4l8.4-8.4c0.8-0.8,0.8-2,0-2.8l-2.8-2.8 c-0.8-0.8-2-0.8-2.8,0L25.4,17c-0.4,0.4-0.9,0.9-1.4,1.4c-0.5-0.5-1-1-1.4-1.4l-8.4-8.4c-0.8-0.8-2-0.8-2.8,0l-2.8,2.8 c-0.8,0.8-0.8,2,0,2.8l8.4,8.4c0.4,0.4,0.9,0.9,1.4,1.4c-0.5,0.5-1,1-1.4,1.4l-8.4,8.4c-0.8,0.8-0.8,2,0,2.8l2.8,2.8 c0.8,0.8,2,0.8,2.8,0l8.4-8.4c0.4-0.4,0.9-0.9,1.4-1.4c0.5,0.5,1,1,1.4,1.4l8.4,8.4c0.8,0.8,2,0.8,2.8,0l2.8-2.8 C40.2,35.8,40.2,34.6,39.4,33.8z' />
                </svg>
              </i>
            </div>
          </div>
        </div>
        {this.renderSources(sources)}
        <footer>
          <button
            className='ScreenPicker-cancel-button'
            onClick={this.eventHandlers.onClose}
          >
            {i18n.t('Cancel', SCREEN_PICKER_NAMESPACE)()}
          </button>
          <button
            className={classNames('ScreenPicker-share-button', {
              'ScreenPicker-share-button-disable': !selectedSource,
            })}
            onClick={this.eventHandlers.onSubmit}
          >
            {selectedSource
              ? i18n.t('Share', SCREEN_PICKER_NAMESPACE)()
              : i18n.t('Select Screen', SCREEN_PICKER_NAMESPACE)()}
          </button>
        </footer>
      </div>
    );
  }

  /**
   * Renders the sources by separating screens and applications
   *
   * @param sources {DesktopCapturerSource}
   */
  private renderSources(sources: ICustomDesktopCapturerSource[]): JSX.Element {
    const screens: JSX.Element[] = [];
    const applications: JSX.Element[] = [];
    sources.map((source: ICustomDesktopCapturerSource) => {
      screenRegExp.lastIndex = 0;
      const shouldHighlight: string = classNames(
        'ScreenPicker-item-container',
        { 'ScreenPicker-selected': this.shouldHighlight(source.id) },
      );
      const sourceName = source.name.toLocaleLowerCase();
      if (
        ((isMac || isLinux) && source.display_id !== '') ||
        (isWindowsOS &&
          (sourceName === ENTIRE_SCREEN || screenRegExp.exec(sourceName)))
      ) {
        source.fileName = 'fullscreen';
        let screenName;
        if (sourceName === ENTIRE_SCREEN) {
          screenName = i18n.t('Entire screen', SCREEN_PICKER_NAMESPACE)();
        } else {
          const screenNumber = source.name.substr(7, source.name.length);
          screenName = i18n.t(
            'Screen {number}',
            SCREEN_PICKER_NAMESPACE,
          )({ number: screenNumber });
        }
        screens.push(
          <div
            className={shouldHighlight}
            id={source.id}
            onClick={() => this.eventHandlers.onSelect(source)}
          >
            <div className='ScreenPicker-screen-section-box'>
              <img
                className='ScreenPicker-img-wrapper'
                src={source.thumbnail.toDataURL()}
                alt='thumbnail image'
              />
            </div>
            <div className='ScreenPicker-screen-source-title'>{screenName}</div>
          </div>,
        );
      } else {
        source.fileName = null;
        applications.push(
          <div
            className={shouldHighlight}
            id={source.id}
            onClick={() => this.eventHandlers.onSelect(source)}
          >
            <div className='ScreenPicker-screen-section-box'>
              <img
                className='ScreenPicker-img-wrapper'
                src={source.thumbnail.toDataURL()}
                alt='thumbnail image'
              />
            </div>
            <div className='ScreenPicker-screen-source-title'>
              {source.name}
            </div>
          </div>,
        );
      }
    });
    this.isScreensAvailable = screens.length > 0;
    this.isApplicationsAvailable = applications.length > 0;
    if (!this.isScreensAvailable && !this.isApplicationsAvailable) {
      return (
        <div className='ScreenPicker-error-content' key='spec'>
          <span className='error-message'>
            {i18n.t(
              'No screens or applications are currently available.',
              SCREEN_PICKER_NAMESPACE,
            )()}
          </span>
        </div>
      );
    }

    return (
      <div className='ScreenPicker-main-content' key='spmc'>
        {this.renderTabTitles()}
        <section id='screen-contents'>{screens}</section>
        <section id='application-contents'> {applications}</section>
      </div>
    );
  }

  /**
   * Renders the screen and application tab section
   */
  private renderTabTitles(): JSX.Element[] | undefined {
    const { selectedTab } = this.state;
    if (this.isScreensAvailable && this.isApplicationsAvailable) {
      return [
        <input
          id='screen-tab'
          className='ScreenPicker-screen-tab'
          type='radio'
          name='tabs'
          checked={selectedTab === 'screens'}
          onChange={this.eventHandlers.onToggle('screens')}
        />,
        <label
          className={classNames('screens', {
            hidden: !this.isScreensAvailable,
          })}
          htmlFor='screen-tab'
        >
          {i18n.t('Screens', SCREEN_PICKER_NAMESPACE)()}
        </label>,
        <input
          id='application-tab'
          className='ScreenPicker-application-tab'
          type='radio'
          name='tabs'
          checked={selectedTab === 'applications'}
          onChange={this.eventHandlers.onToggle('applications')}
        />,
        <label
          className={classNames('applications', {
            hidden: !this.isApplicationsAvailable,
          })}
          htmlFor='application-tab'
        >
          {i18n.t('Applications', SCREEN_PICKER_NAMESPACE)()}
        </label>,
      ];
    }
    if (this.isScreensAvailable) {
      return [
        <input
          id='screen-tab'
          className='ScreenPicker-screen-tab'
          type='radio'
          name='tabs'
          checked={true}
          onChange={this.eventHandlers.onToggle('screens')}
        />,
        <label
          className={classNames('screens', {
            hidden: !this.isScreensAvailable,
          })}
          htmlFor='screen-tab'
        >
          {i18n.t('Screens', SCREEN_PICKER_NAMESPACE)()}
        </label>,
      ];
    }
    if (this.isApplicationsAvailable) {
      return [
        <input
          id='application-tab'
          className='ScreenPicker-application-tab'
          type='radio'
          name='tabs'
          checked={true}
          onChange={this.eventHandlers.onToggle('applications')}
        />,
        <label
          className={classNames('applications', {
            hidden: !this.isApplicationsAvailable,
          })}
          htmlFor='application-tab'
        >
          {i18n.t('Applications', SCREEN_PICKER_NAMESPACE)()}
        </label>,
      ];
    }
    return;
  }

  /**
   * Updates the selected state
   *
   * @param id {string}
   */
  private shouldHighlight(id: string): boolean {
    const { selectedSource } = this.state;
    return selectedSource ? id === selectedSource.id : false;
  }

  /**
   * updates the state when the source is selected
   *
   * @param selectedSource {DesktopCapturerSource}
   */
  private select(selectedSource: ICustomDesktopCapturerSource): void {
    this.setState({ selectedSource });

    if (selectedSource) {
      ipcRenderer.send('screen-source-select', selectedSource);
    }
  }

  /**
   * Updates the screen picker tabs
   *
   * @param selectedTab
   */
  private toggle(selectedTab: tabs): void {
    this.setState({ selectedTab });
  }

  /**
   * Closes the screen picker window
   */
  private close(): void {
    // setting null will clean up listeners
    ipcRenderer.send('screen-source-selected', null);
    ipcRenderer.send(apiName.symphonyApi, {
      cmd: apiCmds.closeWindow,
      windowType: 'screen-picker',
    });
  }

  /**
   * Sends the selected source to the main process
   * and closes the screen picker window
   */
  private submit(): void {
    const { selectedSource } = this.state;
    if (selectedSource) {
      ipcRenderer.send('screen-source-selected', selectedSource);
    }
  }

  /**
   * Method handles used key up event
   * @param e
   */
  private handleKeyUpPress(e): void {
    const key = e.keyCode || e.which;
    const { sources } = this.state;

    switch (key) {
      case keyCode.pageDown:
      case keyCode.rightArrow:
        this.updateSelectedSource(1);
        break;
      case keyCode.pageUp:
      case keyCode.leftArrow:
        this.updateSelectedSource(-1);
        break;
      case keyCode.homeKey:
        if (this.currentIndex !== 0) {
          this.updateSelectedSource(0);
        }
        break;
      case keyCode.upArrow:
        this.updateSelectedSource(-2);
        break;
      case keyCode.endKey:
        if (this.currentIndex !== sources.length - 1) {
          this.updateSelectedSource(sources.length - 1);
        }
        break;
      case keyCode.arrowDown:
        this.updateSelectedSource(2);
        break;
      case keyCode.enterKey:
        this.eventHandlers.onSubmit();
        break;
      case keyCode.escapeKey:
        this.eventHandlers.onClose();
        break;
      default:
        break;
    }
  }

  /**
   * Updated the UI selected state based on
   * the selected source state
   *
   * @param index
   */
  private updateSelectedSource(index) {
    const { sources, selectedSource } = this.state;
    if (selectedSource) {
      this.currentIndex = sources.findIndex((source) => {
        return source.id === selectedSource.id;
      });
    }

    // Find the next item to be selected
    const nextIndex =
      (this.currentIndex + index + sources.length) % sources.length;
    if (sources[nextIndex] && sources[nextIndex].id) {
      // Updates the selected source
      this.setState({ selectedSource: sources[nextIndex] });
    }
  }

  /**
   * Sets the component state
   *
   * @param _event
   * @param data {Object}
   */
  private updateState(_event, data): void {
    this.setState(data as IState);
  }
}
