/*jslint nomen: true, indent: 2, maxerr: 3, maxlen: 80*/
/*global window, RSVP, rJS, WebTorrent*/
(function (window, RSVP, rJS, document, crypto, WebTorrent) {
  "use strict";

  var gadget_klass = rJS(window),
      template_element = gadget_klass.__template_element,
      torrent_info_template = Handlebars.compile(template_element
                                  .getElementById("torrent-info-template")
                                  .innerHTML);

  // Human readable bytes util
  function prettyBytes(num) {
    var exponent,
      unit,
      neg = num < 0,
      units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];

    if (neg) {
      num = -num;
    }
    if (num < 1) {
      return (neg ? '-' : '') + num + ' B';
    }
    exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)),
                        units.length - 1);
    num = Number((num / Math.pow(1000, exponent)).toFixed(2));
    unit = units[exponent];
    return (neg ? '-' : '') + num + ' ' + unit;
  }

  function onProgress(gadget, torrent, element) {
    var num_peers = element.querySelector(".numPeers"),
        progressBar = element.querySelector(".progressBar"),
        downloaded = element.querySelector(".downloaded"),
        total = element.querySelector(".total"),
        remaining_html = element.querySelector(".remaining"),
        remaining,
        downloadSpeed = element.querySelector(".downloadSpeed"),
        uploadSpeed = element.querySelector(".uploadSpeed");
    // Peers
    num_peers.innerHTML = torrent.numPeers +
      (torrent.numPeers === 1 ? ' peer' : ' peers');

    // Progress
    var percent = Math.round(torrent.progress * 100 * 100) / 100;
    progressBar.style.width = percent + '%';
    downloaded.innerHTML = prettyBytes(torrent.downloaded);
    total.innerHTML = prettyBytes(torrent.length);

    // Remaining time
    if (torrent.done) {
      remaining = 'Done.';
    } else {
      remaining = moment.duration(torrent.timeRemaining / 1000, 'seconds')
        .humanize();
      remaining = remaining[0].toUpperCase() +
        remaining.substring(1) + ' remaining.';
    }
    remaining_html.innerHTML = remaining;

    // Speed rates
    downloadSpeed.innerHTML = prettyBytes(torrent.downloadSpeed) + '/s';
    uploadSpeed.innerHTML = prettyBytes(torrent.uploadSpeed) + '/s';
  }

  gadget_klass

    .declareAcquiredMethod("jio_get", "jio_get")
    .declareAcquiredMethod("jio_put", "jio_put")
    .declareAcquiredMethod("jio_post", "jio_post")
    .declareAcquiredMethod("jio_allDocs", "jio_allDocs")
    .declareAcquiredMethod("notifySubmitted", "notifySubmitted")
    .declareAcquiredMethod("notifySubmitting", "notifySubmitting")
    .declareAcquiredMethod("getSetting", "getSetting")
    .declareAcquiredMethod("setSetting", "setSetting")
    .declareAcquiredMethod("redirect", "redirect")

    .ready(function () {
      var gadget = this;

      return gadget.getDeclaredGadget("model")
      .push(function (model) {
        return gadget.changeState({
          model_gadget: model,
          client: new WebTorrent(),
          seed_index: {}
        });
      });
    })

    .declareMethod("render", function (options) {
      var gadget = this;

    })

    .declareMethod("digestMessage", function digestMessage(text, algorithm) {
      if (!algorithm)
        algorithm = 'SHA-256';
      return new RSVP.Queue()
        .push(function () {
          var uint8 = new TextEncoder().encode(text);
          return crypto.subtle.digest(algorithm, uint8);
        })
        .push(function (hash_buffer) {
          var hash_array;
          hash_array = Array.from(new Uint8Array(hash_buffer));
          return hash_array.map(b => b.toString(16).padStart(2, '0')).join('');
        });
    })

    .declareMethod("index_to_file", function (index_name) {
      var gadget = this,
          ids,
          index_id = "index-" + index_name,
          file_list = [];

      return gadget.state.model_gadget.getAttachment(
        index_id,
        "ids",
        {"format": "json"})
      .push(function (result) {
        ids = result;
        file_list.push(new File(
          [new Blob([JSON.stringify(result)], {type : "application/json"})],
          index_id + '.ids',
          {type: 'application/json'})
        );
        return gadget.state.model_gadget.getAttachment(
          index_id,
          "map",
          {"format": "text"});
      })
      .push(function (result) {
        file_list.push(new File(
          [new Blob([result], {type : "application/json"})],
          index_id + '.map',
          {type: 'application/json'})
        );
        return gadget.state.model_gadget.getAttachment(
          index_id,
          "ctx",
          {"format": "text"});
      })
      .push(function (result) {
        var i,
            get_page,
            page_queue = new RSVP.Queue();

        get_page = function (page_id) {
          page_queue
            .push(function () {
              return gadget.state.model_gadget.get_page(page_id);
            })
            .push(function (page) {
              file_list.push(new File(
                [new Blob([JSON.stringify(page)], {type : "application/json"})],
                page_id + '.page',
                {type: 'application/json'})
              );
            });
        }

        file_list.push(new File(
          [new Blob([result], {type : "application/json"})],
          index_id + '.ctx',
          {type: 'application/json'})
        );
        // get all page of this index
        for (i = 0; i < ids.length; i += 1) {
          get_page(ids[i].slice(1));
        }
        return page_queue
      })
      .push(function () {
        return file_list;
      });
    })

    .declareMethod("file_to_index", function (torrent) {
      var gadget = this,
          extract_blob,
          blob_list = [],
          name_list = [],
          failed = false,
          file_list = torrent.files;

      //quick check of file consitency here before insert
      extract_blob = function (err, blob) {
        var file;
        if (err || failed) {
          console.log("Failed to extract torrent file...");
          console.log(err);
          failed = true;
          return;
        }
        if (blob !== null && blob !== undefined) {
          blob_list.push(blob);
        }
        if (file_list.length > 0) {
          file = file_list.shift();
          name_list.push(file.name);
          // extract next blob file
          console.log("Extracting ... " + file.name);
          return file.getBlob(extract_blob);
        }
        else {
          // blob extracted, save them now
          if ((blob_list.length !== name_list.length) || failed) {
            console.log("Torrent extraction failed!!!");
            return;
          }
          return gadget.saveExtractedIndex(torrent, name_list, blob_list)
        }
      }
      // extract all blob from files
      return extract_blob();
    })

    .declareMethod("saveExtractedIndex", function (torrent,
                                                   name_list,
                                                   blob_list) {
      var gadget = this,
          i,
          insert_queue = new RSVP.Queue(),
          insert_blob,
          insert_page,
          insert_doc,
          current,
          type,
          index_name,
          id;

      insert_doc = function (blob) {
        insert_queue
          .push(function () {
            return jIO.util.readBlobAsText(blob);
          })
          .push(function (evt) {
            var doc = JSON.parse(evt.target.result);
            return gadget.state.model_gadget.createIndexDoc({
              title: doc.title,
              links: doc.links,
              magnet_uri: torrent.magnetURI,
              status: "built"
            });
          })
      }

      insert_blob = function (index_id, type, blob) {
        insert_queue
          .push(function () {
            return gadget.state.model_gadget.putAttachment(
              index_id,
              type,
              blob);
          })
      }

      insert_page = function (page_id, blob) {
        insert_queue
          .push(function () {
            return jIO.util.readBlobAsText(blob);
          })
          .push(function (evt) {
            return gadget.state.model_gadget.put(
              page_id, JSON.parse(evt.target.result));
          })

      }

      for (i = 0; i < blob_list.length;  i += 1) {
        current = name_list[i];
        if (current.endsWith('page')) {
          type = 'page';
          id = current.slice(0,-5);
        } else {
          type = current.slice(-3);
          id = current.slice(0,-4);
        }
        if (type === 'page') {
          insert_page(id, blob_list[i]);
        } else if (type === 'ctx') {
          insert_blob(id, type, blob_list[i]);
        } else if (type === 'map') {
          insert_blob(id, type, blob_list[i]);
        } else if (type === 'ids') {
          index_name = id.split("index-")[1]
          insert_blob(id, type, blob_list[i]);
        } else if (type === 'doc') {
          insert_doc(blob_list[i]);
        }
      }
      return insert_queue
        .push(function () {
            return gadget.state.model_gadget.pushIndex(index_name);
        })
        .push(function () {
          gadget.notifySubmitted({
            message: "Index '" + index_name + "' Saved.",
            status: "success"
          })
        })
        .push(function () {
          if (gadget.state.redirect) {
            return gadget.redirect({command: 'display', options: {
              page: gadget.state.redirect
            }});
          }
          gadget.state.redirect = "";
        });
    })

    .onLoop(function () {
      var gadget = this,
          container = document.getElementById('seedactivities'),
          info,
          element,
          i,
          key_list = [];
      if (!container)
        return;
      key_list = Object.keys(gadget.state.seed_index);
      for (i = 0; i < key_list.length; i += 1) {
        let torrent = gadget.state.seed_index[key_list[i]];
        if (!torrent.ready) {
          continue;
        }
        if (document.getElementById(torrent.infoHash)) {
          // already present
          continue;
        }
        info = container.querySelector('h2.info')
        if (info) {
          info.remove();
        }
        let torrent_template = torrent_info_template({
          magnet_title: torrent.comment,
          magnet_link: torrent.name,
          magnet_uri: torrent.magnetURI,
          torrent_link: torrent.torrentFileBlobURL,
          infohash: torrent.infoHash,
          is_seed: true
        });
        container.innerHTML += torrent_template;
      }
    }, 5000)

    /*******************************/
    /**    Seed torrent here     **/
    /*****************************/

    .declareMethod("seedIndex", function (id) {
      var gadget = this,
          get_name,
          on_torrent,
          seed_file,
          seed_index,
          torrent_dict = [],
          torrent_name,
          index_doc;

      get_name = function (link_list) {
        var name = "";
        link_list.sort();
        name = link_list.join('');
        return gadget.digestMessage(name)
          .push(function (hash) {
             return hash + ".json";
          });
      }

      on_torrent = function (torrent) {
        //gadget.addSeedContent(torrent);
        gadget.state.seed_index[id] = torrent;
        torrent.on('warning', function (err) {
          console.error(err.stack || err.message || err)
        });

        torrent.on('error', function (err) {
          console.error(err.stack || err.message || err)
          return gadget.notifySubmitted({
            message: "Error: " + err.message || err,
            status: "error"
          });
        });

        torrent.on('upload', function (bytes) {
          var content = document.getElementById(torrent.infoHash);
          if (content) {
            onProgress(gadget, torrent, content);
          }
        });

        console.log(torrent);
        return gadget.notifySubmitted({
            message: "Seeding index '" + torrent.comment + "'...",
            status: "success"
          })
          .push(function () {
            var id;
            for (id in torrent_dict) {
              if (torrent_dict[id] === torrent.name) {
                return gadget.jio_get(id);
              }
            }
          });
      };

      return gadget.jio_get(id)
        .push(function (doc) {
          var url_list = doc.links.replace('\n', ' ').match(/[^ ]+/g);
          index_doc = doc;
          return get_name(url_list)
        })
        .push(function (name) {
          torrent_name = name;
          torrent_dict[id] = name;
          return gadget.index_to_file(index_doc.title);
        })
        .push(function (index_file_list) {
          var doc = {title: index_doc.title, links: index_doc.links};
          // Add index document to blob
          index_file_list.push(new File(
            [new Blob([JSON.stringify(doc)],
                      {type : "application/json"})],
            index_doc.title + '.doc',
            {type: 'application/json'})
          );
          return gadget.state.client.seed(index_file_list, {
            name: torrent_name,
            createdBy: "Nexedi SA",
            private: false,
            announceList: [
              ['wss://tracker.fastcast.nz'],
              ['wss://tracker.openwebtorrent.com'],
              ['wss://tracker.btorrent.xyz']
            ],
            comment: index_doc.title
           },
          on_torrent
        );
      });
    })

    .declareJob("seedAll", function () {
      var gadget = this;

      function seedTorrent() {
        return gadget.jio_allDocs({
          query: 'portal_type: "Mynij Search Collection"',
          "select_list": ["seed"]
        })
          .push(function (result_list) {
            var i,
              promise_list = [];
            console.log(result_list);
            for (i = 0; i < result_list.data.rows.length; i += 1) {
              let item = result_list.data.rows[i];
              if (item.value.seed === "on" &&
                  !gadget.state.seed_index.hasOwnProperty(item.id)) {
                gadget.state.seed_index[item.id] = {};
                promise_list.push(gadget.seedIndex(item.id));
                console.log("call seed to " + item.id);
              }
            }
            return RSVP.all(promise_list);
        })
        .push(function () {
          return RSVP.delay(60000);
        })
        .push(function () {
          return seedTorrent();
        });
      }

      return seedTorrent();
    })

    .declareJob("download", function (torrent_id, redirect) {
      var gadget = this,
          torrent_template,
          container = gadget.element.querySelector('.torrent-download');

      if (!torrent_id) {
        return;
      }
      gadget.state.redirect = redirect;
      container.classList.remove('hidden');
      return gadget.notifySubmitting()
      .push(function () {
        // Download the torrent
        return gadget.state.client.add(torrent_id, function (torrent) {
          var container_h2 = container.querySelector("h2.info"),
              element;

          function onDone () {
            clearInterval(gadget.state.interval);
            gadget.element.querySelector(".torrentBoddy")
              .classList.add("is-seed");
            onProgress(gadget, torrent, element);
            return gadget.file_to_index(torrent);
          }

          if (container_h2) {
            container_h2.remove();
          }
          torrent_template = torrent_info_template({
            magnet_title: torrent.comment || torrent.files[0].name.slice(0, -4),
            magnet_link: torrent.name,
            torrent_link: torrent.torrentFileBlobURL,
            infohash: torrent.infoHash,
            is_seed: false
          });
          container.innerHTML = torrent_template;

          element = document.getElementById(torrent.infoHash);
          torrent.on('error', function (err) {
            clearInterval(gadget.state.interval);
            console.error(err.stack || err.message || err);
            return gadget.notifySubmitted({
              message: "Error: " + err.message || err,
              status: "error"
            });
          });

          // Trigger statistics refresh
          torrent.on('done', onDone);
          gadget.state.interval = setInterval(function(){
            onProgress(gadget, torrent, element);
          }, 500);

          onProgress(gadget, torrent, element);
        });
      });
    });

}(window, RSVP, rJS, document, crypto, WebTorrent));