/*
http://patorjk.com/software/taag/#p=display&f=ANSI%20Regular&t=Server

███████ ███████ ██████  ██    ██ ███████ ██████  
██      ██      ██   ██ ██    ██ ██      ██   ██ 
███████ █████   ██████  ██    ██ █████   ██████  
     ██ ██      ██   ██  ██  ██  ██      ██   ██ 
███████ ███████ ██   ██   ████   ███████ ██   ██                                           
*/

require("dotenv").config();
const express = require("express");
const path = require("path");
const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server().listen(server);
const ngrok = require("ngrok");

var ngrokEnabled = process.env.NGROK_ENABLED;
var ngrokAuthToken = process.env.NGROK_AUTH_TOKEN;
var turnUrls = process.env.TURN_URLS;
var turnUsername = process.env.TURN_USERNAME;
var turnCredential = process.env.TURN_PASSWORD;

// use all static files from the www folder
app.use(express.static(path.join(__dirname, "www")));

// =====================================================
// expose server to external with https tunnel using ngrok
// =====================================================
async function ngrokstart() {
  try {
    await ngrok.authtoken(ngrokAuthToken);
    await ngrok.connect(PORT);
    const api = ngrok.getApi();
    let data = await api.get("api/tunnels");
    data = JSON.parse(data);
    //console.log(data);
    const pu0 = data.tunnels[0].public_url;
    const pu1 = data.tunnels[1].public_url;
    const tunelHttps = pu0.startsWith("https") ? pu0 : pu1;
    console.log("ngrok-tunnel", { https: tunelHttps });
    // https://www.iditect.com/how-to/55122741.html
  } catch (e) {
    console.error("[Error] ngrokstart", e);
  }
}

/*
 * You should probably use a different stun-turn server doing commercial stuff
 * Also see: https://gist.github.com/zziuni/3741933 or https://www.twilio.com/docs/stun-turn
 * Check the functionality of STUN/TURN servers: https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
 */
var ice_servers = [
  { urls: "stun:stun.l.google.com:19302" },
  {
    urls: turnUrls,
    username: turnUsername,
    credential: turnCredential,
  },
];

// =====================================================
// Start Local Server with ngrok https tunel
// =====================================================
var PORT = process.env.PORT || 80;
server.listen(PORT, null, function () {
  console.log(
    `%c

	███████╗██╗ ██████╗ ███╗   ██╗      ███████╗███████╗██████╗ ██╗   ██╗███████╗██████╗ 
	██╔════╝██║██╔════╝ ████╗  ██║      ██╔════╝██╔════╝██╔══██╗██║   ██║██╔════╝██╔══██╗
	███████╗██║██║  ███╗██╔██╗ ██║█████╗███████╗█████╗  ██████╔╝██║   ██║█████╗  ██████╔╝
	╚════██║██║██║   ██║██║╚██╗██║╚════╝╚════██║██╔══╝  ██╔══██╗╚██╗ ██╔╝██╔══╝  ██╔══██╗
	███████║██║╚██████╔╝██║ ╚████║      ███████║███████╗██║  ██║ ╚████╔╝ ███████╗██║  ██║
	╚══════╝╚═╝ ╚═════╝ ╚═╝  ╚═══╝      ╚══════╝╚══════╝╚═╝  ╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝ started...

	`,
    "font-family:monospace"
  );

  if (ngrokEnabled == "true") {
    ngrokstart();
  }
  // init settings
  console.log("settings", {
    http: "http://localhost:" + PORT,
    iceServers: ice_servers,
  });
});

// All URL patterns should served with the same file.
app.get(["/", "/:room"], (req, res) =>
  res.sendFile(path.join(__dirname, "www/index.html"))
);

var channels = {}; // collect channels
var sockets = {}; // collect sockets

/**
 * Users will connect to the signaling server, after which they'll issue a "join"
 * to join a particular channel. The signaling server keeps track of all sockets
 * who are in a channel, and on join will send out 'addPeer' events to each pair
 * of users in a channel. When clients receive the 'addPeer' even they'll begin
 * setting up an RTCPeerConnection with one another. During this process they'll
 * need to relay ICECandidate information to one another, as well as SessionDescription
 * information. After all of that happens, they'll finally be able to complete
 * the peer connection and will be in streaming audio/video between eachother.
 */
// =====================================================
// on peer connected
// =====================================================
io.sockets.on("connect", (socket) => {
  console.log("[" + socket.id + "] --> connection accepted");

  socket.channels = {};
  sockets[socket.id] = socket;

  // =====================================================
  // on peer diconnected
  // =====================================================
  socket.on("disconnect", () => {
    for (var channel in socket.channels) {
      removePeerFrom(channel);
    }
    console.log("[" + socket.id + "] <--> disconnected");
    delete sockets[socket.id];
  });

  // =====================================================
  // on peer join
  // =====================================================
  socket.on("join", (config) => {
    console.log("[" + socket.id + "] --> join ", config);
    var channel = config.channel;

    if (channel in socket.channels) {
      console.log("[" + socket.id + "] [Warning] already joined", channel);
      return;
    }

    if (!(channel in channels)) {
      channels[channel] = {};
    }
    //
    for (id in channels[channel]) {
      // offer false
      channels[channel][id].emit("addPeer", {
        peer_id: socket.id,
        should_create_offer: false,
        iceServers: ice_servers,
      });
      // offer true
      socket.emit("addPeer", {
        peer_id: id,
        should_create_offer: true,
        iceServers: ice_servers,
      });
      console.log("[" + socket.id + "] emit add Peer [" + id + "]");
    }

    channels[channel][socket.id] = socket;
    socket.channels[channel] = channel;
  });

  // =====================================================
  // remove peers
  // =====================================================
  function removePeerFrom(channel) {
    if (!(channel in socket.channels)) {
      console.log("[" + socket.id + "] [Warning] not in ", channel);
      return;
    }

    delete socket.channels[channel];
    delete channels[channel][socket.id];

    for (id in channels[channel]) {
      channels[channel][id].emit("removePeer", { peer_id: socket.id });
      socket.emit("removePeer", { peer_id: id });
      console.log("[" + socket.id + "] emit remove Peer [" + id + "]");
    }
  }

  // =====================================================
  // relay ICE to peers
  // =====================================================
  socket.on("relayICE", (config) => {
    let peer_id = config.peer_id;
    let ice_candidate = config.ice_candidate;
    /*
    console.log(
      "[" + socket.id + "] relay ICE-candidate to [" + peer_id + "] ",
      { address: config.ice_candidate.address }
    ); // ice_candidate
    */
    if (peer_id in sockets) {
      sockets[peer_id].emit("iceCandidate", {
        peer_id: socket.id,
        ice_candidate: ice_candidate,
      });
    }
  });

  // =====================================================
  // relay SDP to peers
  // =====================================================
  socket.on("relaySDP", (config) => {
    let peer_id = config.peer_id;
    let session_description = config.session_description;

    console.log(
      "[" + socket.id + "] relay SessionDescription to [" + peer_id + "] ",
      { type: session_description.type }
    ); // session_description

    if (peer_id in sockets) {
      sockets[peer_id].emit("sessionDescription", {
        peer_id: socket.id,
        session_description: session_description,
      });
    }
  });
}); // end [sockets.on-connect]
