import invariant from "invariant";
import React, { Component } from "react";
import { DeviceEventEmitter, findNodeHandle, processColor, UIManager } from "react-native";
import captureFrame from "./GLCanvas.captureFrame";
import GLCanvasNative from './spec/fabric/RNCGLCanvasNativeComponent'

const serializeOption = config =>
  config.format + ":" + config.type + ":" + config.quality;

export default class GLCanvas extends Component {

  constructor(props) {
    super(props)
    this._pendingCaptureFrame = {};
    this.state = {
      onLoad: null,
      onProgress: null,
      onLoadError: null,
      onContextLost: null,
      onContextRestored: null,
    }
  }

  viewConfig = {
    uiViewClassName: "GLCanvas"
  };

  componentDidMount() {
    let propsItem = this.refs
    this.state.onLoad = DeviceEventEmitter.addListener('onLoad', () => {
      this.props.onLoad?.(propsItem)
    })
    this.state.onProgress = DeviceEventEmitter.addListener('onProgress', (item) => {
      this.props.onProgress?.({ nativeEvent: { ...item }, ...propsItem })
    })
    this.state.onLoadError = DeviceEventEmitter.addListener('onLoadError', (item) => {
      this.props.onLoadError?.(item)
    })
    this.state.onContextLost = DeviceEventEmitter.addListener('onContextLost', (item) => {
      this.props.onContextLost?.(item)
    })
    this.state.onContextLost = DeviceEventEmitter.addListener('onContextRestored', () => {
      this.props.onContextRestored?.('')
    })
  }

  componentWillUnmount() {
    Object.keys(this._pendingCaptureFrame).forEach(key =>
      this._pendingCaptureFrame[key].reject(new Error("GLCanvas is unmounting")));
    this._pendingCaptureFrame = null;
    this.state.onLoad && this.state.onLoad.remove()
    this.state.onProgress && this.state.onProgress.remove()
    this.state.onLoadError && this.state.onLoadError.remove()
    this.state.onContextLost && this.state.onContextLost.remove()
    this.state.onContextRestored && this.state.onContextRestored.remove()
  }

  setNativeProps(props) {
    this.refs.native.setNativeProps(props);
  }

  _addPendingCaptureFrame(config) {
    const key = serializeOption(config);
    return this._pendingCaptureFrame[key] || (
      (captureFrame(findNodeHandle(this.refs.native), config),
        this._pendingCaptureFrame[key] = this._makeDeferred())
    );
  }

  _makeDeferred() {
    var defer = {};
    var p = new Promise(function (resolve, reject) {
      defer.resolve = resolve;
      defer.reject = reject;
    });
    defer.promise = p;
    return defer;
  }

  captureFrame(configArg) {
    let config;
    if (configArg) {
      invariant(typeof configArg === "object", "captureFrame takes an object option in parameter");
      let nb = 0;
      if ("format" in configArg) {
        invariant(
          typeof configArg.format === "string",
          "captureFrame({format}): format must be a string (e.g: 'base64'), Got: '%s'",
          configArg.format);
        if (configArg.format === "file") invariant(
          typeof configArg.filePath === "string" && configArg.filePath,
          "captureFrame({filePath}): filePath must be defined when using 'file' format and be an non-empty string, Got: '%s'",
          configArg.filePath);
        nb++;
      }
      if ("type" in configArg) {
        invariant(
          typeof configArg.type === "string",
          "captureFrame({type}): type must be a string (e.g: 'png', 'jpg'), Got: '%s'",
          configArg.type);
        nb++;
      }
      if ("quality" in configArg) {
        invariant(
          typeof configArg.quality === "number" &&
          configArg.quality >= 0 &&
          configArg.quality <= 1,
          "captureFrame({quality}): quality must be a number between 0 and 1, Got: '%s'",
          configArg.quality);
        nb++;
      }
      if ("filePath" in configArg) {
        nb++;
      }
      const keys = Object.keys(configArg);
      invariant(keys.length === nb, "captureFrame(config): config must be an object with {format, type, quality, filePath}, found some invalid keys in '%s'", keys);
      config = configArg;
    }
    return this._addPendingCaptureFrame({
      format: "base64",
      type: "png",
      quality: 1,
      filePath: "",
      ...config
    }).promise;
  }

  onGLCaptureFrame = ({ nativeEvent: { error, result, config } }) => {
    const key = serializeOption(config);
    invariant(key in this._pendingCaptureFrame, "capture '%s' is not scheduled in this._pendingCaptureFrame", key);
    if (error) {
      this._pendingCaptureFrame[key].reject(error);
    }
    else {
      this._pendingCaptureFrame[key].resolve(result);
    }
    delete this._pendingCaptureFrame[key];
  };

  render() {
    const {
      width, height, style = {},
      onLoad, onProgress, eventsThrough,
      ...restProps } = this.props;
    const { backgroundColor } = style;
    console.info('glRN', JSON.stringify(this.props));
    return <GLCanvasNative
      ref="native"
      {...restProps}
      backgroundColor={processColor(backgroundColor)}
      style={{ width, height }}
      children={JSON.stringify(this.props.data.children)}
      uri={this.props.data.children[0].uniforms.t?.uri}
      onGLLoad={onLoad ? onLoad : null}
      onGLProgress={onProgress ? onProgress : null}
      onGLCaptureFrame={this.onGLCaptureFrame}
      pointerEvents={eventsThrough ? "none" : "auto"}
    />;
  }
}

