var _ = require('lodash');
var os = require('os');
var sf = require('sf');
var green = '\u001b[42m \u001b[0m';
var red = '\u001b[41m \u001b[0m';

function createContainer(docker, fqn, options, next) {

    var optsCreate = {
        'name': options.service.name,
        'Image': fqn,
        'Hostname': '',
        'User': '',
        'AttachStdin': false,
        'AttachStdout': false,
        'AttachStderr': false,
        'Tty': false,
        'OpenStdin': false,
        'StdinOnce': false,
        'Env': null,
        'Volumes': null
    };

    if (options.service.docker && options.service.docker.Config) {
        // For example options look in Config in: docker inspect <container_name>
        optsCreate = _.extend(optsCreate, options.service.docker.Config);
    }

    // Process any variables
    processCmdVars(optsCreate, options.name, options.cwd);

    var doCreate = function(err) {
        if (err && err.statusCode !== 404) return next(err);
        docker.createContainer(optsCreate, next);
    };
    var container = docker.getContainer(optsCreate.name);
    if (container) return container.remove(doCreate);
    doCreate();
}

function locateImage(docker, repoTag, callback) {

    docker.listImages(function(err, list) {
        if (err) return callback(err);

        for (var i = 0, len = list.length; i < len; i++) {
            if (list[i].RepoTags.indexOf(repoTag) !== -1) {
                return callback(null, docker.getImage(list[i].Id));
            }
        }

        return callback();
    });
}

function processCmdVars(optsCreate, name, cwd) {

    // Allow simple variable substitution in Cmds
    var processedCommands = [], processedBinds = [],
        data = {
            HOST_IP: getHostIp(),
            PATH: cwd
        };

    if(optsCreate.Cmd) {
        optsCreate.Cmd.forEach(function(cmd) {
            processedCommands.push(sf(cmd, data));
        });
        optsCreate.Cmd = processedCommands;
    }

    if(optsCreate.Binds) {
        optsCreate.Binds.forEach(function(bind) {
            processedBinds.push(sf(bind, data));
        });
        optsCreate.Binds = processedBinds;
    }

}

function startContainer(bosco, docker, fqn, options, container, next) {
    // We need to get the SSH port?
    var optsStart = {
        'NetworkMode': 'bridge',
        'VolumesFrom': null
    };

    if (options.service.docker && options.service.docker.HostConfig) {
        // For example options look in HostConfig in: docker inspect <container_name>
        optsStart = _.extend(optsStart, options.service.docker.HostConfig);
    }

    // Process any variables
    processCmdVars(optsStart, options.name, options.cwd);

    bosco.log('Starting ' + options.name.green + ': ' + fqn.magenta + '...');

    container.start(optsStart, function(err) {
        if (err) {
            bosco.error('Failed to start Docker image: ' + err.message);
            return next(err);
        }
        var checkPort, attempts = 0;
        _.forOwn(options.service.docker.HostConfig.PortBindings, function(value) {
            if(!checkPort && value[0].HostPort) checkPort = value[0].HostPort; // Check first port
        });

        var isRunning = function() {
            attempts++;
            checkRunning(checkPort, function(err, running) {
                if(running) return next();
                if(attempts > 50) {
                    bosco.warn('Could not detect if ' + options.name.green + ' had started on port ' + ('' + checkPort).magenta);
                    return next();
                }
                setTimeout(isRunning, 200);
            });
        }

        isRunning();
    });
}

function pullImage(bosco, docker, repoTag, next) {
    var prettyError;

    function handler() {
        locateImage(docker, repoTag, function(err, image) {
            if (err || prettyError) return next(prettyError || err);
            next(null, image);
        });
    }

    docker.pull(repoTag, function(err, stream) {
        var currentLayer;
        var progress;
        var previousTotal;

        if (err || prettyError) return next(prettyError || err);

        function newBar(id, total) {
            if (bosco.config.get('progress') == 'bar') {
                return new bosco.progress('Downloading ' + id + ' [:bar] :percent :etas', {
                    complete: green,
                    incomplete: red,
                    width: 50,
                    total: total
                });
            } else {
                var logged = false;
                return {
                    tick: function() {
                        if (!logged) {
                            bosco.log('Downloading layer ' + id + '...');
                            logged = true;
                        }
                    }
                }
            }
        }

        stream.on('data', function(data) {
            var json = JSON.parse(data);

            if (json.errorDetail) {
                prettyError = json.error;
            } else if (json.status === 'Downloading') {
                if (json.id !== currentLayer) {
                    progress = newBar(json.id, json.progressDetail.total);
                    currentLayer = json.id;
                    previousTotal = 0;
                }
                progress.tick(json.progressDetail.current - previousTotal);
                previousTotal = json.progressDetail.current;
            }
        })
        stream.once('end', handler);
    });

}

/**
 * Check to see if the process is running by making a connection and
 * seeing if it is immediately closed or stays open long enough for us to close it.
 */
function checkRunning(port, next) {
    var net = require('net');
    var socket = net.createConnection(port);
    var start = new Date();
    socket.on('connect', function() {
        setTimeout(function() { socket.end() }, 100);
    });
    socket.on('close', function() {
        var closed = new Date() - start;
        next(null, closed > 50 ? true : false);
    });
    socket.on('error', function() {
        next(new Error('Failed to connect'), false);
    });
}

function getHostIp() {

    var ip = _.chain(os.networkInterfaces())
              .values()
              .flatten()
              .filter(function(val) {
                return (val.family === 'IPv4' && val.internal === false)
              })
              .pluck('address')
              .first()
              .value();

    return ip;

}

module.exports = {
    locateImage: locateImage,
    pullImage: pullImage,
    startContainer: startContainer,
    createContainer: createContainer
}

@@ -2958,24 +2958,10 @@
 Port
-, attempts = 0
 ;%0A
+
     
@@ -3147,24 +3147,140 @@
        %7D);%0A%0A
+        var checkTimeout = options.service.checkTimeout %7C%7C 10000;%0A        var checkEnd = Date.now() + checkTimeout;%0A
         var 
@@ -3308,32 +3308,8 @@
 ) %7B%0A
-            attempts++;%0A
     
@@ -3383,16 +3383,17 @@
       if
+ 
 (running
@@ -3431,22 +3431,31 @@
   if
-(attempts %3E 50
+ (Date.now() %3E checkEnd
 ) %7B%0A
@@ -3581,16 +3581,50 @@
 .magenta
+ + ' after ' + checkTimeout + 'ms'
 );%0A     

Allow wait-for-start timeout to be specified by services