'use strict';

var debug = require('debug');
var log = debug('libp2p:webrtc-star');
var multiaddr = require('multiaddr');
var mafmt = require('mafmt');
var parallel = require('run-parallel');
var io = require('socket.io-client');
var EE = require('events').EventEmitter;
var SimplePeer = require('simple-peer');
var Duplexify = require('duplexify');
var peerId = require('peer-id');
var PeerInfo = require('peer-info');

exports = module.exports = WebRTCStar;

function WebRTCStar() {
  var _this = this;

  if (!(this instanceof WebRTCStar)) {
    return new WebRTCStar();
  }

  var listeners = [];
  var mhSelf = void 0;
  this.discovery = new EE();

  this.dial = function (multiaddr, options) {
    if (!options) {
      options = {};
    }

    options.ready = options.ready || function noop() {};
    var pt = new Duplexify();

    var intentId = (~ ~(Math.random() * 1e9)).toString(36) + Date.now();
    var sioClient = listeners[0];
    var conn = new SimplePeer({ initiator: true, trickle: false });

    conn.on('signal', function (signal) {
      sioClient.emit('ss-handshake', {
        intentId: intentId,
        srcMultiaddr: mhSelf.toString(),
        dstMultiaddr: multiaddr.toString(),
        signal: signal
      });
    });

    sioClient.on('ws-handshake', function (offer) {
      if (offer.intentId !== intentId || !offer.answer) {
        return;
      }

      conn.on('connect', function () {
        pt.setReadable(conn);
        pt.setWritable(conn);

        pt.destroy = conn.destroy.bind(conn);

        conn.on('close', function () {
          pt.emit('close');
        });

        pt.getObservedAddrs = function () {
          return [multiaddr];
        };
        options.ready(null, pt);
      });
      conn.signal(offer.signal);
    });

    return pt;
  };

  this.createListener = function (multiaddrs, handler, callback) {
    if (!Array.isArray(multiaddrs)) {
      multiaddrs = [multiaddrs];
    }

    var sioOptions = {
      transports: ['websocket'],
      'force new connection': true
    };
    // for now it only supports listening in one signalling server
    // no technical limitation why not to do more :)
    var mh = multiaddrs[0];
    mhSelf = mh;
    // I know.. "websockets connects on a http endpoint, but through a
    // tcp port"
    var sioUrl = 'http://' + mh.toString().split('/')[3] + ':' + mh.toString().split('/')[5];
    var sioClient = io.connect(sioUrl, sioOptions);
    sioClient.on('connect_error', callback);
    sioClient.on('connect', function () {
      sioClient.emit('ss-join', multiaddrs[0].toString());
      sioClient.on('ws-handshake', incommingDial);
      sioClient.on('ws-peer', peerDiscovered.bind(_this));
      listeners.push(sioClient);
      callback();
    });

    function incommingDial(offer) {
      if (offer.answer) {
        return;
      }

      var conn = new SimplePeer({ trickle: false });

      conn.on('connect', function () {
        conn.getObservedAddrs = function () {
          return [];
        };

        handler(conn);
      });

      conn.on('signal', function (signal) {
        offer.signal = signal;
        offer.answer = true;
        sioClient.emit('ss-handshake', offer);
      });

      conn.signal(offer.signal);
    }
  };

  this.close = function (callback) {
    if (listeners.length === 0) {
      log('Called close with no active listeners');
      return callback();
    }

    parallel(listeners.map(function (listener) {
      return function (cb) {
        listener.emit('ss-leave');
        cb();
      };
    }), callback);
  };

  this.filter = function (multiaddrs) {
    if (!Array.isArray(multiaddrs)) {
      multiaddrs = [multiaddrs];
    }
    return multiaddrs.filter(function (ma) {
      return mafmt.WebRTCStar.matches(ma);
    });
  };

  function peerDiscovered(mh) {
    var id = peerId.createFromB58String(mh.split('/')[8]);
    var peer = new PeerInfo(id);
    peer.multiaddr.add(multiaddr(mh));
    this.discovery.emit('peer', peer);
  }
}