// requires
var http = require('http');
var path = require('path');
var url = require('url');
var fs = require('fs');
var child_process = require('child_process');

// constants
var PORT = +process.env.PORT || 9292;
var HOST = process.env.HOST || '0.0.0.0';

// main
http.createServer(serveRequest).listen(PORT, HOST);
console.log('listening on ' + HOST + ':' + PORT);
run_make_test();
'src test Makefile package.json'.split(' ').forEach(function (filename) {
  recursivelyWatch(filename, run_make_test);
});

// functions
function serveRequest(req, res) {
  var reqTime = new Date();
  enqueueOrDo(function () {
    var filepath = path.normalize(url.parse(req.url).pathname).slice(1);
    fs.readFile(filepath, function (err, data) {
      if (err) {
        if (err.code === 'ENOENT' || err.code === 'EISDIR') {
          res.statusCode = 404;
          res.end('404 Not Found: /' + filepath + '\n');
        } else {
          console.log(err);
          res.statusCode = 500;
          res.end('500 Internal Server Error: ' + err.code + '\n');
        }
      } else {
        var ext = filepath.match(/\.[^.]+$/);
        if (ext) res.setHeader('Content-Type', 'text/' + ext[0].slice(1));
        res.end(data);
      }

      console.log(
        '[%s] %s %s /%s - %s%sms',
        reqTime.toISOString(),
        res.statusCode,
        req.method,
        filepath,
        data ? (data.length >> 10) + 'kb, ' : '',
        Date.now() - reqTime
      );
    });
  });
}

function recursivelyWatch(watchee, cb) {
  fs.readdir(watchee, function (err, files) {
    if (err) {
      // not a directory, just watch it
      fs.watch(watchee, cb);
    } else {
      // a directory, recurse, also watch for files being added or deleted
      files.forEach(recurse);
      fs.watch(watchee, function () {
        fs.readdir(watchee, function (err, filesNew) {
          if (err) return; // watchee may have been deleted
          // filesNew - files = new files or dirs to watch
          filesNew
            .filter(function (file) {
              return files.indexOf(file) < 0;
            })
            .forEach(recurse);
          files = filesNew;
        });
        cb();
      });
    }
    function recurse(file) {
      recursivelyWatch(path.join(watchee, file), cb);
    }
  });
}

var q;
function enqueueOrDo(cb) {
  q ? q.push(cb) : cb();
}
function run_make_test() {
  if (q) return;
  q = [];
  console.log('[%s]\nmake test', new Date().toISOString());
  var make_test = child_process.exec('make test', { env: process.env });
  make_test.stdout.pipe(process.stdout, { end: false });
  make_test.stderr.pipe(process.stderr, { end: false });
  make_test.on('exit', function (code) {
    if (code) {
      console.error('Exit Code ' + code);
    } else {
      var serverAddress =
        HOST === '0.0.0.0' ? 'localhost:' + PORT : HOST + ':' + PORT;
      console.log('\nMathQuill is now running on ' + serverAddress);
      console.log('Open http://' + serverAddress + '/test/demo.html\n');
    }
    for (var i = 0; i < q.length; i += 1) q[i]();
    q = undefined;
  });
}
