<!doctype html>
<html>
<head>
    <meta charset="utf-8">
    <title>WebRTC test stub</title>
    <!-- supervisor -i dist -n exit `which grunt` -->
    <!-- open /Applications/Google\ Chrome\ Canary.app --args --enable-data-channels -->
    <script src="pouchdb-nightly.js"></script>
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <style>
      a:not([href]):not(.action) { text-decoration: underline; color: lightgrey; }
      a.action { text-decoration: underline; cursor: pointer; }
      .dangerous { color: red; }
      
      #hubStatus { color: grey; }
      #hubStatus.ready { color: green; }
      #hubStatus.error { color: orange; }
    </style>
</head>
<body>

<h1>PeerPouch: PouchDB-over-WebRTC</h1>

<h2>Hub</h2>
<p>
The hub is a "centralized" (shared, anyway) database used for presence and connection setup signalling.
It can be any PouchDB database accessible by both peers; a design doc will be installed.
Once the connection is established, database requests are exchanged directly between peers.
</p>
Status: <span id=hubStatus>-</span>
<form id=hubSettings>
  <input id=hubURL value="idb://webrtc-test"> <button>Join</button>
  <a id=leaveHub class="action">Leave</a>
  <a id=resetHub class="dangerous action">Reset</a>
  <br>
  <input id=name placeholder="Your alias (optional)">
</form>

<h2>My shares</h2>
<p>
This part isn't really so much implemented…yet!
When it is, you will be able to delegate access to a database accessible locally, to any peer anywhere!
</p>
<ul id=shares></ul>
<a class=action id=addShare>+ share a database</a>

<h2>Peers</h2>
<p>
To fully connect with peers, you may need some browser settings or pre-release versions and unfortunately, data connections between Chrome and Firefox are not interoperable yet either.
See the <a href="http://www.webrtc.org/chrome">Chrome</a> / <a href="http://www.webrtc.org/firefox">Firefox</a> / <a href="http://www.webrtc.org/interop">Interop</a> notes on <a href="http://www.webrtc.org/">WebRTC.org</a> for some deets.
</p>
<p>
Also: peers don't tend to do a good job of cleaning themselves up either. We may need to implement something of a heartbeat and toss/ignore stale peers. (…but *carefully* so as not to totally prevent slow gossip-type meshy networks, built using interreplicating PeerPouch instances as the hubs themselves, from springing up!)
</p>
<ul id=peers></ul>
<a class=action id=addPeer>+ add example peer</a>


<script>
var hub, presence, peers,
    hubStatus = d3.select('#hubStatus');
function clearHub() {
  hubStatus.text("leaving").classed('ready', false).classed('error', false);
  function done() {
    hubStatus.text("n/a");
  }
  if (presence) presence.leaveHub(done);
  else done();
  hub = presence = peers = null;
  updatePeersList();
}
function joinHub(url, name, identity) {
  hubStatus.text("opening").classed('ready', false).classed('error', false);
  Pouch(url, function (e, db) {
    if (e) {
      hubStatus.text("error").classed('error', true);
      throw e;
    }
    hub = db;
    hubStatus.text("preparing");
    Pouch.dbgPeerPouch.Presence.verifyHub(hub, function (e, v) {
      if (e) {
        hubStatus.text("error").classed('error', true);
        throw e;
      }
      hubStatus.text("joining");
      presence = Pouch.dbgPeerPouch.Presence(hub, {name:name,identity:identity}, function () {
        hubStatus.text("ready").classed('ready', true);
      });
      
      peers = [];
      function handlePeerChange(peer) {
        var peerIdx = -1;
        peers.forEach(function (p, i) {
          if (p.identity === peer.identity) peerIdx = i;
        });
        if (~peerIdx && !peer._deleted) peers[peerIdx] = peer;
        else if (~peerIdx) peers.splice(peerIdx, 1);
        else if (!peer._deleted) peers.push(peer);
        // HACK: only update list if we're NOT getting preChangedPeers.forEach'ed
        if (arguments.length === 1) updatePeersList();
      }
      presence.getPeers({onChange:handlePeerChange}, function (e, initialList) {
        if (e) throw e;
        var preChangedPeers = peers;
        peers = initialList;
        // NOTE: this is potentially ineffecient; we assume preChangedPeers.length ≪ initialList.length
        preChangedPeers.forEach(handlePeerChange);    
        updatePeersList();
      });
    });
  });
}
clearHub();

d3.select('#hubSettings').on('submit', function () {
  d3.event.preventDefault();
  clearHub();
  
  var url = d3.select('#hubURL').property('value'),
      name = d3.select('#name').property('value'),
      idt = 'peer-'+Math.random().toFixed(16).slice(2);
  if (!name) {
    name = 'Peer #' + idt.slice(-4);
    d3.select('#name').property('value', name);
  }
  joinHub(url, name, idt);
});

d3.select('#resetHub').on('click', function () {
  d3.event.preventDefault();
  
  var hubURL = d3.select('#hubURL').property('value');
  var really = confirm("This will completely destroy " + hubURL);
  if (really) {
    hubStatus.text("destroying").classed('ready', false).classed('error', false);
    Pouch.destroy("idb://webrtc-test", function (e) {
      if (e) {
        hubStatus.text("destroy error").classed('error', true);
        throw e;
      }
      clearHub();
    });
  }
});

d3.select('#leaveHub').on('click', function () {
  if (d3.event) d3.event.preventDefault();
  clearHub();
});

d3.select('#addShare').on('click', function () {
  if (d3.event) d3.event.preventDefault();
  alert("Sorry, not yet implemented.");
});

d3.select('#addPeer').on('click', function () {
  if (d3.event) d3.event.preventDefault();
  // TODO: pass along our hubURL…and auto-join?
  window.open(document.location);
});

d3.select(window).on('beforeunload', function () {
  clearHub();     // NOTE: this is likely to fail :-( https://code.google.com/p/chromium/issues/detail?id=144862#c4
});

function updatePeersList() {
  var items = d3.select('#peers').selectAll('li').data(peers||[], function (d) { return d.identity; }),
      itemsEnter = items.enter().append('li');
  itemsEnter.append('h3');
  itemsEnter.append('a').classed('action', true).text("pre-connect").on('click', function (d) {
    var el = d3.select(this);
    el.classed('action', false).text("attempting…");
    presence.connectToPeer(d, function () {
      el.text("connected!").style('color', "green");    // TODO: show connection status when peer initiates!
      presence.sendToPeer(d, {testing:123});
    });
  }).style('display', "block");
  itemsEnter.append('span');
  items.select('h3').text(function (d) { return d.name; });
  items.select('span').text(function (d) { return d.profile.browser + ", last updated @ " + new Date(d.lastUpdate); });
  items.exit().remove();
}

</script>
</body>
</html>