import React, { Component, createRef, Fragment } from "react";
import store from "store";
import { message, Modal, Button } from "antd";
import "./App.less";
import { Router, navigate } from "@reach/router";
import Controller from "./components/Controller";
import Setting from "./components/Setting";
import SpeedDisplay from "./components/SpeedDisplay";
import { ReloadOutlined } from "@ant-design/icons";
import Login from "./components/Login";
import md5 from "md5";
import debounce from "debounce";
import Status from "./components/Status";
import localChannelStatus from "./lib/localChannelStatus";
import axios from "axios";
import WebRTC from "./lib/WebRTC";

const pubilcUrl = process.env.PUBLIC_URL;

export default class App extends Component {
  constructor(props) {
    super(props);
    this.appRef = createRef();
    this.state = {
      editabled: false,
      cameraList: [],
      setting: {
        host: window.location.host,
        webrtcEnabled: true,
        ...store.get("setting"),
      },
      serverConfig: {},
      gpioChannelStatus: {},
      wsConnected: false,
      cameraEnabled: false,
      canvasRef: undefined,
      isAiControlling: false,
      isFullscreen: false,
      videoSize: 50,
      delay: undefined,
      connectType: "ws",
      action: {
        speed: 0,
        direction: 0,
      },
      isLogin: false,
      volume: 0,
      micVolume: 0,
      session: {},
      webrtcChannel: [],
      locaked: false,
      enabledControllerMicphone: false,
      statusInfo: {},
    };

    this.controller = {
      speed: (v) => {
        const {
          changeSpeed,
          state: { action },
        } = this;
        action.speed = v;
        this.setState({ action: { ...action } });
        changeSpeed(action.speed);
      },
      direction: (v) => {
        const {
          changeDirection,
          state: { action },
        } = this;
        action.direction = v;
        changeDirection(v);
        this.setState({ action: { ...action } });
      },
    };
    this.saveServerConfig = debounce(this._saveServerConfig, 300);
    this.resetChannel = debounce(this._resetChannel, 300);
    this.reconnectTimer = null;
    this.connecting = false;
  }

  componentDidMount() {
    const { connect } = this;
    connect();

    document.body.addEventListener("fullscreenchange", () => {
      if (document.fullscreenElement) {
        this.setState({ isFullscreen: true });
      } else {
        this.setState({ isFullscreen: false });
      }
    });
  }

  connect = () => {
    if (this.connecting) return;
    this.connecting = true;
    if (this.reconnectTimer) {
      clearTimeout(this.reconnectTimer);
      this.reconnectTimer = null;
    }

    const { host } = this.state.setting;
    let pingTime, heartbeatTime;

    const scheduleReconnect = () => {
      if (this.reconnectTimer) return;
      this.reconnectTimer = setTimeout(() => {
        this.reconnectTimer = null;
        message.error("断开连接，正在重连！");
        this.connect();
      }, 3000);
    };

    const socket = (this.socket = new WebSocket(
      `${
        window.location.protocol === "https:" ? "wss://" : "ws://"
      }${host}/control`
    ));
    socket.binaryType = "arraybuffer";

    socket.addEventListener("open", () => {
      this.connecting = false;
      this.setState({
        wsConnected: true,
        connectType: "ws",
      });
      pingTime = setInterval(() => {
        const sendTime = new Date().getTime();
        this.sendData("ping", { sendTime });
      }, 1000);

      heartbeatTime = setInterval(() => {
        this.sendData("heartbeat");
      }, 200);

      setTimeout(() => this.login(), 200);
    });

    socket.addEventListener("message", async ({ data }) => {
      this.messageHandle(data);
    });

    socket.addEventListener("error", () => {
      this.connecting = false;
      scheduleReconnect();
      try {
        socket.close();
      } catch (e) {}
    });

    socket.addEventListener("close", () => {
      this.connecting = false;
      clearInterval(pingTime);
      clearInterval(heartbeatTime);
      this.setState({ wsConnected: false });
      scheduleReconnect();
    });
  };

  messageHandle(data) {
    const { gpioChannelStatus, serverConfig, statusInfo } = this.state;

    if (typeof data === "string") {
      const { action, payload } = JSON.parse(data);
      switch (action) {
        case "connect type":
          this.setState({ connectType: payload });
          break;
        case "camera list":
          this.setState({ cameraList: payload });
          break;
        case "login":
          this.onLogin(payload);
          break;
        case "config":
          this.setState({ serverConfig: { ...serverConfig, ...payload } });
          break;
        case "config update":
          this.getServerConfig();
          break;
        case "volume":
          this.setState({ volume: payload });
          break;
        case "micVolume":
          this.setState({ micVolume: payload });
          break;
        case "channel status":
          this.setState({
            gpioChannelStatus: { ...gpioChannelStatus, ...payload },
          });
          break;
        case "locked":
          this.setState({ locked: payload });
          break;

        case "status info":
          if (payload && payload.remove) {
            const nextStatus = { ...statusInfo };
            delete nextStatus[payload.label];
            this.setState({ statusInfo: nextStatus });
          } else {
            this.setState({
              statusInfo: { ...statusInfo, [payload.label]: payload },
            });
          }
          break;

        case "pong":
          this.setState({
            delay: (new Date().getTime() - payload.sendTime) / 2,
          });
          break;
        case "info":
          message.info(payload.message);
          break;
        case "warn":
          message.warn(payload.message);
          break;
        case "error":
          if (payload.type === "auth error") {
            if (window.location.pathname !== "/login") {
              navigate(`${pubilcUrl}/login`);
            }
          }
          message.error(payload.message);
          break;
        case "success":
          message.success(payload.message);
          break;
        default:
          // message.info(`action: ${action}`)
          break;
      }
    }
  }

  sendData(action, payload) {
    if (this.state.wsConnected) {
      if (this?.webrtcChannel?.controller?.readyState === "open") {
        this.webrtcChannel.controller.send(JSON.stringify({ action, payload }));
      } else {
        this.socket.send(JSON.stringify({ action, payload }));
      }
    }
  }

  async getServerConfig() {
    this.setState({ loading: true });
    const { host } = this.state.setting;
    try {
      const { data: serverConfig } = await axios(`//${host}/config`);
      this.setState({ serverConfig });
    } finally {
      this.setState({ loading: false });
    }
  }

  onLogin = ({ message: m = "无密码", session } = {}) => {
    message.success(m);
    this.setState({ isLogin: true, session });
    store.set("RaspiCar-session", session);
    if (document.referrer.indexOf(window.location.host) > -1) {
      if (window.history.length > 1) {
        navigate(-1);
      } else {
        navigate(`${pubilcUrl}/controller`, { replace: true });
      }
    } else {
      navigate(`${pubilcUrl}/controller`, { replace: true });
    }

    this.getServerConfig();

    if (this.state.setting.webrtcEnabled) {
      this.openWebRTC();
    }
  };

  openWebRTC() {
    this.webrtc?.close?.();
    if (!this.state.isLogin) return;
    this.webrtc = new WebRTC({
      micphoneEanbled: this.state.enabledControllerMicphone,
      socket: this.socket,
      onClose() {
        delete this.webrtc;
      },
      onDataChannel: (rtcDataChannel) => {
        const { label } = rtcDataChannel;
        if (this.webrtcChannel) {
          this.webrtcChannel[label] = rtcDataChannel;
        } else {
          this.webrtcChannel = { [label]: rtcDataChannel };
        }
        this.setState({ webrtcChannel: this.webrtcChannel });
        if (rtcDataChannel.label === "controller") {
          this.setState({ locked: false });
          rtcDataChannel.addEventListener("message", ({ data }) =>
            this.messageHandle(data)
          );
        }
      },
      onDataChannelClose: (channel) => {
        if (this.webrtcChannel[channel.label]) {
          delete this.webrtcChannel[channel.label];
          this.setState({ webrtcChannel: this.webrtcChannel });
        }
      },
    });
  }

  playCarMicphonne = (playing) => {
    this.webrtc?.playAudio?.(playing);
  };

  disconnect = (e) => {
    e && e.preventDefault();
    this.setState({ wsConnected: false });
    if (!this.socket) return;
    this.socket.close();
  };

  changeControllerMicphone = () => {
    this.setState({
      enabledControllerMicphone: !this.state.enabledControllerMicphone,
    });
    this.webrtc?.changeMicrophone();
  };

  login = ({ password } = {}) => {
    const session = store.get("RaspiCar-session") || {};
    this.sendData("login", {
      token: password ? md5(`${password}eson`) : undefined,
      sessionId: session.id,
    });
  };

  piReboot = () => {
    const { wsConnected } = this.state;
    if (!wsConnected) return;
    const modal = Modal.warning({
      autoFocusButton: "cancel",
      icon: <ReloadOutlined />,
      title: "确定要重启树莓派？",
      content: (
        <Fragment>
          <Button
            type="primary"
            icon={<ReloadOutlined />}
            onClick={() => {
              this.sendData("pi reboot");
              modal.destroy();
            }}
          >
            重启树莓派
          </Button>
        </Fragment>
      ),
      maskClosable: true,
      okText: "取消",
    });
  };

  changeEditabled = (editabled) => {
    this.setState({ editabled });
  };

  changeSetting = (setting) => {
    this.setState({ setting });
    store.set("setting", setting);
    navigate(`${pubilcUrl}/controller`);
    window.location.reload();
  };

  changeZeroSpeedRate = (speedZeroRate) => {
    if (!this.state.wsConnected) return;
    this.sendData("speed zero rate", speedZeroRate);
    this.setState({ speedZeroRate });
  };

  changeSpeed = (speedRate) => {
    if (!this.state.wsConnected) return;
    this.sendData("speed rate", speedRate);
  };

  changeDirection = (directionRate) => {
    this.sendData("direction rate", directionRate);
  };

  playAudio = ({ path, stop = false } = {}) => {
    this.sendData("play audio", { path, stop });
  };

  changeVolume = (v) => {
    this.sendData("volume", v);
  };

  changeMicVolume = (v) => {
    this.sendData("micVolume", v);
  };

  changeChannel = (payload) => {
    const { pin, value } = payload;
    localChannelStatus[pin] = value;
    this.sendData("change channel", payload);
  };

  _saveServerConfig = (config) => {
    const {
      setting: { host },
    } = this.state;
    axios
      .post(`//${host}/config`, config)
      .then(() => {
        message.success("保存成功");
      })
      .catch((e) => {
        console.error(e);
      });
  };

  _resetChannel = () => {
    this.sendData("reset channel");
  };

  render() {
    const {
      disconnect,
      controller,
      changeChannel,
      changeSetting,
      login,
      saveServerConfig,
      resetChannel,
      changeVolume,
      changeMicVolume,
      changeEditabled,
      state: {
        cameraList,
        setting,
        wsConnected,
        cameraEnabled,
        action,
        isFullscreen,
        serverConfig,
        delay,
        isLogin,
        volume,
        micVolume,
        session,
        editabled,
        gpioChannelStatus,
        connectType,
        webrtcChannel,
        locked,
        enabledControllerMicphone,
        statusInfo,
      },
      playAudio,
      playCarMicphonne,
      changeControllerMicphone,
    } = this;

    return (
      <div className="App" ref={this.appRef}>
        {!isFullscreen && (
          <Status
            statusInfo={statusInfo}
            channelStatus={gpioChannelStatus}
            delay={delay}
            connectType={connectType}
            onCarMicphoneChange={playCarMicphonne}
            onControllerMicphoneChange={changeControllerMicphone}
            onReboot={this.piReboot}
            locked={locked}
            {...{
              wsConnected,
              isFullscreen,
              setting,
              isLogin,
              session,
              changeEditabled,
              editabled,
              changeChannel,
              serverConfig,
              enabledControllerMicphone,
            }}
            disabled={!isLogin}
          />
        )}

        <Router className="app-page">
          <Setting
            path={`${pubilcUrl}/setting`}
            {...setting}
            cameraList={cameraList}
            wsConnected={wsConnected}
            onDisconnect={disconnect}
            onSubmit={changeSetting}
            saveServerConfig={saveServerConfig}
            resetChannel={resetChannel}
            serverConfig={serverConfig}
            changeVolume={changeVolume}
            changeMicVolume={changeMicVolume}
            volume={volume}
            micVolume={micVolume}
          />
          {wsConnected && (
            <Login path={`${pubilcUrl}/login`} onSubmit={login} />
          )}
          {isLogin ? (
            <>
              <Controller
                statusInfo={statusInfo}
                connectType={connectType}
                session={session}
                path={`${pubilcUrl}/controller`}
                controller={controller}
                cameraEnabled={cameraEnabled}
                action={action}
                playAudio={playAudio}
                setting={setting}
                saveServerConfig={saveServerConfig}
                serverConfig={serverConfig}
                changeChannel={changeChannel}
                editabled={editabled}
                cameraList={cameraList}
                channelStatus={gpioChannelStatus}
                isFullscreen={isFullscreen}
                webrtcChannel={webrtcChannel}
                ws={this.socket}
                wsConnected={wsConnected}
              />
            </>
          ) : undefined}
        </Router>
      </div>
    );
  }
}
