;(function(){

	/* UNBUILD */
	var root;
	if(typeof window !== "undefined"){ root = window }
	if(typeof global !== "undefined"){ root = global }
	root = root || {};
	var console = root.console || {log: function(){}};
	function USE(arg, req){
		return req? require(arg) : arg.slice? USE[R(arg)] : function(mod, path){
			arg(mod = {exports: {}});
			USE[R(path)] = mod.exports;
		}
		function R(p){
			return p.split('/').slice(-1).toString().replace('.js','');
		}
	}
	if(typeof module !== "undefined"){ var common = module }
	/* UNBUILD */

	;USE(function(module){
    if(typeof window !== "undefined"){ module.window = window }
    var tmp = module.window || module;
		var AXE = tmp.AXE || function(){};

    if(AXE.window = module.window){ AXE.window.AXE = AXE }
    try{ if(typeof common !== "undefined"){ common.exports = AXE } }catch(e){}
    module.exports = AXE;
	})(USE, './root');
  
	;USE(function(module){

		var AXE = USE('./root'), Gun = (AXE.window||{}).Gun || USE('./gun', 1);
		(Gun.AXE = AXE).GUN = AXE.Gun = Gun;

		Gun.on('opt', function(at){
			start(at);
			this.to.next(at); // make sure to call the "next" middleware adapter.
		});

		function start(at){
			if(at.axe){ return }
			var opt = at.opt, peers = opt.peers;
			if(false === opt.axe){ return }
			if((typeof process !== "undefined") && 'false' === ''+(process.env||{}).AXE){ return }
			var axe = at.axe = {}, tmp;
			// 1. If any remembered peers or from last cache or extension
			// 2. Fallback to use hard coded peers from dApp
			// 3. Or any offered peers.
			//if(Gun.obj.empty(p)){
			//  Gun.obj.map(['http://localhost:8765/gun'/*, 'https://guntest.herokuapp.com/gun'*/], function(url){
			//    p[url] = {url: url, axe: {}};
			//  });
			//}
			// Our current hypothesis is that it is most optimal
			// to take peers in a common network, and align
			// them in a line, where you only have left and right
			// peers, so messages propagate left and right in
			// a linear manner with reduced overlap, and
			// with one common superpeer (with ready failovers)
			// in case the p2p linear latency is high.
			// Or there could be plenty of other better options.
			var mesh = opt.mesh = opt.mesh || Gun.Mesh(at);
			console.log("AXE enabled.");

			function verify(dht, msg) {
				var puts = Object.keys(msg.put);
				var soul = puts[0]; /// TODO: verify all souls in puts. Copy the msg only with subscribed souls?
				var subs = dht(soul);
				if (!subs) { return; }
				var tmp = [];
				Gun.obj.map(subs.split(','), function(pid) {
					if (pid in peers) {
						tmp.push(pid);
						mesh.say(msg, peers[pid]);
					}
				});
				/// Only connected peers in the tmp array.
				if (opt.super) {
					dht(soul, tmp.join(','));
				}
			}
			function route(get){ var tmp;
				if(!get){ return }
				if('string' != typeof (tmp = get['#'])){ return }
				return tmp;
			}

			var Rad = (Gun.window||{}).Radix || USE('./lib/radix', 1);
			at.opt.dht = Rad();
			at.on('in', function input(msg){
				var to = this.to, peer = (msg._||{}).via;
				var dht = opt.dht;
				var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
				var get = msg.get, hash, tmp;
				//if(get && opt.super && peer){
				if(get && opt.super && peer && (tmp = route(get))){
					hash = tmp; //Gun.obj.hash(get); // USE RAD INSTEAD!
					(routes[hash] || (routes[hash] = {}))[peer.id] = peer;
					(peer.routes || (peer.routes = {}))[hash] = routes[hash];

					/*if(soul = get['#']){ // SWITCH BACK TO USING DHT!
						if(key = get['.']){

						} else {

						}
						if (!peer.id) {console.log('[*** WARN] no peer.id %s', soul);}
						var pids = joindht(dht, soul, peer.id);
						if (pids) {
								var dht = {};
								dht[soul] = pids;
								mesh.say({dht:dht}, opt.peers[peer.id]);
						}
					}*/
				}
				if((tmp = msg['@']) && (tmp = at.dup.s[tmp]) && (tmp = tmp.it)){
					(tmp = (tmp._||ok)).ack = (tmp.ack || 0) + 1; // count remote ACKs to GET.
				}
				to.next(msg);

				if (opt.rtc && msg.dht) {
					Gun.obj.map(msg.dht, function(pids, soul) {
						dht(soul, pids);
						Gun.obj.map(pids.split(','), function(pid) {
							/// TODO: here we can put an algorithm of who must connect?
							if (!pid || pid in opt.peers || pid === opt.pid || opt.announce[pid]) { return; }
								opt.announce[pid] = true; /// To try only one connection to the same peer.
								opt.announce(pid);
						});
					});
				}
			});

			//try{console.log(req.connection.remoteAddress)}catch(e){};
			mesh.hear['opt'] = function(msg, peer){
				if(msg.ok){ return opt.log(msg) }
				var tmp = msg.opt;
				if(!tmp){ return }
				tmp = tmp.peers;
				if(!tmp || !Gun.text.is(tmp)){ return }
				if(axe.up[tmp] || 6 <= Object.keys(axe.up).length){ return }
				var o = tmp; //{peers: tmp};
				at.$.opt(o);
				o = peers[tmp];
				if(!o){ return }
				o.retry = 9;
				mesh.wire(o);
				if(peer){ mesh.say({dam: 'opt', ok: 1, '@': msg['#']}, peer) }
			}
			setInterval(function(tmp){
				if(!(tmp = at.stats && at.stats.stay)){ return }
				(tmp.axe = tmp.axe || {}).up = Object.keys(axe.up||{});
			},1000 * 60)
			setTimeout(function(tmp){
				if(!(tmp = at.stats && at.stats.stay)){ return }
				Gun.obj.map((tmp.axe||{}).up, function(url){ mesh.hear.opt({opt: {peers: url}}) })
			},1000);

			if(at.opt.super){
				var rotate = 0;
				mesh.way = function(msg) {
					if (msg.rtc) {
						if (msg.rtc.to) {
							/// Send announce to one peer only if the msg have 'to' attr
							var peer = (peers) ? peers[msg.rtc.to] : null;
							if (peer) { mesh.say(msg, peer); }
							return;
						}
					}
					if(msg.get){ mesh.say(msg, axe.up) } // always send gets up!
					if(msg.get && (tmp = route(msg.get))){
						var hash = tmp; //Gun.obj.hash(msg.get);
						var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
						var peers = routes[hash];
						function chat(peers, old){ // what about optimizing for directed peers?
							if(!peers){ return chat(opt.peers) }
							var ids = Object.keys(peers); // TODO: BUG! THIS IS BAD PERFORMANCE!!!!
							var meta = (msg._||yes);
							clearTimeout(meta.lack);
							var id, peer, c = 1; // opt. ?redundancy?
							while((id = ids[meta.turn || 0]) && c--){ // TODO: This hits peers in order, not necessarily best for load balancing. And what about optimizing for directed peers?
								peer = peers[id];
								meta.turn = (meta.turn || 0) + 1;
								if((old && old[id]) || false === mesh.say(msg, peer)){ ++c }
							}
							//console.log("AXE:", Gun.obj.copy(msg), meta.turn, c, ids, opt.peers === peers);
							if(0 < c){
								if(peers === opt.peers){ return } // prevent infinite lack loop.
								return meta.turn = 0, chat(opt.peers, peers) 
							}
							var hash = msg['##'], ack = meta.ack;
							meta.lack = setTimeout(function(){
								if(ack && hash && hash === msg['##']){ return }
								if(meta.turn >= (axe.turns || 3)){ return } // variable for later! Also consider ACK based turn limit.
								//console.log(msg['#'], "CONTINUE:", ack, hash, msg['##']);
								chat(peers, old); // keep asking for data if there is mismatching hashes.
							}, 25);
						}
						return chat(peers);
					}
					// TODO: PUTs need to only go to subs!
					if(msg.put){
						var routes = axe.routes || (axe.routes = {}); // USE RAD INSTEAD! TMP TESTING!
						var peers = {};
						Gun.obj.map(msg.put, function(node, soul){
							var hash = soul; //Gun.obj.hash({'#': soul});
							var to = routes[hash];
							if(!to){ return }
							Gun.obj.to(to, peers);
						});
						mesh.say(msg, peers);
						return;
					}
					mesh.say(msg, opt.peers); return; // TODO: DISABLE THIS!!! USE DHT!


					if (!msg.put) { mesh.say(msg); return; }
					//console.log('AXE HOOK!! ', msg);
					verify(opt.dht, msg);
				};
			} else {
				mesh.route = function(msg) {
					if (msg.rtc) {
					}
					if (!msg.put) { mesh.say(msg); return; }
					verify(opt.dht, msg);
					/// Always send to superpeers?
					Gun.obj.map(peers, function(peer) {
						if (peer.url) {
							mesh.say(msg, peer);
						}
					});
				};
				/*var connections = 0; // THIS HAS BEEN MOVED TO CORE NOW!
				at.on('hi', function(opt) {
					this.to.next(opt);
					//console.log('AXE PEER [HI]', new Date(), opt);
					connections++;
					/// The first connection don't need to resubscribe the nodes.
					if (connections === 1) { return; }
					/// Resubscribe all nodes.
					setTimeout(function() {
						var souls = Object.keys(at.graph);
						for (var i=0; i < souls.length; ++i) {
							//at.gun.get(souls[i]).off();
							at.next[souls[i]].ack = 0;
							at.gun.get(souls[i]).once(function(){});
						}
					//location.reload();
					}, 500);
				}, at);*/
			}
			axe.up = {};
			at.on('hi', function(peer){
				this.to.next(peer);
				if(!peer.url){ return }
				axe.up[peer.id] = peer;
			})
			at.on('bye', function(peer){ this.to.next(peer);
				if(peer.url){ delete axe.up[peer.id] }
				Gun.obj.map(peer.routes, function(route, hash){
					delete route[peer.id];
					if(Gun.obj.empty(route)){
						delete axe.routes[hash];
					}
				});
			});
		}

		function joindht(dht, soul, pids) {
			if (!pids || !soul || !dht) { return; }
			var subs = dht(soul);
			var tmp = subs ? subs.split(',') : [];
			Gun.obj.map(pids.split(','), function(pid) {
				if (pid && tmp.indexOf(pid) === -1) { tmp.push(pid); }
			});
			tmp = tmp.join(',');
			dht(soul, tmp);
			return tmp;
		}

		var empty = {}, yes = true, u;

		module.exports = AXE;
	})(USE, './axe');
}());