'use strict';

module.exports = function (opts) {
  var express = require('express');
  //var finalhandler = require('finalhandler');
  var serveStatic = require('serve-static');
  var serveIndex = require('serve-index');
  //var assetServer = serveStatic(opts.assetsPath);
  var path = require('path');
  //var wellKnownServer = serveStatic(path.join(opts.assetsPath, 'well-known'));

  var serveStaticMap = {};
  var serveIndexMap = {};
  var content = opts.content;
  //var server;
  var serveInit;
  var app;

  function _reloadWrite(data, enc, cb) {
    /*jshint validthis: true */
    if (this.headersSent) {
      this.__write(data, enc, cb);
      return;
    }

    if (!/html/i.test(this.getHeader('Content-Type'))) {
      this.__write(data, enc, cb);
      return;
    }

    if (this.getHeader('Content-Length')) {
      this.setHeader('Content-Length', this.getHeader('Content-Length') + this.__my_addLen);
    }

    this.__write(this.__my_livereload);
    this.__write(data, enc, cb);
  }


  function createServeInit() {
    var PromiseA = require('bluebird');
    var stunnel = require('stunnel');
    var OAUTH3 = require('../packages/assets/org.oauth3');
    require('../packages/assets/org.oauth3/oauth3.domains.js');
    require('../packages/assets/org.oauth3/oauth3.dns.js');
    require('../packages/assets/org.oauth3/oauth3.tunnel.js');
    OAUTH3._hooks = require('../packages/assets/org.oauth3/oauth3.node.storage.js');
    var fs = PromiseA.promisifyAll(require('fs'));
    var ownersPath = path.join(__dirname, '..', 'var', 'owners.json');

    var scmp = require('scmp');

    return require('../packages/apis/com.daplie.caddy').create({
      PromiseA: PromiseA
    , OAUTH3: OAUTH3
    , storage: {
        owners: {
          all: function () {
            var owners;
            try {
              owners = require(ownersPath);
            } catch(e) {
              owners = {};
            }

            return PromiseA.resolve(Object.keys(owners).map(function (key) {
              var owner = owners[key];
              owner.id = key;
              return owner;
            }));
          }
        , get: function (id) {
            var me = this;

            return me.all().then(function (owners) {
              return owners.filter(function (owner) {
                return scmp(id, owner.id);
              })[0];
            });
          }
        , exists: function (id) {
            var me = this;

            return me.get(id).then(function (owner) {
              return !!owner;
            });
          }
        , set: function (id, obj) {
            var owners;
            try {
              owners = require(ownersPath);
            } catch(e) {
              owners = {};
            }
            obj.id = id;
            owners[id] = obj;

            return fs.writeFileAsync(ownersPath, JSON.stringify(owners), 'utf8');
          }
        }
      }
    , recase: require('recase').create({})
    , request: PromiseA.promisify(require('request'))
    , options: opts
    , api: {
        tunnel: function (deps, session) {
          var OAUTH3 = deps.OAUTH3;
          var url = require('url');
          var providerUri = session.token.aud;
          var urlObj = url.parse(OAUTH3.url.normalize(session.token.azp));
          var oauth3 = OAUTH3.create(urlObj, {
            providerUri: providerUri
          , session: session
          });
          //var crypto = require('crypto');
          //var id = crypto.createHash('sha256').update(session.token.sub).digest('hex');
          return oauth3.setProvider(providerUri).then(function () {
            return oauth3.api('domains.list').then(function (domains) {
              var domainsMap = {};
              domains.forEach(function (d) {
                if (!d.device) {
                  return;
                }
                if (d.device !== deps.options.device.hostname) {
                  return;
                }
                domainsMap[d.name] = true;
              });

              //console.log('domains matching hostname', Object.keys(domainsMap));
              //console.log('device', deps.options.device);
              return oauth3.api('tunnel.token', {
                data: {
                  // filter to all domains that are on this device
                  domains: Object.keys(domainsMap)
                , device: {
                    hostname: deps.options.device.hostname
                  , id: deps.options.device.uid || deps.options.device.id
                  }
                }
              }).then(function (result) {
                console.log('got a token from the tunnel server?');
                console.log(result);
                if (!result.tunnelUrl) {
                  result.tunnelUrl = ('wss://' + (new Buffer(results.jwt.split('.')[1], 'base64').toString('ascii')).aud + '/');
                }
                var opts = {
                  token: results.jwt
                , stunneld: results.tunnelUrl
                  // we'll provide faux networking and pipe as we please
                , services: { https: { '*': 443 }, http: { '*': 80 }, smtp: { '*': 25}, smtps: { '*': 587 /*also 465/starttls*/ } /*, ssh: { '*': 22 }*/ }
                , net: opts.net
                };
              });
            });
          });
          //, { token: token, refresh: refresh });
        }
      }
    });
  }

  app = express();

  if (!opts.sites) {
    opts.sites = [];
  }
  opts.sites._map = {};
  opts.sites.forEach(function (site) {

    if (!opts.sites._map[site.$id]) {
      opts.sites._map[site.$id] = site;
    }

    if (!site.paths) {
      site.paths = [];
    }
    if (!site.paths._map) {
      site.paths._map = {};
    }
    site.paths.forEach(function (path) {

      site.paths._map[path.$id] = path;

      if (!path.modules) {
        path.modules = [];
      }
      if (!path.modules._map) {
        path.modules._map = {};
      }
      path.modules.forEach(function (module) {

        path.modules._map[module.$id] = module;
      });
    });
  });

  function mapMap(el, i, arr) {
    arr._map[el.$id] = el;
  }
  opts.global.modules._map = {};
  opts.global.modules.forEach(mapMap);
  opts.global.paths._map = {};
  opts.global.paths.forEach(function (path, i, arr) {
    mapMap(path, i, arr);
    //opts.global.paths._map[path.$id] = path;
    path.modules._map = {};
    path.modules.forEach(mapMap);
  });
  opts.sites.forEach(function (site) {
    site.paths._map = {};
    site.paths.forEach(function (path, i, arr) {
      mapMap(path, i, arr);
      //site.paths._map[path.$id] = path;
      path.modules._map = {};
      path.modules.forEach(mapMap);
    });
  });
  opts.defaults.modules._map = {};
  opts.defaults.modules.forEach(mapMap);
  opts.defaults.paths._map = {};
  opts.defaults.paths.forEach(function (path, i, arr) {
    mapMap(path, i, arr);
    //opts.global.paths._map[path.$id] = path;
    path.modules._map = {};
    path.modules.forEach(mapMap);
  });
  return app.use('/', function (req, res, next) {
    if (!req.headers.host) {
      next(new Error('missing HTTP Host header'));
      return;
    }

    if (0 === req.url.indexOf('/api/com.daplie.caddy/')) {
      if (!serveInit) {
        serveInit = createServeInit();
      }
    }
    if ('/api/com.daplie.caddy/init' === req.url) {
      serveInit.init(req, res);
      return;
    }
    if ('/api/com.daplie.caddy/tunnel' === req.url) {
      serveInit.tunnel(req, res);
      return;
    }
    if ('/api/com.daplie.caddy/config' === req.url) {
      serveInit.config(req, res);
      return;
    }
    if ('/api/com.daplie.caddy/request' === req.url) {
      serveInit.request(req, res);
      return;
    }

    if (content && '/' === req.url) {
      // res.setHeader('Content-Type', 'application/octet-stream');
      res.end(content);
      return;
    }

    //var done = finalhandler(req, res);
    var host = req.headers.host;
    var hostname = (host||'').split(':')[0].toLowerCase();

    console.log('opts.global', opts.global);
    var sites = [ opts.global || null, opts.sites._map[hostname] || null, opts.defaults || null ];
    var loadables = {
      serve: function (config, hostname, pathname, req, res, next) {
        var originalUrl = req.url;
        var dirpaths = config.paths.slice(0);

        function nextServe() {
          var dirname = dirpaths.pop();
          if (!dirname) {
            req.url = originalUrl;
            next();
            return;
          }

          console.log('[serve]', req.url, hostname, pathname, dirname);
          dirname = path.resolve(opts.cwd, dirname.replace(/:hostname/, hostname));
          if (!serveStaticMap[dirname]) {
            serveStaticMap[dirname] = serveStatic(dirname);
          }

          serveStaticMap[dirname](req, res, nextServe);
        }

        req.url = req.url.substr(pathname.length - 1);
        nextServe();
      }
    , indexes: function (config, hostname, pathname, req, res, next) {
        var originalUrl = req.url;
        var dirpaths = config.paths.slice(0);

        function nextIndex() {
          var dirname = dirpaths.pop();
          if (!dirname) {
            req.url = originalUrl;
            next();
            return;
          }

          console.log('[indexes]', req.url, hostname, pathname, dirname);
          dirname = path.resolve(opts.cwd, dirname.replace(/:hostname/, hostname));
          if (!serveStaticMap[dirname]) {
            serveIndexMap[dirname] = serveIndex(dirname);
          }
          serveIndexMap[dirname](req, res, nextIndex);
        }

        req.url = req.url.substr(pathname.length - 1);
        nextIndex();
      }
    , app: function (config, hostname, pathname, req, res, next) {
        //var appfile = path.resolve(/*process.cwd(), */config.path.replace(/:hostname/, hostname));
        var appfile = config.path.replace(/:hostname/, hostname);
        var app = require(appfile);
        app(req, res, next);
      }
    };

    function runModule(module, hostname, pathname, modulename, req, res, next) {
      if (!loadables[modulename]) {
        next(new Error("no module '" + modulename + "' found"));
        return;
      }
      loadables[modulename](module, hostname, pathname, req, res, next);
    }

    function iterModules(modules, hostname, pathname, req, res, next) {
      console.log('modules');
      console.log(modules);
      var modulenames = Object.keys(modules._map);

      function nextModule() {
        var modulename = modulenames.pop();
        if (!modulename) {
          next();
          return;
        }

        console.log('modules', modules);
        runModule(modules._map[modulename], hostname, pathname, modulename, req, res, nextModule);
      }

      nextModule();
    }

    function iterPaths(site, hostname, req, res, next) {
      console.log('site', hostname);
      console.log(site);
      var pathnames = Object.keys(site.paths._map);
      console.log('pathnames', pathnames);
      pathnames = pathnames.filter(function (pathname) {
        // TODO ensure that pathname has trailing /
        return (0 === req.url.indexOf(pathname));
        //return req.url.match(pathname);
      });
      pathnames.sort(function (a, b) {
        return b.length - a.length;
      });
      console.log('pathnames', pathnames);

      function nextPath() {
        var pathname = pathnames.shift();
        if (!pathname) {
          next();
          return;
        }

        console.log('iterPaths', hostname, pathname, req.url);
        iterModules(site.paths._map[pathname].modules, hostname, pathname, req, res, nextPath);
      }

      nextPath();
    }

    function nextSite() {
      console.log('hostname', hostname, sites);
      var site;
      if (!sites.length) {
        next(); // 404
        return;
      }
      site = sites.shift();
      if (!site) {
        nextSite();
        return;
      }
      iterPaths(site, hostname, req, res, nextSite);
    }

    nextSite();

    /*
    function serveStaticly(server) {
      function serveTheStatic() {
        server.serve(req, res, function (err) {
          if (err) { return done(err); }
          server.index(req, res, function (err) {
            if (err) { return done(err); }
            req.url = req.url.replace(/\/assets/, '');
            assetServer(req, res, function  () {
              if (err) { return done(err); }
              req.url = req.url.replace(/\/\.well-known/, '');
              wellKnownServer(req, res, done);
            });
          });
        });
      }

      if (server.expressApp) {
        server.expressApp(req, res, serveTheStatic);
        return;
      }

      serveTheStatic();
    }

    if (opts.livereload) {
      res.__my_livereload = '<script src="//'
        + (host || opts.sites[0].name).split(':')[0]
        + ':35729/livereload.js?snipver=1"></script>';
      res.__my_addLen = res.__my_livereload.length;

      // TODO modify prototype instead of each instance?
      res.__write = res.write;
      res.write = _reloadWrite;
    }

    console.log('hostname:', hostname, opts.sites[0].paths);

    addServer(hostname);
    server = hostsMap[hostname] || hostsMap[opts.sites[0].name];
    serveStaticly(server);
    */
  });
};