'use strict';

var crypto = require('crypto');
//var dnsjs = require('dns-suite');
var dig = require('dig.js/dns-request');
var express = require('express');
var app = express();

var nameservers = require('dns').getServers();
var index = crypto.randomBytes(2).readUInt16BE(0) % nameservers.length;
var nameserver = nameservers[index];

app.use('/', express.static('./'));
app.use('/api', express.json());
app.get('/api/dns/:domain', function (req, res, next) {
  console.log(req.params);
  var domain = req.params.domain;
  var casedDomain = domain.toLowerCase().split('').map(function (ch) {
		// dns0x20 takes advantage of the fact that the binary operation for toUpperCase is
		// ch = ch | 0x20;
		return Math.round(Math.random()) % 2 ? ch : ch.toUpperCase();
	}).join('');
  var typ = req.query.type;
  var query = {
    header: {
      id: crypto.randomBytes(2).readUInt16BE(0)
    , qr: 0
    , opcode: 0
    , aa: 0 // Authoritative-Only
    , tc: 0                   // NA
    , rd: 1 // Recurse
    , ra: 0                   // NA
    , rcode: 0                // NA
    }
  , question: [
      { name: casedDomain
      //, type: typ || 'A'
      , typeName: typ || 'A'
      , className: 'IN'
      }
    ]
  };
	var opts = {
    onError: function (err) {
			next(err);
		}
  , onMessage: function (packet) {
			var fail0x20;

			if (packet.id !== query.id) {
				console.error('[SECURITY] ignoring packet for \'' + packet.question[0].name + '\' due to mismatched id');
				console.error(packet);
				return;
			}

			packet.question.forEach(function (q) {
				// if (-1 === q.name.lastIndexOf(cli.casedQuery))
				if (q.name !== casedDomain) {
					fail0x20 = q.name;
				}
			});

			[ 'question', 'answer', 'authority', 'additional' ].forEach(function (group) {
				(packet[group]||[]).forEach(function (a) {
					var an = a.name;
					var i = domain.toLowerCase().lastIndexOf(a.name.toLowerCase());  // answer is something like ExAMPle.cOM and query was wWw.ExAMPle.cOM
					var j = a.name.toLowerCase().lastIndexOf(domain.toLowerCase());  // answer is something like www.ExAMPle.cOM and query was ExAMPle.cOM

					// it's important to note that these should only relpace changes in casing that we expected
					// any abnormalities should be left intact to go "huh?" about
					// TODO detect abnormalities?
					if (-1 !== i) {
						// "EXamPLE.cOm".replace("wWw.EXamPLE.cOm".substr(4), "www.example.com".substr(4))
						a.name = a.name.replace(casedDomain.substr(i), domain.substr(i));
					} else if (-1 !== j) {
						// "www.example.com".replace("EXamPLE.cOm", "example.com")
						a.name = a.name.substr(0, j) + a.name.substr(j).replace(casedDomain, domain);
					}

					// NOTE: right now this assumes that anything matching the query matches all the way to the end
					// it does not handle the case of a record for example.com.uk being returned in response to a query for www.example.com correctly
					// (but I don't think it should need to)
					if (a.name.length !== an.length) {
						console.error("[ERROR] question / answer mismatch: '" + an + "' != '" + a.length + "'");
						console.error(a);
					}
				});
			});

			if (fail0x20) {
				console.warn(";; Warning: DNS 0x20 security not implemented (or packet spoofed). Queried '"
					+ casedDomain + "' but got response for '" + fail0x20 + "'.");
				return;
			}

			res.send({
        header: packet.header
      , question: packet.question
      , answer: packet.answer
      , authority: packet.authority
      , additional: packet.additional
      , edns_options: packet.edns_options
      });
		}
  , onListening: function () {}
  , onSent: function (/*res*/) { }
  , onTimeout: function (res) {
			console.error('dns timeout:', res);
			next(new Error("DNS timeout - no response"));
    }
  , onClose: function () { }
  //, mdns: cli.mdns
  , nameserver: nameserver
  , port: 53
  , timeout: 2000
  };

  dig.resolveJson(query, opts);
});

// curl -L http://localhost:3000/api/dns/example.com?type=A
console.log("Listening on localhost:3000");
app.listen(3000);
console.log("Try this:");
console.log("\tcurl -L 'http://localhost:3000/api/dns/example.com?type=A'");