import React from "react";
import { Spin } from "antd";
import { sldComLanguage } from "@/utils/utils";
import styles from "./index.less";

const PI = Math.PI;

function sum(x, y) {
  return x + y;
}
function square(x) {
  return x * x;
}

class SlideVerify extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      success: false,
      containerActive: false,
      containerSuccess: false,
      containerFail: false,
      sliderLeft: 0,
      sliderWidth: 0,
      loadBlock: true,
      blockX: 0,
      blockY: 0,
    };

    this.origin = { x: 0, y: 0 };
    this.isMouseDown = false;
    this.timestamp = 0;
    this.trail = [];

    this.blockRef = React.createRef();
    this.canvasRef = React.createRef();

    this.img = null;

    this.handleStart = this.handleStart.bind(this);
    this.handleMove = this.handleMove.bind(this);
    this.handleEnd = this.handleEnd.bind(this);
    this.reset = this.reset.bind(this);
    this.refresh = this.refresh.bind(this);
  }

  componentDidMount() {
    this.initCanvas();

    document.addEventListener("mousemove", this.handleMove);
    document.addEventListener("mouseup", this.handleEnd);

    // 如果需要支持移动端，也加对应touch事件监听
    document.addEventListener("touchmove", this.handleMove, { passive: false });
    document.addEventListener("touchend", this.handleEnd);
  }

  componentWillUnmount() {
    document.removeEventListener("mousemove", this.handleMove);
    document.removeEventListener("mouseup", this.handleEnd);

    document.removeEventListener("touchmove", this.handleMove);
    document.removeEventListener("touchend", this.handleEnd);
  }

  draw = (ctx, x, y, l, r, operation) => {
    ctx.beginPath();
    ctx.moveTo(x, y);
    ctx.arc(x + l / 2, y - r + 2, r, 0.72 * PI, 2.26 * PI);
    ctx.lineTo(x + l, y);
    ctx.arc(x + l + r - 2, y + l / 2, r, 1.21 * PI, 2.78 * PI);
    ctx.lineTo(x + l, y + l);
    ctx.lineTo(x, y + l);
    ctx.arc(x + r - 2, y + l / 2, r + 0.4, 2.76 * PI, 1.24 * PI, true);
    ctx.lineTo(x, y);
    ctx.lineWidth = 2;
    ctx.fillStyle = "rgba(255, 255, 255, 0.7)";
    ctx.strokeStyle = "rgba(255, 255, 255, 0.7)";
    ctx.stroke();
    ctx[operation]();
    ctx.globalCompositeOperation = "destination-over";
  };

  getRandomNumberByRange = (start, end) => {
    return Math.round(Math.random() * (end - start) + start);
  };

  getRandomImg = (imgs, w, h) => {
    if (!imgs || imgs.length === 0) {
      return `https://picsum.photos/300/150?random=${Date.now()}`;
    }
    const idx = getRandomNumberByRange(0, imgs.length - 1);
    return imgs[idx];
  };

  createImg = (imgs, onload) => {
    const img = document.createElement("img");
    img.crossOrigin = "Anonymous";
    img.onload = onload;
    img.onerror = () => {
      img.src = this.getRandomImg(imgs);
    };
    img.src = this.getRandomImg(imgs);
    return img;
  };

  initCanvas() {
    const { w, h, l, r, imgs } = this.props;
    const canvas = this.canvasRef.current;
    const block = this.blockRef.current;
    if (!canvas || !block) return;

    this.canvasCtx = canvas.getContext("2d");
    this.blockCtx = block.getContext("2d", { willReadFrequently: true });

    this.img = this.createImg(imgs, () => {
      this.setState({ loadBlock: false });

      const L = l + r * 2 + 3;
      const blockX = this.getRandomNumberByRange(L + 10, w - (L + 10));
      const blockY = this.getRandomNumberByRange(10 + r * 2, h - (L + 10));

      this.setState({ blockX, blockY });

      if (this.canvasCtx && this.blockCtx) {
        this.draw(this.canvasCtx, blockX, blockY, l, r, "fill");
        this.draw(this.blockCtx, blockX, blockY, l, r, "clip");

        this.canvasCtx.drawImage(this.img, 0, 0, w, h);
        this.blockCtx.drawImage(this.img, 0, 0, w, h);

        const _y = blockY - r * 2 - 1;
        const imgData = this.blockCtx.getImageData(blockX, _y, L, L);
        block.width = L;
        this.blockCtx.putImageData(imgData, 0, _y);
      }

      if (this.props.onImageLoad) this.props.onImageLoad();
    });
  }

  handleStart(e) {
    if (this.state.success) return;

    const nativeEvent = e.nativeEvent || e;

    if (nativeEvent.touches && nativeEvent.touches.length > 0) {
      this.origin.x = nativeEvent.touches[0].pageX;
      this.origin.y = nativeEvent.touches[0].pageY;
    } else if ("clientX" in nativeEvent && "clientY" in nativeEvent) {
      this.origin.x = nativeEvent.clientX;
      this.origin.y = nativeEvent.clientY;
    }
    this.isMouseDown = true;
    this.trail = [];
    this.timestamp = Date.now();
  }

  handleMove(e) {
    if (!this.isMouseDown) return false;

    // 阻止滚动等默认事件
    e.preventDefault();

    const nativeEvent = e.nativeEvent || e;

    let moveX = 0;
    let moveY = 0;

    if (nativeEvent.touches && nativeEvent.touches.length > 0) {
      moveX = nativeEvent.touches[0].pageX - this.origin.x;
      moveY = nativeEvent.touches[0].pageY - this.origin.y;
    } else if ("clientX" in nativeEvent && "clientY" in nativeEvent) {
      moveX = nativeEvent.clientX - this.origin.x;
      moveY = nativeEvent.clientY - this.origin.y;
    }

    const { w } = this.props;

    if (moveX < 0 || moveX + 38 >= w) return false;

    this.setState({
      sliderLeft: moveX,
      sliderWidth: moveX,
      containerActive: true,
      containerFail: false,
      containerSuccess: false,
    });

    this.trail.push(moveY);
  }

  handleEnd(e) {
    if (!this.isMouseDown) return false;
    this.isMouseDown = false;

    const nativeEvent = e.nativeEvent || e;

    let moveX;

    if (nativeEvent.changedTouches && nativeEvent.changedTouches.length > 0) {
      moveX = nativeEvent.changedTouches[0].pageX;
    } else if ("clientX" in nativeEvent) {
      moveX = nativeEvent.clientX;
    } else {
      moveX = this.origin.x;
    }

    if (moveX === this.origin.x) return false;

    const timestamp = Date.now() - this.timestamp;

    const { spliced, TuringTest } = this.verify(
      this.state.sliderLeft,
      this.state.blockX,
      this.props.accuracy
    );

    if (spliced) {
      if (TuringTest) {
        this.setState({
          success: true,
          containerSuccess: true,
          containerFail: false,
          sliderLeft: this.state.blockX,
          sliderWidth: this.state.blockX,
        });
        this.props.onSuccess && this.props.onSuccess(timestamp);
      } else {
        this.setState({
          containerFail: true,
          containerSuccess: false,
          sliderLeft: 0,
          sliderWidth: 0,
        });
        this.props.onAgain && this.props.onAgain();
      }
    } else {
      this.setState({
        containerFail: true,
        containerSuccess: false,
        sliderLeft: 0,
        sliderWidth: 0,
      });
      this.refresh();
    }
  }

  verify(left, blockX, accuracy) {
    const arr = this.trail;
    const sum = (x, y) => x + y;
    const square = (x) => x * x;

    const average = arr.reduce(sum, 0) / arr.length || 0;
    const deviations = arr.map((x) => x - average);
    const stddev = Math.sqrt(
      deviations.map(square).reduce(sum, 0) / arr.length || 0
    );

    const leftNum = parseInt(left);
    accuracy = accuracy <= 1 ? 1 : accuracy > 10 ? 10 : accuracy;

    return {
      spliced: Math.abs(leftNum - blockX) <= accuracy,
      TuringTest: average !== stddev,
    };
  }

  reset() {
    const { w, h, l, r, imgs } = this.props;
    this.setState({
      success: false,
      containerActive: false,
      containerSuccess: false,
      containerFail: false,
      sliderLeft: 0,
      sliderWidth: 0,
      loadBlock: true,
    });

    const block = this.blockRef.current;
    if (block) block.style.left = "0px";
    if (this.canvasCtx) this.canvasCtx.clearRect(0, 0, w, h);
    if (this.blockCtx) this.blockCtx.clearRect(0, 0, w, h);
    if (block) block.width = w;

    this.img.src = this.getRandomImg(imgs);
  }

  refresh() {
    this.reset();
    this.props.onRefresh && this.props.onRefresh();
  }

  render() {
    const { w, h, sliderText, show } = this.props;
    const {
      loadBlock,
      containerActive,
      containerSuccess,
      containerFail,
      sliderLeft,
      sliderWidth,
      success,
    } = this.state;

    // 容器className 组合
    const containerCls = [
      styles["slide-verify-slider"],
      containerActive ? styles["container-active"] : "",
      containerSuccess ? styles["container-success"] : "",
      containerFail ? styles["container-fail"] : "",
    ].join(" ");

    return (
      <div
        id="slideVerify"
        className={styles["slide-verify"]}
        style={{ width: w }}
        onSelect={(e) => e.preventDefault()}
      >
        <Spin spinning={loadBlock}>
          <canvas
            ref={this.canvasRef}
            width={w}
            height={h}
            className={styles["slide-verify-canvas"]}
          />

          {show && (
            <div
              className={styles["slide-verify-refresh-icon"]}
              onClick={this.refresh}
            >
              <i className={`iconfont ${styles["icon-refresh"]}`} />
            </div>
          )}

          <canvas
            ref={this.blockRef}
            width={w}
            height={h}
            className={styles["slide-verify-block"]}
            style={{ left: `${sliderLeft}px`, position: "absolute", top: 0 }}
          />

          <div className={containerCls}>
            <div
              className={styles["slide-verify-slider-mask"]}
              style={{ width: sliderWidth }}
            >
              <div
                className={styles["slide-verify-slider-mask-item"]}
                style={{ left: sliderLeft }}
                onMouseDown={this.handleStart}
                onTouchStart={this.handleStart}
                onTouchMove={this.handleMove}
                onTouchEnd={this.handleEnd}
              >
                <img
                  draggable={false}
                  src={
                    success
                      ? require("./icons/success.png")
                      : require("./icons/right.png")
                  }
                />
              </div>
            </div>
            <span className={styles["slide-verify-slider-text"]}>
              {sliderText}
            </span>
          </div>
          <div className={styles["bottom-action"]} onClick={this.refresh}>
            <img src={require("./icons/refresh.png")} />
            <span>{sldComLanguage('刷新')}</span>
          </div>
        </Spin>
      </div>
    );
  }
}

SlideVerify.defaultProps = {
  l: 42,
  r: 10,
  w: 310,
  h: 155,
  sliderText: sldComLanguage("拖动滑块来填充拼图"),
  accuracy: 5,
  show: true,
  imgs: [],
  interval: 50,
};

export default SlideVerify;
