/*! @file

   @id $Id$

   This is the main application as it is fully run in the user's browser.

 */
//       1         2         3         4         5         6         7         8
// 45678901234567890123456789012345678901234567890123456789012345678901234567890

var socket = null;
var docker = null;

function htmlenc(html) {
  return $('<div/>').text(html).html();
}

function htmldec(data) {
  return $('<div/>').html(data).text();
}

/// Show error messsage
/** Fades in an error message and logs to console.
   @param data (optional) The error can be a string or any structure.
   Strings are shown to the user, structures are logged only.
   @param stay (optional) If not given as @c true, reloads page after 5s. */
function error(data) {
  $("#status").fadeOut("slow", function() {
    $("#status").addClass("error")
    $("#status").removeClass("notice")
    $("#status").removeClass("success")
    if (data) {
      if (typeof data == 'string') {
        $("#status").html(data);
        console.log("error: "+data);
      } else {
        $("#status").html('unknown error: '+JSON.stringify(data));
        console.log("error: ", data);
        console.log((new Error('stacktrace')));
      }
    } else {
      $("#status").html('error');
      console.log("error");
    }
    $("#status").fadeIn("slow");
  });
}

/// Show notice messsage
/** Fades in an notice message and logs to console.
   @param text (optional) The data is a string. */
function notice(text) {
  $("#status").fadeOut("slow", function() {
    $("#status").addClass("notice")
    $("#status").removeClass("error")
    $("#status").removeClass("success")
    if (text) {
      $("#status").html(text);
      console.log("notice: "+text);
    } else {
      $("#status").html('');
      console.log("notice");
    }
    $("#status").fadeIn("slow");
  });
}

/// Show notice messsage
/** Fades in an success message and logs to console.
   @param text (optional) The data is a string. */
function success(text) {
  $("#status").fadeOut("slow", function() {
    $("#status").addClass("success")
    $("#status").removeClass("error")
    $("#status").removeClass("notice")
    if (text) {
      $("#status").html(text);
      console.log("success: "+text);
    } else {
      $("#status").html('');
      console.log("success");
    }
    $("#status").fadeIn("slow");
  });
}

/// Show status message in the main screen area
/** @param text Text is a message or some complex HTML from the server.
   @param msg The success message text */
function status(text, msg) {
  $("#main").hide();
  $("#main").html(text);
  if (msg) success(msg);
  else setTimeout("$('#status').fadeOut('slow')", 5000);
  zoom(0);
  $("#main").show();
  $("form input:first-child").focus();
}

function emit(signal, data) {
  console.log("<-snd "+signal, data);
  socket.emit(signal, data);
}

function connect() {
  $("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
  console.log("server connect");
  $("#connectionstatus #bad").hide();
  $("#connectionstatus #authentication").show();
  $("#connectionstatus #good").hide();
  success("login to server");
  socket.emit('authentication', {
    username: $("#usernamefield").val(),
    password: $("#passwordfield").val()
  });
}

function authenticated() {
  $("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
  console.log("server authenticated");
  $("#connectionstatus #bad").hide();
  $("#connectionstatus #authentication").hide();
  $("#connectionstatus #good").show();
  success("server connected");
  start();
}

function unauthorized() {
  $("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
  console.log("authentication failed");
  $("#connectionstatus #good").hide();
  $("#connectionstatus #authentication").hide();
  $("#connectionstatus #bad").show();
  error("authentication failed", true);
}

function disconnected() {
  $("#server").html($("#usernamefield").val()+'@'+window.location.hostname)
  console.log("server disconnected");
  $("#connectionstatus #good").hide();
  $("#connectionstatus #authentication").hide();
  $("#connectionstatus #bad").show();
  error("server disconnected", true);
}

/// Toggle Menu Display
function togglemenu() {
  $("#menu").toggle();
}

/// Upload a configuration and send it to server
function upload(evt) {
  if (!window.FileReader)
    return error("your browser does not support file upload", true);
  for (var i=0, f; f=evt.target.files[i]; ++i) {
    var file = f;
    var reader = new FileReader();
    reader.onload = function(evt) {
      if (evt.target.error) return error("error reading file", true);
      if (evt.target.readyState==0) return notice("waiting for data …");
      if (evt.target.readyState==1) return notice("loading data …");
      tobecreated = JSON.parse(evt.target.result);
      showCreate();
    }
    reader.readAsText(file);
  }
}

var tobecreated = {};
function previewCreate() {
  var name = $('#name').val();
  var nodes = Object.create(tobecreated);
  if (name != '') {
    nodes[name] =  {
      status: docker.containers.Status.Preview,
      id: null,
      name: name,
      image: {
        name: $('#image').val(),
        id: null
      },
      ports: $('#createports option').map(function() {return $(this).data();}).get(),
      env: $('#createvars option').map(function() {return $(this).val();}).get(),
      volumes: $('#createvolumes option').map(function() {return $(this).data();}).get(),
      volumesfrom: $('#createvolumefroms option').map(function() {return $(this).val();}).get(),
      links: $('#createlinks option').map(function() {return $(this).data();}).get(),
      entrypoint: $('#createentrypoints option').map(function() {return $(this).val();}).get(),
      cmd: $('#createcommands option').map(function() {return $(this).val();}).get(),
    };
    $('#doappend').unbind().click(function() {
      tobecreated[name] = Object.create(nodes[name]);
      tobecreated[name].status = docker.containers.Status.Prepared;
      $('#createpreview').append('<option>'+name+'</option>');
      $('#create form input[type=text]').val('');
      $('#create form select.collect option').remove();
      previewCreate();
    });
    $('#doremove').unbind().click(function() {
      $(this).siblings('#createpreview').children('option:selected').each(function(a,b,c) {
        delete tobecreated[b.innerHTML];
      }).remove();
      previewCreate();
    });
    $('#dosend').unbind().click(function() {
      if (Object.keys(tobecreated).length>0) {
        emit("docker.container.create", docker.containers.configuration(tobecreated));
        tobecreated = {};
        showImage();
      }
    });
  }
  if ($('#image').val()!='') {
    var img = docker.images.get($('#image').val());
    if (img) {
      $('#portintdata').empty().append(img.ports.map(function(i) {
        var option =  document.createElement('option');
        option.value = i.replace(/\/.*/g, '');
        return option;
      }));
      $('#varnamedata').empty().append(img.env.map(function(i) {
        var option =  document.createElement('option');
        option.value = i.replace(/=.*/g, '');
        return option;
      }));
      $('#varvaluedata').empty().append(img.env.map(function(i) {
        var option =  document.createElement('option');
        option.value = i.replace(/^[^=]*=/g, '');
        return option;
      }));
      $('#volumeextdata').empty();
      $('#volumeintdata').empty();
      for (i in img.volumes) {
        var option =  document.createElement('option');
        option.value = i;
        $('#volumeextdata').append(option);
        $('#volumeintdata').append(option);
      }
      $('#volumesfromdata').empty().append(docker.containers.names(tobecreated).map(function(i) {
        var option =  document.createElement('option');
        option.value = i;
        return option;
      }));
      $('#linkcontainerdata').empty().append(docker.containers.names(tobecreated).map(function(i) {
        var option =  document.createElement('option');
        option.value = i;
        return option;
      }));
    }
  }
  if (name=='' && nodes) for (name in nodes) break;
  if (name && name!='') $('#preview').html(Viz("digraph {\n"+"  rankdir="+rankdir+";\n"
                                              +docker.containers.subgraph(name, nodes)
                                              +"\n}"));
  else $('#preview').html('');
}

function create() {
}

var zoomlevel = 0;
function zoom(incr = 0) {
  zoomlevel = (zoomlevel+incr)%2;
  switch (zoomlevel) {
    case 0: {
      $("#main svg, #preview svg").css("width", "auto");
      $("#main svg, #preview svg").css("height", "auto");
      $("#main svg, #preview svg").css("max-width", "100%");
      $("#main svg, #preview svg").css("max-height", "100%");
    } break;
    case 1: {
      $("#main svg, #preview svg").css("width", "100%");
      $("#main svg, #preview svg").css("height", "auto");
      $("#main svg, #preview svg").css("max-width", "100%");
      $("#main svg, #preview svg").css("max-height", "none");
    } break;
    case 2: {
      $("#main.svg, #preview svg").css("width", "auto");
      $("#main.svg, #preview svg").css("height", "100%");
      $("#main.svg, #preview svg").css("max-width", "none");
      $("#main.svg, #preview svg").css("max-height", "100%");
    } break;
  }
}

function showLogin() {
  $("#close").hide();
  $("#console").hide();
  $("#create").hide();
  $("#imagetools").hide();
  $("#login").show();
  $("#logs").hide();
  $("#main").hide();
}

function showImage() {
  $("#close").hide();
  $("#console").hide();
  $("#create").hide();
  $("#imagetools").show();
  $("#login").hide();
  $("#logs").hide();
  $("#main").show();
}

function showCreate() {
  $("#close").show();
  $("#console").hide();
  $("#create").show();
  $("#imagetools").show();
  $("#login").hide();
  $("#logs").hide();
  $("#main").hide();
  previewCreate();
}

function showConsole() {
  $("#close").show();
  $("#console").show();
  $("#create").hide();
  $("#imagetools").hide();
  $("#login").hide();
  $("#logs").hide();
  $("#main").hide();
  // $("#command").focus();
  // $("#command").val("");
  // if ($("#screen").val()!="") $("#screen").append("\n");
}

function showLogs() {
  $("#close").show();
  $("#console").hide();
  $("#create").hide();
  $("#imagetools").hide();
  $("#login").hide();
  $("#logs").show();
  $("#main").hide();
}

function images(i) {
  console.log("->rcv images");
  docker.images.set(i);
  $('#imagedata').empty().append(docker.images.tags().map(function(i) {
    var option =  document.createElement('option');
    option.value = i;
    return option;
  }));
}

function logs(data) {
  console.log("->rcv logs("+data.name+")");
  if (data.type=='done') {
    $("#logs").append('<span class="'+data.type+'">\nDONE</span>');
  } else {
    $("#logs").append('<span class="'+data.type+'">'+htmlenc(data.text)+'</span>');
  }
}

function strInsert(str, pos, txt) {
  return str.slice(0, pos)+txt+str.slice(pos);
}

function ansifilter(data) {
  console.log("ansifilter");
  var res = data;
  var pos = -1;
  var spans = 0;
  while ((pos=res.indexOf("\x1B[")) >= 0) {
    var end = res.indexOf("m", pos+2);
    if (end>0) {
      var control= res.slice(pos+2, end);
      res = res.slice(0, pos)+res.slice(end+1);
      control.split(';').forEach(function(c) {
        switch (parseInt(c)) {
            // set
          case 1: res = strInsert(res, pos, '<span class="bold">'); ++spans; break;
          case 2: res = strInsert(res, pos, '<span class="dim">'); ++spans; break;
          case 4: res = strInsert(res, pos, '<span class="underlined">'); ++spans; break;
          case 5: res = strInsert(res, pos, '<span class="blink">'); ++spans; break;
          case 7: res = strInsert(res, pos, '<span class="reverse">'); ++spans; break;
          case 8: res = strInsert(res, pos, '<span class="hidden">'); ++spans; break;
            // reset
          case 0: for (;spans;--spans) res = strInsert(res, pos, "</span>"); break;
          case 21: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
          case 22: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
          case 23: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
          case 25: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
          case 27: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
          case 28: if (spans) --spans; res = strInsert(res, pos, "</span>"); break;
            // fg colors
          case 39: res = strInsert(res, pos, '<span class="fgdefault">'); ++spans; break;
          case 30: res = strInsert(res, pos, '<span class="fgblack">'); ++spans; break;
          case 31: res = strInsert(res, pos, '<span class="fgred">'); ++spans; break;
          case 32: res = strInsert(res, pos, '<span class="fggreen">'); ++spans; break;
          case 33: res = strInsert(res, pos, '<span class="fgyellow">'); ++spans; break;
          case 34: res = strInsert(res, pos, '<span class="fgblue">'); ++spans; break;
          case 35: res = strInsert(res, pos, '<span class="fgmagenta">'); ++spans; break;
          case 36: res = strInsert(res, pos, '<span class="fgcyan">'); ++spans; break;
          case 37: res = strInsert(res, pos, '<span class="fglightgrey">'); ++spans; break;
          case 90: res = strInsert(res, pos, '<span class="fgdarkgrey">'); ++spans; break;
          case 91: res = strInsert(res, pos, '<span class="fglightred">'); ++spans; break;
          case 92: res = strInsert(res, pos, '<span class="fglightgreen">'); ++spans; break;
          case 93: res = strInsert(res, pos, '<span class="fglightyellow">'); ++spans; break;
          case 94: res = strInsert(res, pos, '<span class="fglightblue">'); ++spans; break;
          case 95: res = strInsert(res, pos, '<span class="fglightmagenta">'); ++spans; break;
          case 96: res = strInsert(res, pos, '<span class="fglightcyan">'); ++spans; break;
          case 97: res = strInsert(res, pos, '<span class="fgwhite">'); ++spans; break;
            // bg colors
          case 49: res = strInsert(res, pos, '<span class="bgdefault">'); ++spans; break;
          case 40: res = strInsert(res, pos, '<span class="bgblack">'); ++spans; break;
          case 41: res = strInsert(res, pos, '<span class="bgred">'); ++spans; break;
          case 42: res = strInsert(res, pos, '<span class="bggreen">'); ++spans; break;
          case 43: res = strInsert(res, pos, '<span class="bgyellow">'); ++spans; break;
          case 44: res = strInsert(res, pos, '<span class="bgblue">'); ++spans; break;
          case 45: res = strInsert(res, pos, '<span class="bgmagenta">'); ++spans; break;
          case 46: res = strInsert(res, pos, '<span class="bgcyan">'); ++spans; break;
          case 47: res = strInsert(res, pos, '<span class="bglightgrey">'); ++spans; break;
          case 100: res = strInsert(res, pos, '<span class="bgdarkgrey">'); ++spans; break;
          case 101: res = strInsert(res, pos, '<span class="bglightred">'); ++spans; break;
          case 102: res = strInsert(res, pos, '<span class="bglightgreen">'); ++spans; break;
          case 103: res = strInsert(res, pos, '<span class="bglightyellow">'); ++spans; break;
          case 104: res = strInsert(res, pos, '<span class="bglightblue">'); ++spans; break;
          case 105: res = strInsert(res, pos, '<span class="bglightmagenta">'); ++spans; break;
          case 106: res = strInsert(res, pos, '<span class="bglightcyan">'); ++spans; break;
          case 107: res = strInsert(res, pos, '<span class="bgwhite">'); ++spans; break;
            
        }
      });
    } else {
      break;
    }
  }
  for (;spans;--spans) res += "</span>";
  console.log(res);
  return res.replace(/\r\r\n/g, '\n');
}

function ascii(txt) {
  var res = "";
  for (i=0; i<txt.length; ++i) {
    if (res) res += ",";
    res += txt.charCodeAt(i).toString();
  }
  return res;
}

function bash_data(data) {
  console.log("->rcv bash-data("+data.name+")", data);
  if (data.type=='done') {
    $("#screen").append('<span class="'+data.type+'">\nDONE</span>');
  } else {
    var buff = "";
    console.log("ASCII: ", ascii(data.text));
    for (var i=0; i<data.text.length; ++i) {
      switch (data.text.charCodeAt(i)) {
        case 7:
        case 8:
          if (buff.length) $("#screen").append(ansifilter(htmlenc(buff)));
          buff = "";
          $('#screen').html(function (_,txt) {
            return txt.slice(0, -1);
          });
          break;
        default: buff += data.text[i];
      }
    }
    if (buff.length) $("#screen").append(ansifilter(htmlenc(buff)));
  }
  $("#screen").animate({
    scrollTop: $("#screen").prop('scrollHeight')
  }, 500);
  $("#screen").focus();
}

/// Initial Function: Startup
/** To be called after login */
function start() {
  $("#imagetools").hide();
  $("#close").hide();
  $("#popup").hide();
  $("#menu").hide();
  try {
    status("Starting up ...");
    emit("docker.images");
    showImage();
  } catch (m) {
    error(m);
  }
}

function initForms() {
  $('#create form *').change(previewCreate);
  $("#create form fieldset .add").unbind().click(function() {
    $(this).siblings("select.collect")
           .append('<option '+$(this).siblings("input")
                                     .map(function() {
                                       if (this.hasAttribute('data-name')) {
                                         var res = "";
                                         var obj = this;
                                         this.getAttribute('data-name').split(' ').forEach(function(n) {
                                           res += 'data-'+n+'="'+
                                      (obj.type!='checkbox'||obj.checked
                                      ?obj.value:obj.getAttribute('data-false'))
                                             .replace(/&/g, '&amp;')
                                             .replace(/"/g, '&quot;')
                                                 +'"';
                                         })
                                         return res;
                                       } else return '';
                                     }).get().join(' ')+'>'+$(this).siblings("input")
                                     .map(function() {
                                       var val = this.value;
                                       var sep = this.getAttribute('data-separator') || '';
                                       if (this.type=='checkbox' && !this.checked) {
                                         val=''; sep=''
                                       }
                                       return sep+val;
                                     }).get().join('')+'</option>');
    $(this).siblings("input[type=text]").val('');
    previewCreate();
  });    
  $("#create form fieldset .remove").unbind().click(function() {
    $(this).siblings('select.collect').children('option:selected').remove();
    previewCreate();
  });
}

function init() {
  socket = io.connect();
  docker = new Docker(socket, '#main', error);
  $("#server").html($("#usernamefield").value+'@'+window.location.hostname)
  initForms();
  showLogin();
  socket
    .io
    .on("connect", connect)
    .on("reconnect", connect)
    .on("disconnect", disconnected)
    .on("error", disconnected);
  socket
    .on("authenticated", authenticated)
    .on("unauthorized", unauthorized)
    .on("docker.images", images)
    .on("docker.container.logs", logs)
    .on("docker.container.bash.data", bash_data);
}

/// On Load, Call @ref start
/*
   $(window.onbeforeunload = function() {
   return "Are you sure you want to navigate away?";
   });
 */
$(init);
