module.exports = function(io, authentication) {

  var pty = require('pty.js');
  var proc = require('child_process');
  var docker = require(__dirname+'/../docker')();

  var module={};
  var idtoname = {};
  
  function broadcast(signal, data) {
    console.log("<= signal: "+signal);
    io.sockets.emit(signal, data);
  }
  
  function exec(cmd, callback) {
    if (cmd.length>40) {
      console.log("== "+cmd.slice(0, 30+cmd.slice(30).indexOf(' '))+" ...");
    } else {
      console.log("== "+cmd);
    }
    proc.exec(cmd, {maxBuffer: 10*1024*1024}, callback);
  }

  function fail(txt, data) {
    console.log("** "+txt, data);
  }

  var oldcontainer = null;
  function containerinspect(error, stdout, stderr) {
    if (error || stderr)
      return fail("inspect docker containers failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    idtoname = {};
    JSON.parse(stdout).forEach(function(n) {
      if (n.State.Running) idtoname[n.Id] = n.Name.replace(/^\//, '');
    });
    if (oldcontainer && oldcontainer==stdout) return; // do not resend same containers
    oldcontainer = stdout;
    broadcast("containers", stdout);
  }
  
  function containerlist(error, stdout, stderr) {
    if (error || stderr)
      return fail("list docker containers failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    exec("docker inspect "+stdout.trim().replace(/\n/g, " "), containerinspect);
  }

  function updatecontainers(error, stdout, stderr) {
    if (error || stderr)
      return fail("update docker container failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    exec("docker ps -aq --no-trunc ", containerlist);
  }

  var oldimage = null;
  function imageinspect(error, stdout, stderr) {
    if (error || stderr)
      return fail("inspect docker images failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    if (oldimage && oldimage==stdout) return; // do not resend same images
    oldimage = stdout;
    broadcast("images", stdout);
  }
  
  function imagelist(error, stdout, stderr) {
    if (error || stderr)
      return fail("list docker images failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    exec("docker inspect "+stdout.trim().replace(/\n/g, " "),
         imageinspect);
  }

  function updateimages(error, stdout, stderr) {
    if (error || stderr)
      return fail("update docker images failed", {
        error: error, stderr: stderr, stdout: stdout
      });
    exec("docker images -q --no-trunc", imagelist);
  }

  function connection(socket, userdata) {
    
    console.log("=> new connection from "+userdata.username);

    function emit(signal, data, info) {
      if (typeof data == 'string' && !data.match("\n")) {
        console.log("<- signal: "+signal+"("+data+")");
      } else {
        console.log("<- signal: "+signal);
      }
      if (info) console.log(info);
      socket.emit(signal, data);
    }

    function fail(txt, data) {
      console.log("** "+txt, data);
      emit("fail", txt);
    }

    function modify(cmd, name) {
      if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i))
        return this.fail("illegal instance name");
      exec("docker "+cmd+" "+name, updatecontainers);
    }

    function createContainer(cmds) {
      if (cmds.length>0)
        exec(cmds.shift(), function(error, stdout, stderr) {
          if (error || stderr)
            return this.fail("create container failed", {
              error: error, stderr: stderr, stdout: stdout
            });
          createContainer(cmds);
        })
      else
        updatecontainers();
    }

    function containers() {
      console.log("-> containers");
      if (oldcontainer) emit("containers", oldcontainer);
      else updatecontainers();
    }
    
    function images() {
      console.log("-> images");
      if (oldimage) emit("images", oldimage);
      else updateimages();
    }
    
    function start(name) {
      console.log("-> start("+name+")");
      modify("start", name);
    }

    function stop(name) {
      console.log("-> stop("+name+")");
      modify("stop", name);
    }

    function pause(name) {
      console.log("-> pause("+name+")");
      modify("pause", name);
    }

    function unpause(name) {
      console.log("-> unpause("+name+")");
      modify("unpause", name);
    }

    function remove(name) {
      console.log("-> remove("+name+")");
      modify("rm", name);
    }

    function create(data) {
      console.log("-> create");
      var d = new docker.Docker();
      var dc = new d.Containers();
      createContainer(dc.creation(data));
    }

    function logs(name) {
      console.log("-> logs("+name+")");
      var l = proc.spawn("docker", ["logs", "-f", name])
                  .on('close', function(code) {
                    emit('logs', {name: name, type: 'done'});
                  });
      l.stdout.on('data', function(data) {
        emit('logs', {name: name, type: 'stdout', text: data.toString()});
      });
      l.stderr.on('data', function(data) {
        emit('logs', {name: name, type: 'stderr', text: data.toString()});
      });
    }

    var bash_connections = {};

    function new_bash(name) {
      if (!name.match(/^[a-z0-9][-_:.+a-z0-9]*$/i))
        return this.fail("illegal instance name");
      if (bash_connections[name]) return;
      bash_connections[name] =
      pty.spawn("docker", ["exec", "-it", name, "bash", "-i"]);
      bash_connections[name].stdout.on('data', function(data) {
        emit('bash-data', {name: name, type: 'stdout', text: data.toString()});
      });
    }

    function bash_start(name) {
      console.log("-> bash-start("+name+")");
      new_bash(name);
    }
    
    function bash_input(data) {
      console.log("-> bash-input("+data.name+", "+data.text+")");
      new_bash(data.name);
      bash_connections[data.name].stdin.resume();
      bash_connections[data.name].stdin.write(data.text);
    }

    function bash_end(name, text) {
      console.log("-> bash-end("+name+")");
      if (!bash_connections[name]) return;
      bash_connections[name].stdin.close();
      delete bash_connections[name]; bash_connections[name] = null;
    }

    socket
      .on("containers", containers)
      .on("images", images)
      .on("start", start)
      .on("stop", stop)
      .on("pause", pause)
      .on("unpause", unpause)
      .on("remove", remove)
      .on("create", create)
      .on('logs', logs)
      .on('bash-start', bash_start)
      .on('bash-input', bash_input)
      .on('bash-end', bash_end);

  }
  
  function stats() {
    var res = {};
    var fs = require('fs');
    var async = require('async');
    var base = "/sys/fs/cgroup/";
    var dataPoints = {
      "cpuacct": [
        "usage"
      ],
      "memory": [
        "usage_in_bytes",
        "limit_in_bytes",
        "max_usage_in_bytes"
      ]
    };
    async.forEachOf(idtoname, function(name, id, cb1) {
      res[name] = {};
      async.forEachOf(dataPoints, function(val1, category, cb2) {
        res[name][category] = {};
        async.each(val1, function(element, cb3) {
          var file = base + category + '/docker/' + id + '/' + category + '.' + element;
          fs.readFile(file, 'utf8', function(err, data) {
            res[name][category][element] = {
              date: (new Date()).getTime(),
              data: {}
            };
            if (err) {cb3(); console.log(err); return}
            data.trim().split('\n').forEach(function(v, i) {
              var vals = v.split(' ');
              switch (vals.length) {
                case 1:
                  res[name][category][element].data = parseInt(vals[0]);
                  break;
                case 2:
                  res[name][category][element].data[vals[0]] = parseInt(vals[1]);
                  break;
              }
            });
            cb3();
          });
        }, function(err) {
          cb2();
        });
      }, function(err) {
        cb1();
      });
    }, function(err) {
      if (err) return;
      broadcast("stats", res);
    });
  }
  
  // Handle Connection
  require('socketio-auth')(io, {
    authenticate: function (socket, data, callback) {
      console.log("=> authenticate: ", data.username);
      //get credentials sent by the client
      var username = data.username;
      var password = data.password;
      authentication(data.username, data.password,
                     function() {console.log("####LOGIN-SUCESS####");callback(null, true)},
                     function() {console.log("####LOGIN-FAIL####");callback(new Error("wrong credentials"))});
    },
    postAuthenticate: connection,
    timeout: "none"
  });

  // Regular Update of Stats
  setInterval(function() {
    stats();
  }, 1000);
  
  // Regular Update of Images and Containers
  setInterval(function() {
    updateimages();
    updatecontainers();
  }, 10000);
  
  return module;
}
