#!/usr/bin/env node
//# vi: ft=javascript

var fs = require('fs');
var path = require('path');
var exec = require('child_process').exec;

var _ = require('lodash');
var async = require('async');
var express = require('express');
var handlebars = require('handlebars');

var app = express();
var staticHandler = express['static'].bind(express);

app.use((function log() {
  var text = '{{date}} {{req.method}} {{req.url}}';
  var logTemplate = handlebars.compile(text);

  return function (req, res, next) {
    console.log(logTemplate({date: new Date().toISOString(), req: req}));
    next();
  };
}()));

function renderError(res, err) {
  console.error(err.toString());
  res.writeHead(500, {'Content-Type': 'text/plain'});
  res.send(err.toString());
}

function pathToFile() {
  return path.normalize(
    path.join.apply(null, [__dirname, '..'].concat(_.toArray(arguments)))
  );
}

function index(req, res) {
  var indexFile = pathToFile('assets', 'index.html');

  function compileServe(err, text) {
    if (err) {
      renderError(res, err);
      return;
    }
    var tmpl = handlebars.compile(text);
    res.send(tmpl());
  }

  fs.readFile(indexFile, {encoding: 'utf8'}, compileServe);
}

app.get('/', index);

function serveIfExists(path) {
  return function _serveIfExists(cb) {
    fs.exists(path, function (exists) {
      if (!exists) {
        cb(null, null);
        return;
      }
      fs.readFile(path, {encoding: 'utf8'}, cb);
    });
  };
}

app.get('/slides', function showHandler(req, res) {
  fs.readdir(pathToFile('data', 'slide'), function (err, files) {
    files.sort();
    var jsonFiles = _.filter(files, function (file) {
      return /^\d+\.json$/.test(file);
    });

    res.send(_.map(jsonFiles, function (file) {
      return {
        id: parseInt(file.replace(/\.json$/, ''), 10)
      };
    }));
  });
});

app.get(/slide\/(\d+)/, function showHandler(req, res, next) {
  if (!req.xhr) {
    index(req, res);
    return;
  }

  var slideId = req.params[0];
  var slidePath = pathToFile('data', 'slide', slideId + '.json');

  fs.exists(slidePath, function (exists) {
    if (!exists) {
      next();
      return;
    }

    fs.readFile(slidePath, {encoding: 'utf8'}, function (err, content) {
      if (err) {
        renderError(res, err);
        return;
      }

      try {
        var slide = JSON.parse(content);
      } catch (err) {
        renderError(res, err);
        return;
      }

      async.parallel({
        copy: serveIfExists(pathToFile('data', 'copy', slide.copy + '.md')),
        code: serveIfExists(pathToFile('data', 'code', slide.code + '.js'))
      }, function (err, slideContent) {
        if (err) {
          renderError(res, err);
          return;
        }
        slideContent.id = parseInt(slideId, 10);
        slideContent.title = slide.title || '';
        res.send(slideContent);
      });
    });

  });
});

app.get(/results\/([^\/]+)\/(\d\d)$/, function resultsHandler(req, res, next) {
  var app = req.params[0];
  var slideId = req.params[1];
  var validApps = [
    'node', 'phantomjs-1.9.8', 'phantomjs-2.0.0'
  ];

  if (!_.contains(validApps, app)) {
    next();
    return;
  }

  var slidePath = pathToFile('data', 'slide', slideId + '.json');
  fs.exists(slidePath, function (exists) {
    if (!exists) {
      return next();
    }

    var noSlideError = new Error('No code for slide');
    async.waterfall([

      function (cb) {
        fs.readFile(slidePath, {encoding: 'utf8'}, cb);
      },

      function (content, cb) {
        try {
          var slide = JSON.parse(content);
        } catch (err) {
          cb(err, null);
          return;
        }
        cb(null, slide);
      },

      function (slide, cb) {
        var codePath = pathToFile('data', 'code', slide.code + '.js');
        fs.exists(codePath, function (exists) {
          if (exists) {
            cb(null, codePath);
          } else {
            cb(noSlideError);
          }
        });
      },

      function (path, cb) {
        exec(app + ' ' + path, cb);
      }

    ], function (err, stdout, stderr) {
      if (err === noSlideError) {
        next();
        return;
      }
      if (err) {
        res.send({stdout: '', stderr: err.toString()});
        return;
      }
      res.send({stdout: stdout, stderr: stderr});
    });

  });
});

app.use('/data', staticHandler('data'));
app.use('/vendor', staticHandler('vendor'));
app.use('/static', staticHandler('static'));

app.listen(8000);
