import React, { Component } from 'react';
import PropTypes from 'prop-types';
import qrMarker from '../../../../style/images/qr-marker.svg';

function hasGetUserMedia() {
  return !!(
    (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) // eslint-disable-line no-undef
    || navigator.webkitGetUserMedia // eslint-disable-line no-undef
    || navigator.mozGetUserMedia // eslint-disable-line no-undef
    || navigator.msGetUserMedia // eslint-disable-line no-undef
  );
}

class Webcam extends Component {
  static mountedInstances = [];

  static userMediaRequested = false;

  constructor() {
    super();
    this.state = {
      hasUserMedia: false,
    };
  }

  componentDidMount() {
    const { hasUserMedia } = this.state;
    if (!hasGetUserMedia()) return;

    Webcam.mountedInstances.push(this);

    if (!hasUserMedia && !Webcam.userMediaRequested) {
      this.requestUserMedia();
    }
  }

  componentWillUpdate(nextProps) {
    const { audioConstraints, videoConstraints } = this.props;
    if (
      JSON.stringify(nextProps.audioConstraints)
        !== JSON.stringify(audioConstraints)
      || JSON.stringify(nextProps.videoConstraints)
        !== JSON.stringify(videoConstraints)
    ) {
      this.requestUserMedia();
    }
  }

  componentWillUnmount() {
    const { hasUserMedia, src } = this.state;
    const index = Webcam.mountedInstances.indexOf(this);
    Webcam.mountedInstances.splice(index, 1);

    Webcam.userMediaRequested = false;
    if (Webcam.mountedInstances.length === 0 && hasUserMedia) {
      if (this.stream.getVideoTracks && this.stream.getAudioTracks) {
        this.stream.getVideoTracks().map(track => track.stop());
        this.stream.getAudioTracks().map(track => track.stop());
      } else {
        this.stream.stop();
      }
      // eslint-disable-next-line no-undef
      window.URL.revokeObjectURL(src);
    }
  }

  getScreenshot() {
    const { hasUserMedia } = this.state;
    const { screenshotFormat, screenshotQuality } = this.props;
    if (!hasUserMedia) return null;

    const canvas = this.getCanvas();
    return (
      canvas
      && canvas.toDataURL(
        screenshotFormat,
        screenshotQuality,
      )
    );
  }

  getCanvas() {
    const { hasUserMedia } = this.state;
    const { screenshotWidth } = this.props;
    if (!hasUserMedia || !this.video.videoHeight) return null;

    if (!this.ctx) {
      // eslint-disable-next-line no-undef
      const canvas = document.createElement('canvas');
      const aspectRatio = this.video.videoWidth / this.video.videoHeight;

      const canvasWidth = screenshotWidth || this.video.clientWidth;

      canvas.width = canvasWidth;
      canvas.height = canvasWidth / aspectRatio;

      this.canvas = canvas;
      this.ctx = canvas.getContext('2d');
    }

    const { ctx, canvas } = this;
    ctx.drawImage(this.video, 0, 0, canvas.width, canvas.height);

    return canvas;
  }

  checkCanvas() {
    const { hasUserMedia } = this.state;
    return hasUserMedia;
  }

  requestUserMedia() {
    const { audio, audioConstraints, videoConstraints } = this.props;
    navigator.getUserMedia = navigator.mediaDevices.getUserMedia// eslint-disable-line no-undef
      || navigator.webkitGetUserMedia// eslint-disable-line no-undef
      || navigator.mozGetUserMedia// eslint-disable-line no-undef
      || navigator.msGetUserMedia;// eslint-disable-line no-undef

    const sourceSelected = (audioConstraints, videoConstraints) => {
      const constraints = {
        video: videoConstraints || true,
      };

      if (audio) {
        constraints.audio = audioConstraints || true;
      }
      // eslint-disable-next-line no-undef
      navigator.mediaDevices
        .getUserMedia(constraints)
        .then((stream) => {
          Webcam.mountedInstances.forEach(instance => instance.handleUserMedia(null, stream));
        })
        .catch((e) => {
          Webcam.mountedInstances.forEach(instance => instance.handleUserMedia(e));
        });
    };
    // eslint-disable-next-line no-undef
    if ('mediaDevices' in navigator) {
      sourceSelected(audioConstraints, videoConstraints);
    } else {
      const optionalSource = id => ({ optional: [{ sourceId: id }] });

      const constraintToSourceId = (constraint) => {
        const deviceId = (constraint || {}).deviceId;

        if (typeof deviceId === 'string') {
          return deviceId;
        } if (Array.isArray(deviceId) && deviceId.length > 0) {
          return deviceId[0];
        } if (typeof deviceId === 'object' && deviceId.ideal) {
          return deviceId.ideal;
        }

        return null;
      };
      // eslint-disable-next-line no-undef
      MediaStreamTrack.getSources((sources) => {
        let audioSource = null;
        let videoSource = null;

        sources.forEach((source) => {
          if (source.kind === 'audio') {
            audioSource = source.id;
          } else if (source.kind === 'video') {
            videoSource = source.id;
          }
        });

        const audioSourceId = constraintToSourceId(audioConstraints);
        if (audioSourceId) {
          audioSource = audioSourceId;
        }

        const videoSourceId = constraintToSourceId(videoConstraints);
        if (videoSourceId) {
          videoSource = videoSourceId;
        }

        sourceSelected(
          optionalSource(audioSource),
          optionalSource(videoSource),
        );
      });
    }

    Webcam.userMediaRequested = true;
  }


  handleUserMedia(err, stream) {
    const { onUserMediaError, onUserMedia } = this.props;
    if (err) {
      this.setState({ hasUserMedia: false });
      onUserMediaError(err);

      return;
    }

    this.stream = stream;

    try {
      this.video.srcObject = stream;
      this.setState({ hasUserMedia: true });
    } catch (error) {
      this.setState({
        hasUserMedia: true,
        src: window.URL.createObjectURL(stream), // eslint-disable-line no-undef
      });
    }
    onUserMedia();
  }

  render() {
    const {
      width, height, audio, className, style
    } = this.props;
    const { src } = this.state;
    return (
      // eslint-disable-next-line jsx-a11y/media-has-caption
      <div className={style.cameraWrapper}>
        <img src={qrMarker} alt="qr code marker" className={style.qrMarker} />
        <video
          autoPlay
          width={width}
          height={height}
          src={src}
          muted={audio}
          className={className}
          playsInline
          ref={(ref) => {
            this.video = ref;
          }}
        />
      </div>
    );
  }
}

const constrainStringType = PropTypes.oneOfType([
  PropTypes.string,
  PropTypes.arrayOf(PropTypes.string),
  PropTypes.shape({
    exact: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]),
  }),
  PropTypes.shape({
    ideal: PropTypes.oneOfType([
      PropTypes.string,
      PropTypes.arrayOf(PropTypes.string),
    ]),
  }),
]);

const constrainBooleanType = PropTypes.oneOfType([
  PropTypes.shape({
    exact: PropTypes.bool,
  }),
  PropTypes.shape({
    ideal: PropTypes.bool,
  }),
]);

const constrainLongType = PropTypes.oneOfType([
  PropTypes.number,
  PropTypes.shape({
    exact: PropTypes.number,
    ideal: PropTypes.number,
    min: PropTypes.number,
    max: PropTypes.number,
  }),
]);

const constrainDoubleType = constrainLongType;

const audioConstraintType = PropTypes.shape({
  deviceId: constrainStringType,
  groupId: constrainStringType,
  autoGainControl: constrainBooleanType,
  channelCount: constrainLongType,
  latency: constrainDoubleType,
  noiseSuppression: constrainBooleanType,
  sampleRate: constrainLongType,
  sampleSize: constrainLongType,
  volume: constrainDoubleType,
});

const videoConstraintType = PropTypes.shape({
  deviceId: constrainStringType,
  groupId: constrainStringType,
  aspectRatio: constrainDoubleType,
  facingMode: constrainStringType,
  frameRate: constrainDoubleType,
  height: constrainLongType,
  width: constrainLongType,
});

Webcam.propTypes = {
  audio: PropTypes.bool,
  onUserMedia: PropTypes.func,
  onUserMediaError: PropTypes.func,
  height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  screenshotFormat: PropTypes.oneOf([
    'image/webp',
    'image/png',
    'image/jpeg',
  ]),
  style: PropTypes.shape({}),
  className: PropTypes.string,
  screenshotQuality: PropTypes.number,
  screenshotWidth: PropTypes.number,
  audioConstraints: audioConstraintType,
  videoConstraints: videoConstraintType,
};

Webcam.defaultProps = {
  audio: true,
  className: '',
  height: 480,
  onUserMedia: () => {},
  onUserMediaError: () => {},
  screenshotFormat: 'image/webp',
  width: 640,
  screenshotQuality: 0.92,
};

export default Webcam;
