const seedTimeout = 60000;
import { webtorrent, base_host } from '$/client/config.js';
import assert from "$/client/assert.js";
import { log } from "$/client/logging.js";

/* This needs an entire rewrite using the FSM code.  Most of this is hacked on
 * garbage that is unreliable at the best of times.
 */

// WARNING: having svelte build the webtorrent client into the script makes it slow to load and massive.
// it's much faster to set it as a script variable in the index.html but that might cause loading problems
// WebTorrent then comes from the public/index.html file, not from an import in here

let LAZY_CLIENT;  // this is lazy loaded by getClient

const getClient = () => {
  // super hacks!
  if(LAZY_CLIENT) {
    return LAZY_CLIENT;
  } else {
    // eslint will complain but webtorrent is loaded by <script>
    let client = new WebTorrent(webtorrent);

    // would need to do some kind of global status for these since it's not per torrent
    if(webtorrent.rate !== undefined) {
      client.throttleDownload(webtorrent.rate);
      client.throttleUpload(webtorrent.rate);
    }

    client.on('error', err => log.error(err));
    client.on('listening', () => log.debug('LISTENING:'));
    LAZY_CLIENT = client;
    return LAZY_CLIENT;
  }
}

export class WTEvents {
  constructor(media, video_opts) {
    assert(media, "Media must exist.");
    this.media = media;
    this.video_opts = video_opts || {autoplay: false, controls: true, muted: false}
    this.appendId = `append-${media.target_id}`;
  }

  setTorrent(torrent) {
    // TODO: refine this
    this.media.torrent = torrent;
    this.torrent = torrent;
  }

  videoReady(file) {
    this.media.kind = 'video';
    file.renderTo(`#${this.appendId}`, this.video_opts);
    file.getBlobURL((err, url) => {
      if(err) log.error(err, "getBlobURL error videoReady");
      this.download_available(url);
    });
  }

  audioReady(file) {
    this.media.kind = 'audio';
    file.renderTo(`#${this.appendId}`, this.video_opts);
    file.getBlobURL((err, url) => {
      if(err) log.error(err, "getBlobURL error audioReady");
      this.download_available(url);
    });
  }

  otherReady(file) {
    file.renderTo(`#${this.appendId}`, (err, elem) => {
      // TODO: hmmm do we need to do anything here with elem?
      if(err) {
        log.error(err);
      } else {
        // keep this active for 60 second
        let client = getClient();
        setTimeout(() => client.remove(this.torrent.infoHash), seedTimeout);
      }
    });

    file.getBlobURL((err, url) => {
      if(err) log.error(err, "getBlobURL error");
      this.download_available(url);
    });
  }

  download_available(url) {
    log.debug("download url available");
  }

  done() {
    log.debug("done", this.media);
  }

  infoHash(hash) {
    this.media.info_hash = hash;
  }

  metadata(data) {
    this.media.metadata = data;
  }

  ready() {
    return true;
  }

  warning(msg) {
    log.debug("WT WARNING", msg);
  }

  error(msg) {
    log.debug("WT ERROR", msg);
  }

  download(bytes) {
    return bytes !== undefined;
  }

  upload(bytes) {
    return bytes !== undefined;
  }

  wire(wire) {
    // this weird idiom is to make eslint shut up for these placeholders
    return wire !== undefined;
  }

  noPeers(announceType) {
    log.debug('noPeers from', announceType);
  }

  client_error(err) {
    log.error(err, "client error");
    return true;
  }
}


const handle_torrent = (media, torrent, events) => {
  torrent.files.forEach((file) => {
    media.file_name = file.name;

    if(media.file_name.endsWith('mp4') || media.file_name.endsWith('webm')) {
      events.videoReady(file);
    } else if(media.file_name.endsWith('mp3')) {
      events.audioReady(file);
    } else {
      events.otherReady(file);
    }
  });

  // do not remove these wrapping functions.  without them you'll have the classic JS error
  // of this inside the events class being randomly set to torrent or whatever it wants.
  // You have to call them inside a closure like this to make sure events.this doesn't change.
  torrent.on('done', () => events.done());
  torrent.on('infoHash', hash => events.infoHash(hash));
  torrent.on('metadata', data => events.metadata(data));
  torrent.on('ready', () => events.ready());
  torrent.on('warning', warning => events.warning(warning));
  torrent.on('error', msg => events.error(msg));
  torrent.on('download', bytes => events.download(bytes));
  torrent.on('upload', bytes => events.upload(bytes));
  torrent.on('wire', wire => events.wire(wire));
  torrent.on('noPeers', announceType => events.noPeers(announceType));
}

export const fetch_torrent_file = async (media) => {
  assert(media, "media can't be undefined");
  assert(media.torrent_url, "media does not have torrent_url set");

  media.full_torrent_url = `${base_host}${media.torrent_url}`;

  let res = await fetch(media.full_torrent_url, { credentials: "same-origin" });

  if(res.status === 200) {
    media.torrent_file = await res.blob();
    return true;
  } else {
    return false;
  }
}

// this thing's job is to load the torrents with webtorrent and then fill out the active_media
// Debug in the browser with:  localStorage.debug = '*';
export const load = (media, events) => {
  assert(media, "media can't be undefined");
  assert(media.torrent_file, "media does not have torrent_file set");

  let client = getClient();

  client.add(media.torrent_file, {private: webtorrent.private, withCredentials: true}, (torrent) => {
    events.setTorrent(torrent);
    handle_torrent(media, torrent, events);
  });
}

export const remove = (media) => {
  if(media.torrent) {
    getClient().remove(media.torrent);
  } else {
    log.error("remove called with a media object without a torrent", media);
  }
}

export default { load, remove, fetch_torrent_file, WTEvents };
