#!/usr/bin/env node

// a little node webserver designed to run the unit tests herein

var   sys = require("sys"),
     http = require("http"),
      url = require("url"),
     path = require("path"),
       fs = require("fs"),
  express = require("express");

var PRIMARY_HOST = "127.0.0.1";

var boundServers = [ ];

//
// this is the test harness, don't send emails, dump them to stdout
//
var nodemailer = require('nodemailer');
nodemailer.EmailMessage.prototype.send = function(callback) {
    this.prepareVariables();
    var headers = this.generateHeaders(),
        body = this.generateBody();
    console.log(headers);
    console.log(body);
};

// given a buffer, find and replace all production hostnames
// with development URLs
function subHostNames(data) {
  for (var i = 0; i < boundServers.length; i++) {
    var o = boundServers[i]
    var a = o.server.address();
    var from = o.name;
    var to = "http://" + a.address + ":" + a.port;
    data = data.toString().replace(new RegExp(from, 'g'), to);

    // now do another replacement to catch bare hostnames sans http(s)
    // and explicit cases where port is appended
    var fromWithPort;
    if (from.substr(0,5) === 'https') {
        from = from.substr(8);
        fromWithPort = from + ":443";
    } else {
        from = from.substr(7);
        fromWithPort = from + ":80";
    }
    to = to.substr(7);

    if (o.subPath) to += o.subPath;

    data = data.replace(new RegExp(fromWithPort, 'g'), to);
    data = data.replace(new RegExp(from, 'g'), to);
  }

  return data;
}

// Middleware that intercepts outbound textual responses and substitutes
// in development hostnames
function substitutionMiddleware(req, resp, next) {
    // cache the *real* functions
    var realWrite = resp.write;
    var realEnd = resp.end;
    var realWriteHead = resp.writeHead;
    var realSend = resp.send;

    var buf = undefined;
    var enc = undefined;
    var contentType = undefined;

    resp.writeHead = function (sc, reason, hdrs) {
        var h = undefined;
        if (typeof hdrs === 'object') h = hdrs;
        else if (typeof reason === 'object') h = reason; 
        for (var k in h) {
            if (k.toLowerCase() === 'content-type') {
                contentType = h[k];
                break;
            }
        }
        if (!contentType) contentType = resp.getHeader('content-type');
        if (!contentType) contentType = "application/unknown";
        realWriteHead.call(resp, sc, reason, hdrs);
    };

    resp.write = function (chunk, encoding) {
        if (buf) buf += chunk;
        else buf = chunk;
        enc = encoding;
    };

    resp.send = function(stuff) {
      buf = stuff;
      realSend.call(resp,stuff);
    };

    resp.end = function() {
        if (!contentType) contentType = resp.getHeader('content-type');
        if (contentType && (contentType === "application/javascript" ||
                            contentType.substr(0,4) === 'text'))
        {
            if (buf) {
                if (Buffer.isBuffer(buf)) buf = buf.toString('utf8');
                var l = Buffer.byteLength(buf);
                buf = subHostNames(buf);
                if (l != Buffer.byteLength(buf)) resp.setHeader('Content-Length', Buffer.byteLength(buf));
            }
        }
        if (buf && buf.length) {
          realWrite.call(resp, buf, enc);
        }
        realEnd.call(resp);
    }

    next();
};

function createServer(obj) {
    var app = express.createServer();
    app.use(express.logger());

    // this file is a *test* harness, to make it go, we'll insert a little handler that
    // substitutes output, changing production URLs to developement URLs.
    app.use(substitutionMiddleware);

    // let the specific server interact directly with the express server to register their middleware,
    // routes, etc...
    if (obj.setup) obj.setup(app);

    // now set up the static resource servin'
    var p = obj.path, ps = path.join(p, "static");
    try { if (fs.statSync(ps).isDirectory()) p = ps; } catch(e) { }
    app.use(express.static(p));

    // and listen!
    app.listen(obj.port, PRIMARY_HOST);
    return app;
};

// start up webservers on ephemeral ports for each subdirectory here.
var dirs = [
    // the reference verification server.  A version is hosted at
    // browserid.org and may be used, or the RP may perform their
    // own verification.
    {
        name: "https://browserid.org/verify",
        subPath: "/",
        path: path.join(__dirname, "verifier")
    },
    // An example relying party.
    {
        name: "http://rp.eyedee.me",
        path: path.join(__dirname, "rp")
    },

    // disabled primary for now since it's not in working
    // order.
    /*    // A reference primary identity provider.
    {
        name: "https://eyedee.me",
        path: path.join(__dirname, "primary")
        }, */

    // BrowserID: the secondary + ip + more.
    {
        name: "https://browserid.org",
        path: path.join(__dirname, "browserid")
    }
];

function formatLink(server, extraPath) {
  var addr = server.address();
  var url = 'http://' + addr.address + ':' + addr.port;
  if (extraPath) {
    url += extraPath;
  }
  return url;
}

console.log("Running test servers:");

var port_num=10000;
dirs.forEach(function(dirObj) {
  if (!fs.statSync(dirObj.path).isDirectory()) return;
  // does this server have a js handler for custom request handling?
  var handlerPath = path.join(dirObj.path, "app.js");
  var runJS = {};
  try {
    var runJSExists = false;
    try { runJSExists = fs.statSync(handlerPath).isFile() } catch(e) {};
    if (runJSExists) {
      var runJS = require(handlerPath);
      // set to development mode
      runJS.production = false;
    }
  } catch(e) {
    console.log("Error loading " + handlerPath + ": " + e);
    process.exit(1);
  }

  var so = {
    path: dirObj.path,
    server: undefined,
    port: port_num++,
    name: dirObj.name,
    handler: runJS.handler,
    setup: runJS.setup,
    subPath: dirObj.subPath
  };
  so.server = createServer(so)
  boundServers.push(so);
  console.log("  " + dirObj.name + ": " + formatLink(so.server));
});