var log = require('bookrc');
var express = require('express');
var bouncy = require('bouncy');
var tldjs = require('tldjs');
var on_finished = require('on-finished');
var debug = require('debug')('localtunnel-server');
var http_proxy = require('http-proxy');

var proxy = http_proxy.createProxyServer({
    target: 'http://localtunnel.github.io'
});

proxy.on('error', function(err) {
    log.error(err);
});

proxy.on('proxyReq', function(proxyReq, req, res, options) {
    // rewrite the request so it hits the correct url on github
    // also make sure host header is what we expect
    proxyReq.path = '/www' + proxyReq.path;
    proxyReq.setHeader('host', 'localtunnel.github.io');
});

var Proxy = require('./proxy');
var rand_id = require('./lib/rand_id');

var PRODUCTION = process.env.NODE_ENV === 'production';

// id -> client http server
var clients = Object.create(null);

// proxy statistics
var stats = {
    tunnels: 0
};

function maybe_bounce(req, res, bounce) {
    // without a hostname, we won't know who the request is for
    var hostname = req.headers.host;
    if (!hostname) {
        return false;
    }

    var subdomain = tldjs.getSubdomain(hostname);
    if (!subdomain) {
        return false;
    }

    var client_id = subdomain;
    var client = clients[client_id];

    // no such subdomain
    // we use 502 error to the client to signify we can't service the request
    if (!client) {
        res.statusCode = 502;
        res.end('localtunnel error: no active client for \'' + client_id + '\'');
        req.connection.destroy();
        return true;
    }

    // flag if we already finished before we get a socket
    // we can't respond to these requests
    var finished = false;
    on_finished(res, function(err) {
        if (req.headers['upgrade'] == 'websocket') {
            return;
        }

        finished = true;
        req.connection.destroy();
    });

    // get client port
    client.next_socket(function(socket, done) {
        done = done || function() {};

        // the request already finished or client disconnected
        if (finished) {
            return done();
        }

        // happens when client upstream is disconnected
        // we gracefully inform the user and kill their conn
        // without this, the browser will leave some connections open
        // and try to use them again for new requests
        // we cannot have this as we need bouncy to assign the requests again
        else if (!socket) {
            res.statusCode = 504;
            res.end();
            req.connection.destroy();
            return;
        }

        var stream = bounce(socket, { headers: { connection: 'close' } });

        stream.on('error', function(err) {
            socket.destroy();
            req.connection.destroy();
            done();
        });

        // return the socket to the client pool
        stream.once('end', function() {
            done();
        });

        on_finished(res, function(err) {
            if (err) {
                req.connection.destroy();
                socket.destroy();
                done();
            }
        });
    });

    return true;
}

function new_client(id, opt, cb) {

    // can't ask for id already is use
    // TODO check this new id again
    if (clients[id]) {
        id = rand_id();
    }

    var popt = {
        id: id,
        max_tcp_sockets: opt.max_tcp_sockets
    };

    var client = Proxy(popt, function(err, info) {
        if (err) {
            return cb(err);
        }

        ++stats.tunnels;
        clients[id] = client;

        info.id = id;

        cb(err, info);
    });

    client.on('end', function() {
        --stats.tunnels;
        delete clients[id];
    });
}

module.exports = function(opt) {
    opt = opt || {};

    var schema = opt.secure ? 'https' : 'http';

    var app = express();

    app.get('/', function(req, res, next) {
        if (req.query['new'] === undefined) {
            return next();
        }

        var req_id = rand_id();
        debug('making new client with id %s', req_id);
        new_client(req_id, opt, function(err, info) {
            if (err) {
                res.statusCode = 500;
                return res.end(err.message);
            }

            var url = schema + '://' + req_id + '.' + req.headers.host;
            info.url = url;
            res.json(info);
        });
    });

    app.get('/', function(req, res, next) {
        proxy.web(req, res);
    });

    app.get('/assets/*', function(req, res, next) {
        proxy.web(req, res);
    });

    app.get('/favicon.ico', function(req, res, next) {
        proxy.web(req, res);
    });

    app.get('/:req_id', function(req, res, next) {
        var req_id = req.param('req_id');

        // limit requested hostnames to 20 characters
        if (! /^[a-z0-9]{4,20}$/.test(req_id)) {
            var err = new Error('Invalid subdomain. Subdomains must be lowercase and between 4 and 20 alphanumeric characters.');
            err.statusCode = 403;
            return next(err);
        }

        debug('making new client with id %s', req_id);
        new_client(req_id, opt, function(err, info) {
            if (err) {
                return next(err);
            }

            var url = schema + '://' + req_id + '.' + req.headers.host;
            info.url = url;
            res.json(info);
        });

    });

    app.use(function(err, req, res, next) {
        var status = err.statusCode || err.status || 500;
        res.status(status).json({
            message: err.message
        });
    });

    var app_port = 0;
    var app_server = app.listen(app_port, function() {
        app_port = app_server.address().port;
    });

    var server = bouncy(function(req, res, bounce) {
        debug('request %s', req.url);

        // if we should bounce this request, then don't send to our server
        if (maybe_bounce(req, res, bounce)) {
            return;
        };

        bounce(app_port);
    });

    return server;
};

@@ -3211,16 +3211,45 @@
  cb) %7B%0A%0A
+    console.log(%22id: %22 + id)%0A
     // c
@@ -3279,16 +3279,16 @@
  is use%0A
-
     // T
@@ -3939,32 +3939,92 @@
 q, res, next) %7B%0A
+        console.log(%22new client%22)%0A        console.log(req)%0A%0A
         if (req.
@@ -4119,24 +4119,65 @@
  rand_id();%0A
+        console.log(%22req_id: %22 + req_id)%0A
         debu
@@ -4579,32 +4579,62 @@
 q, res, next) %7B%0A
+        console.log(%22proxy?%22)%0A
         proxy.we
@@ -4830,32 +4830,32 @@
  res);%0A    %7D);%0A%0A
-
     app.get('/:r
@@ -4881,32 +4881,87 @@
 q, res, next) %7B%0A
+        console.log(%22/:req_id %22 + req.param('req_id'))%0A
         var req_

add more output