/**
 **      ==============================
 **       O           O      O   OOOO
 **       O           O     O O  O   O
 **       O           O     O O  O   O
 **       OOOO   OOOO O     OOO  OOOO
 **       O   O       O    O   O O   O
 **       O   O       O    O   O O   O
 **       OOOO        OOOO O   O OOOO
 **      ==============================
 **      Dr. Stefan Bosse http://www.bsslab.de
 **
 **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 **                 BY THE AUTHOR(S).
 **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 **
 **    $AUTHORS:     Stefan Bosse
 **    $INITIAL:     (C) 2006-2022 bLAB
 **    $CREATED:     1-1-19 by sbosse.
 **    $VERSION:     1.8.1
 **    $RCS:         $Id:$
 **    $INFO:
 **
 ** Main Application JAM LAB WEBUI
 **
 **    $ENDOFINFO
 */

var version = "1.8.3"
console.log(version)
var UI= $$;
var logtext= '';
var treemap=[];
var sourcetext='';
var printloc=false;

var shell=[];
var shellWin=[];
var infoShellWin=[];
var configShellWin=[];
var shellNum=1;
var monitorWin=[];
var monitorNum=1;
var monitor=[]
var reporterWin=[];
var reporterNum=1;
var editor=[];
var editorWin=[];
var editorNum=1;
var configEditorWin=[];
var netNodes=[]
var state=0;
var cookieConfig = 'jam.webui.config';
var help={md:{}};
var Marked;

if (typeof jamConfig == 'undefined') info ('No custom configuration file config.js loaded.') 
else info ('Custom configuration file config.js loaded.');

/** Main Options **/

// Optional App configuration loaded from file (config.js)
if (typeof jamConfig == 'undefined') jamConfig = {
  name : '?',
  link1:{ip:"ag-0.de",  ipport:10001, proto:'http', secure:'', enable:true, shells:[1]},
  link2:{ip:"localhost",ipport:10001, proto:'http', secure:'', enable:false, shells:[1]},
  link3:{ip:"",ipport:0, proto:'http', secure:'', enable:false, shells:[1]},
  link4:{ip:"",ipport:0, proto:'http', secure:'', enable:false, shells:[1]},
  log:{agent:true,parent:false,time:false,Time:true,class:false},  // Message flags: agent parent time class
  fontsize: 14,
  group:'jamweb',
  user:'user',
  workdir : '/',
  webclipTime:300,
  webclipUrl:'edu-9.de:9176',
}
jamConfig.version=version;

// load config from cookie
function loadConfig() {
  var data = typeof localStorage=='undefined'?Utils.getCookie(cookieConfig):localStorage.getItem(cookieConfig);
  if (!data) data=Utils.getCookie(cookieConfig);
  if (data) {
    console.log('Loaded jamConfig from cookie '+cookieConfig);
    var obj = JSON.parse(data);
    delete obj.version;
    Object.assign(jamConfig,obj);
    /*
    jamConfig.link1=obj.link1;
    jamConfig.link2=obj.link2;
    jamConfig.link3=obj.link3;
    jamConfig.link4=obj.link4;
    jamConfig.log=obj.log;
    jamConfig.version=version;
    */
  }
}
function saveConfig() {
  var obj = {}
  /*
    link1:jamConfig.link1,
    link2:jamConfig.link2,
    link3:jamConfig.link3,
    link4:jamConfig.link4,
    log:jamConfig.log,
  }
  */
  Object.assign(obj,jamConfig);
  console.log('Saving jamConfig in cookie '+cookieConfig);
  var data = JSON.stringify(obj);
  setCookie(cookieConfig,obj,2)
  if (typeof localStorage!='undefined') localStorage.setItem(cookieConfig, data);
}
function updateConfig(shell) {
  shell.config({log:jamConfig.log});
}

// get config updates from URL params
function urlConfig() {
  var params = parseUrl(document.URL);
  console.log(document.URL,params);
  if (params && (params.link||params.link1)) {
      var tokens=(params.link||params.link1).split(':');
      if (tokens.length==2) {
        jamConfig.link1.ip=tokens[0];
        jamConfig.link1.ipport=Number(tokens[1]);
        jamConfig.link1.enable=true;
      }
  } 
  if (params && params.link2) {
      var tokens=params.link2.split(':');
      if (tokens.length==2) {
        jamConfig.link2.ip=tokens[0];
        jamConfig.link2.ipport=Number(tokens[1]);
        jamConfig.link2.enable=true;
      }
  } 
  if (params && (params.secure||params.secure1)) {
    jamConfig.link1.secure=(params.secure||params.secure1);
  }
  if (params && params.secure2) {
    jamConfig.link2.secure=params.secure2;
  }
  if (params.proto) {
    jamConfig.link1.proto=params.proto;
    jamConfig.link2.proto=params.proto;
    jamConfig.link3.proto=params.proto;
    jamConfig.link4.proto=params.proto;
  }
}
loadConfig();
urlConfig();

var classTemplate = "function ac (options) {\n  this.x=null;\n  this.act = {\n    a1: () => {log('Start')},\n    a2: () => {log('End');kill()}  }\n  this.trans = {\n    a1:a2\n  }\n  this.on = {\n    signal : function () {}\n  }\n  this.next = a1\n} "


// Info and error message toasts ...
function info (text) {
  webix.message({text:text,type:'Info'})
}

function error (text) {
  webix.message({text:text,type:'error'})
}

function clear (widget) {
  var view = UI(widget);
  var log = UI(widget+'LogText');
  var scroll = view.getBody();
  logtext = '';
  log.removeView(widget+'LogTextView');
  log.addView({template:'<pre>'+logtext+'</pre>', autoheight:true, borderless:true, id:widget+'LogTextView'});
  scroll.scrollTo(0,1000000)
  view.show();    
}

function deltree() {
  var root,tree = this.UI('navTree');
  if (tree._roots) {
    for (var p in tree._roots) {
      p=tree._roots[p];
      tree.remove(p);
    } 
  } else tree.remove('root');
  root={ id:"root", open:true, value:'Nodes @'+SHELL.Io.Time() }
  tree.add(root);
}

function maketree() {
  var i=0,tree = this.UI('navTree');
  for(var p in netNodes) {
    tree.add({id:p,value:netNodes[p]+' '+p},i,'root');
    i++;
  }
}


// Update node information view in shell window
async function updateInfo(shellcmd, shellnum) {
  var node = await shellcmd.info('node').id,
      stats = await shellcmd.stats('node');
  function makeTree(element,root) {
    var i=0;
    var rows=[];
    if (element == undefined) 
      return { id:root+'-'+i, value:'null' }
    else if (typeof element == 'number' ||
        typeof element == 'string' ||
        typeof element == 'boolean')
      return { id:root+'-'+i, value:String(element) }
    else if (typeof element == 'function')
      return { id:root+'-'+i, value:'function' }   
    else if (typeof element == 'object') {
      for(var p in element) {
        if (p=='self') continue;
        var tree=makeTree(element[p],root+'-'+i);
        if (!tree) continue;
        if (tree && tree.length)
          rows.push({ id:root+'-'+i, open:false, value:p, data : tree});
        else {
          tree.value=p+'='+tree.value;
          rows.push(tree);
        }
        i++;
      }
      return rows
    }
  }
  var rows = [
    { view:'label', label:'<b>Node '+node+'</b>', height:18}
  ]
  var agents = await shellcmd.stats('agent');
  var i = 1, height=50;
  var treeAgents = [];
  function click(o) { console.log(o) }
  for(var p in agents) {
    var info='agent.'+p+' pid='+agents[p].pid+' class='+agents[p].class+' state='+agents[p].state;
    var agent = await shellcmd.info('agent-data',p);
    treeAgents.push({ id:info, open:false, value:p, data : [
       { id:info+'-'+0, value:'class='+agents[p].class },
       { id:info+'-'+1, value:'pid='+agents[p].pid },
       { id:info+'-'+2, value:'gid='+agents[p].gid },
       { id:info+'-'+3, value:'parent='+agents[p].parent },
       { id:info+'-'+4, value:'state='+agents[p].state },
       { id:info+'-'+5, value:'next='+agents[p].next },
       { id:info+'-'+6, value:'time='+agents[p].resources.consumed },
       { id:info+'-'+7, open:false, value:'agent', data : makeTree(agent,info+'-'+7)},
    ]});
    i++;
  }
  height=50+i*25;
  var tree1 = {
    view:"tree",
    id:'treeAgentsNode'+shellnum,
    select:true,
    tooltip:true,
    height:'auto',
    data: [
        {id:"root", value:"Agents", open:true, data:treeAgents}
    ],
    on:{
      // the default click behavior that is true for any datatable cell
      "onItemClick":function(id, e, trg){ 
         if (id.indexOf('agent.')==0) {
          
         }
      }
    }, 
  }
  rows.push(tree1)
  for (var p in stats) rows.push({ view:"text", label:p, readonly:true, type:'text', value:stats[p].toString()})
  return rows;  
}

// Update window pull-down menus
function updateMenu() {
  var i,items=[];
  UI('menuShells').clearAll();
  for(i in shellWin) items.push((i)+' : '+shellWin[i]._options.name);
  UI('menuShells').add({value:"Shells", submenu:items});
  items=[];
  for(i in editorWin) items.push((i)+' : '+editorWin[i]._options.file);
  UI('menuEditors').clearAll();
  UI('menuEditors').add({value:"Editors", submenu:items});  
  items=[];
  for(i in monitorWin) items.push((i)+' : '+monitorWin[i]._options.name);
  UI('menuMonitors').clearAll();
  UI('menuMonitors').add({value:"Monitors", submenu:items});  
}

// Update conenction tree
async function updateConn(cmd,name) {
  var links = await cmd.connected(DIR.IP('%')),
      ips   = await cmd.connected(DIR.IP('*'));
  // netNodes={};
  for(var i in netNodes) {
    if (i.indexOf(name)==0) delete netNodes[i];
  }
  for(var i in links) netNodes[name+':'+links[i]]=ips[i]; 
  deltree();
  maketree();  
}

function removeConn(name) {
  for(var i in netNodes) {
    if (i.indexOf(name)==0) delete netNodes[i];
  }
  deltree();
  maketree();  
}
function autoLayout() {
  var x=0,
      y=50;
  for(i in shellWin) {
    var shell=shellWin[i];
    if (!shell) continue;
    var size=shell.collapse(true);
    x=$('body').width()-size.width;
    shell.window.setPosition(x,y);
    y+=(size.height+1);
  }
  for(i in editorWin) {
    var shell=editorWin[i];
    if (!shell) continue;
    var size=shell.collapse(true);
    x=$('body').width()-size.width;
    shell.window.setPosition(x,y);
    y+=(size.height+1);
  }
  for(i in monitorWin) {
    var shell=monitorWin[i];
    if (!shell) continue;
    var size=shell.collapse(true);
    x=$('body').width()-size.width;
    shell.window.setPosition(x,y);
    y+=(size.height+1);
  }
}

/************ NETWORK ***************/
Network = {
  state:0,
  start:  async function () { 
    if (Network.state<2) {
      var proto=jamConfig.link1.proto||'http';
      for(var i in shell) {
        if (shell[i] && shell[i].options.shell && !shell[i].port) 
          shell[i].port=await shell[i].options.cmd.port(DIR.IP(),{proto:proto,verbose:shell[i].options.log.Network});
      }
      Network.state=1;
    }
    for(var i in shell) {
      if (!shell[i] || !shell[i].options.shell) continue;
      if (jamConfig.link1.enable && jamConfig.link1.ipport) 
        shell[i].options.cmd.connect(DIR.IP(jamConfig.link1.proto+'://'+jamConfig.link1.ip+':'+
                                                  jamConfig.link1.ipport+
                                                  (jamConfig.link1.secure!=''?'?secure='+jamConfig.link1.secure:'')));
      if (jamConfig.link2.enable && jamConfig.link2.ipport) 
        shell[i].options.cmd.connect(DIR.IP(jamConfig.link2.proto+'://'+jamConfig.link2.ip+':'+
                                                  jamConfig.link2.ipport+
                                                  (jamConfig.link2.secure!=''?'?secure='+jamConfig.link2.secure:'')));
      if (jamConfig.link3.enable && jamConfig.link3.ipport) 
        shell[i].options.cmd.connect(DIR.IP(jamConfig.link3.proto+'://'+jamConfig.link3.ip+':'+
                                                  jamConfig.link3.ipport+
                                                  (jamConfig.link3.secure!=''?'?secure='+jamConfig.link3.secure:'')));
      if (jamConfig.link4.enable && jamConfig.link4.ipport) 
        shell[i].options.cmd.connect(DIR.IP(jamConfig.link4.proto+'://'+jamConfig.link4.ip+':'+
                                                  jamConfig.link4.ipport+
                                                  (jamConfig.link4.secure!=''?'?secure='+jamConfig.link4.secure:'')));
    }
    Network.state=2; 
  },
  stop:   function () { 
    for(var i in shell) {
      if (!shell[i] || !shell[i].options.shell) continue;
      // disconnect w/o proto!!
      if (jamConfig.link1.enable) 
        shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link1.ip+':'+
                                                     jamConfig.link1.ipport));
      if (jamConfig.link2.enable) 
        shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link2.ip+':'+
                                                     jamConfig.link2.ipport));
      if (jamConfig.link3.enable) 
        shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link3.ip+':'+
                                                     jamConfig.link3.ipport));
      if (jamConfig.link4.enable) 
        shell[i].options.cmd.disconnect(DIR.IP(jamConfig.link4.ip+':'+
                                                     jamConfig.link4.ipport));
    }
    Network.state=1;
  },
  update : function (options,num) {
    if (shell[num].port) shell[num].port.amp.config(options);  
  }
}
jam = {
  state:0,
  start:  function () { 
    for(var i in shell) {
      if (shell[i] && shell[i].options.shell) shell[i].options.shell.env.start();
    }
    jam.state=1; 
  },
  stop:   function () { 
    for(var i in shell) {
      if (shell[i] && shell[i].options.shell) shell[i].options.shell.env.stop();
    }
    jam.state=0;
  }

}





/************ TOP TOOLBAR ********/

toolbar = this.webix.ui({
        view:"toolbar",
        id:"myToolbar",
    	left:0, top:0, width:'100%',
        cols:[
         { view:"button", type:"icon", icon:"play", id:'button.jam.start', tooltip:'Start', width:30, click:function () {
          if (jam.state==0) {
            UI('button.jam.start').disable();
            UI('button.jam.stop').enable();
            jam.start();
          }
         }},
         { view:"button", type:"icon", icon:"stop", id:'button.jam.stop', tooltip:'Stop', width:30, click:function () {
            UI('button.jam.start').enable();
            UI('button.jam.stop').disable();
            jam.stop();
         }},
         { view:"button", type:"icon", icon:"chain", id:'button.net.start', tooltip:'Connect', width:30, click:function () {
          if (Network.state<2) {
            UI('button.net.start').disable();
            UI('button.net.stop').enable();
            Network.start();
          }
         }},
         { view:"button", type:"icon", icon:"chain-broken", id:'button.net.stop', tooltip:'Disconnect', width:30, click:function () {
            UI('button.net.start').enable();
            UI('button.net.stop').disable();
            Network.stop();
         }},
         { view:"button", type:"icon", icon:"navicon", tooltip:'Configuration', width:30, click:function () {
           mainConfigWin.show();
         }},
         { view:"button", type:"icon", icon:"plus", tooltip:'New Shell Worker', width:30, click:function () {
            createShell(shellNum,{worker:true});
            shellNum++;
         }},
         { view:"button", type:"icon", icon:"sort-amount-asc", tooltip:'Bigger Fonts', width:30, click:function () {
           jamConfig.fontsize++;
           changeCSS('.input','font-size',jamConfig.fontsize+'px')
           changeCSS('.normalInput','font-size',jamConfig.fontsize+'px')
           changeCSS('.normalOutput','font-size',jamConfig.fontsize+'px')
           changeCSS('.error','font-size',jamConfig.fontsize+'px')
           changeCSS('.webix_view','font-size',jamConfig.fontsize+'px')
         }},
         { view:"button", type:"icon", icon:"sort-amount-desc", tooltip:'Smaller Fonts', width:30, click:function () {
           jamConfig.fontsize--;
           changeCSS('.input','font-size',jamConfig.fontsize+'px')
           changeCSS('.normalInput','font-size',jamConfig.fontsize+'px')
           changeCSS('.normalOutput','font-size',jamConfig.fontsize+'px')
           changeCSS('.error','font-size',jamConfig.fontsize+'px')
           changeCSS('.webix_view','font-size',jamConfig.fontsize+'px')
         }},
         { view:"button", type:"icon", icon:"user-plus", tooltip:'Chat Dialog', width:30, click:function () {
            if (chatWin.isVisible())
              chatWin.hide();
            else {
              chatWin.show();
              chatInit();
            }
         }},
         { view:"button", type:"icon", icon:"hand-o-right", tooltip:'Visual Pointer', width:30, click:function () {
            var el = document.getElementsByTagName("body")[0];
            self._cursorToggle=!self._cursorToggle;
            if (self._cursorToggle)
             el.style.cursor = "url(redpointer.png), auto";
            else
             el.style.cursor = "auto";
            ['webix_view'].forEach(function (id) {
              var ell = document.getElementsByClassName(id);
              if (!ell) return;
              ell.forEach(function (el) {
                if (self._cursorToggle)
                  el.style.cursor = "url(redpointer.png), auto";
                else
                  el.style.cursor = "auto";
              });
            })
         }},
         { view:"button", type:"icon", icon:"question", tooltip:'Help', width:30, click:function () {
           helpWin.show();
         }},
         { view:"button", type:"icon", icon:"th", tooltip:'Auto Layout', width:30, click:function () {
          autoLayout()
         }},
         { view:"button", type:"icon", icon:"terminal", tooltip:'New Developer Console', width:30, click:function () {
          var shell = Shell.default({
            label:'Developer Console (JS)',
            hide:false,
            editor:true,
          });
          shell.run = function (code) {
            var ___error;
            with ({
              error:shell.env.error,
              keys:Object.keys.bind(Object),
              load:Utils.loadScript,
              print:shell.env.print,
              time:Date.now,
            }) { try { var result = eval(code) } catch (e) { ___error=result=e }};
            if (!code.match(/;[ ]*$/))
              shell.env.print(result);
            else if (___error) shell.env.error(___error);
          }
         }},
         {
             view:"menu",
             id:"menuShells",
             autowidth:true,
             width:100,
             data:[ //menu data
                 { value:"Shells", submenu:["1"] },
             ],
             type:{
                 subsign:true,
             },          
             on:{
                 onMenuItemClick:function(id){
                    var name = this.getMenuItem(id).value.split(':');
                     var win=shellWin[Number(name[0])].window;
                     if (win) win.isVisible()?win.hide():win.show();
                 }
             }
         },
         {
             view:"menu",
             id:"menuEditors",
             autowidth:true,
             width:100,
             data:[ //menu data
                 { value:"Editors", submenu:[] },
             ],
             type:{
                 subsign:true,
             }, 
             on:{
                 onMenuItemClick:function(id){
                    var name = this.getMenuItem(id).value.split(':');
                     var win=editorWin[Number(name[0])].window;
                     if (win) win.isVisible()?win.hide():win.show();
                 }
             }
         },
         {
             view:"menu",
             id:"menuMonitors",
             autowidth:true,
             width:100,
             data:[ //menu data
                 { value:"Monitors", submenu:[] },
             ],
             type:{
                 subsign:true,
             },
             on:{
                 onMenuItemClick:function(id){
                    var name = this.getMenuItem(id).value.split(':');
                     var win=monitorWin[Number(name[0])].window;
                     if (win) win.isVisible()?win.hide():win.show();
                 }
             }
         },
         { view:"label", id:'myTopLabel', label:'JAM Laboratory WEB API (C) Dr. Stefan Bosse Ver. '+jamConfig.version, align:'right'},
         { view:"button", type:"icon", icon:"close", tooltip:'Exit', width:30, click:function () {
         }},
        ]
  });
UI('button.jam.stop').disable();
UI('button.net.stop').disable();
toolbar.show();


/************ MAIN CONFIG ********/
mainConfigWin=webix.ui({
      id:'mainConfigWin',
	  view:"window",
	  width:300,
	  height:450,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      head: {
          view:"toolbar",
          cols:[
           { view:"label", label:"Main Configuration", align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              jamConfig.link1.ip=UI('mainConfigWinLink1IPAddress').getValue();
              jamConfig.link1.ipport=UI('mainConfigWinLink1IPPort').getValue();
              jamConfig.link1.proto=UI('mainConfigWinLink1IPProto').getValue();
              jamConfig.link1.secure=UI('mainConfigWinLink1SecPort').getValue();
              jamConfig.link2.ip=UI('mainConfigWinLink2IPAddress').getValue();
              jamConfig.link2.ipport=UI('mainConfigWinLink2IPPort').getValue();
              jamConfig.link2.proto=UI('mainConfigWinLink2IPProto').getValue();
              jamConfig.link2.secure=UI('mainConfigWinLink2SecPort').getValue();
              jamConfig.link3.ip=UI('mainConfigWinLink3IPAddress').getValue();
              jamConfig.link3.ipport=UI('mainConfigWinLink3IPPort').getValue();
              jamConfig.link3.proto=UI('mainConfigWinLink3IPProto').getValue();
              jamConfig.link3.secure=UI('mainConfigWinLink3SecPort').getValue();
              jamConfig.link4.ip=UI('mainConfigWinLink4IPAddress').getValue();
              jamConfig.link4.ipport=UI('mainConfigWinLink4IPPort').getValue();
              jamConfig.link4.proto=UI('mainConfigWinLink4IPProto').getValue();
              jamConfig.link4.secure=UI('mainConfigWinLink4SecPort').getValue();
              jamConfig.webclipUrl=UI('mainConfigWinClipUrl').getValue();
              jamConfig.user=UI('mainConfigWinUser').getValue();
              jamConfig.workdir=UI('mainConfigWinWorkdir').getValue();
              saveConfig();
              mainConfigWin.hide();
           }}
          ]
      },
	  body: {
        rows:[
          { view:"form", scroll:true, width:300, height:400, elements: [

            { view:"label", label:"<b>JAM Network Link 1</b>", height:'18px', align:'left'},
            { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link1.enable?1:0,
              click: function (o) {jamConfig.link1.enable=Number(self.UI(o).getValue())?true:false}}, 
            { view:"text", id:"mainConfigWinLink1IPAddress", label:"IP Address", value:jamConfig.link1.ip},
            { view:"text", id:"mainConfigWinLink1IPPort", label:"IP Port", value:jamConfig.link1.ipport},
            { view:"text", id:"mainConfigWinLink1IPProto", label:"IP Proto", value:jamConfig.link1.proto},
            { view:"text", id:"mainConfigWinLink1SecPort", label:"Secure Port (Opt.)", value:jamConfig.link1.secure},

            { view:"label", label:"<b>JAM Network Link 2</b>", height:'18px', align:'left'},
            { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link2.enable?1:0,
              click: function (o) {jamConfig.link2.enable=Number(self.UI(o).getValue())?true:false}}, 
            { view:"text", id:"mainConfigWinLink2IPAddress", label:"IP Address", value:jamConfig.link2.ip},
            { view:"text", id:"mainConfigWinLink2IPPort", label:"IP Port", value:jamConfig.link2.ipport},
            { view:"text", id:"mainConfigWinLink2IPProto", label:"IP Proto", value:jamConfig.link2.proto},
            { view:"text", id:"mainConfigWinLink2SecPort", label:"Secure Port (Opt.)", value:jamConfig.link2.secure},

            { view:"label", label:"<b>JAM Network Link 3</b>", height:'18px', align:'left'},
            { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link3.enable?1:0,
              click: function (o) {jamConfig.link3.enable=Number(self.UI(o).getValue())?true:false}}, 
            { view:"text", id:"mainConfigWinLink3IPAddress", label:"IP Address", value:jamConfig.link3.ip},
            { view:"text", id:"mainConfigWinLink3IPPort", label:"IP Port", value:jamConfig.link3.ipport},
            { view:"text", id:"mainConfigWinLink3IPProto", label:"IP Proto", value:jamConfig.link3.proto},
            { view:"text", id:"mainConfigWinLink3SecPort", label:"Secure Port (Opt.)", value:jamConfig.link3.secure},

            { view:"label", label:"<b>JAM Network Link 4</b>", height:'18px', align:'left'},
            { view:"checkbox", customCheckbox:false, labelRight:"Enable", height:'18px', value:jamConfig.link4.enable?1:0,
              click: function (o) {jamConfig.link4.enable=Number(self.UI(o).getValue())?true:false}}, 
            { view:"text", id:"mainConfigWinLink4IPAddress", label:"IP Address", value:jamConfig.link4.ip},
            { view:"text", id:"mainConfigWinLink4IPPort", label:"IP Port", value:jamConfig.link4.ipport},
            { view:"text", id:"mainConfigWinLink4IPProto", label:"IP Proto", value:jamConfig.link4.proto},
            { view:"text", id:"mainConfigWinLink4SecPort", label:"Secure Port (Opt.)", value:jamConfig.link4.secure},

            { view:"label", label:"<b>WebClipboard</b>", height:'18px', align:'left'},
            { view:"text", id:"mainConfigWinClipUrl", label:"URL", value:jamConfig.webclipUrl},
            { view:"text", id:"mainConfigWinUser", label:"User", value:jamConfig.user},
            { view:"text", id:"mainConfigWinWorkdir", label:"WorkDir", value:jamConfig.workdir},
          ]}
        ]
      }
    });


helpWin=webix.ui({
    id:'helpWin',
	  view:"window",
	  width:500,
	  height:450,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      css:'gray_toolbar',
      head: {
          view:"toolbar",
          cols:[
           { view:"button", value:'JAMSH',  align:'center', click: function () {
            if (helpWin._view) UI('helpWinScrollView').removeView(helpWin._view);
            var helpRendered=help.md.jamsh?help.md.jamsh:Marked(help.jamsh);
            help.md.jamsh=helpRendered;
            helpWin._view=UI('helpWinScrollView').addView(
                {
                  rows : [
                    {template:'<div class="marked">'+helpRendered+'</div>', autoheight:true}
                  ]
                }
             );
            }  
           },
           { view:"button", value:'AIOS',  align:'center', click: function () {
            if (helpWin._view) UI('helpWinScrollView').removeView(helpWin._view);
            var helpRendered=help.md.aios?help.md.aios:Marked(help.aios);
            help.md.aios=helpRendered;
            helpWin._view=UI('helpWinScrollView').addView(
                {
                  rows : [
                    {template:'<div class="marked">'+helpRendered+'</div>', autoheight:true}
                  ]
                }
             );
            }  
           },
           { view:"button", value:'JS',  align:'center', click: function () {
            if (helpWin._view) UI('helpWinScrollView').removeView(helpWin._view);
            var helpRendered=help.md.js?help.md.js:Marked(help.js);
            help.md.js=helpRendered;
            helpWin._view=UI('helpWinScrollView').addView(
                {
                  rows : [
                    {template:'<div class="marked">'+helpRendered+'</div>', autoheight:true}
                  ]
                }
             );
            }  
           },
           { view:"label", label:"Help", align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              helpWin.hide();
           }}
          ]
      },
	  body:{
        view : 'scrollview',
        scroll : 'y',
        body : {
          id:'helpWinScrollView',
          rows : [
          ]
        }
     }    
    });

/*********** Network Tree **********/
navTree=webix.ui({
    id:'navTree',
	left:50, top:50,
    view:'tree',
    type:'lineTree',
    select:true,
    data: [
        { id:"root", open:true, value:"Not connected!"}
    ]
})
navTree.show();        
navTree.attachEvent("onAfterSelect", function(id){
  webix.message({text:id,type:'Info'});
  if (id!='root') for (var i in configShellWin) {
    if (configShellWin[i] && configShellWin[i].isVisible()) {
      UI('configRemoteHostIp'+i).setValue(id);
    }
  }
});



 
/************ SHELL ********/

// Only one shell (num=1) supported!

function _createShell(num,params) {
  shell[num] = winShell(jamConfig);    // shell[num].options.shell contains the jamsh object
  var options = shell[num].options;
  var cmd;
  
  function Label () {
    return options.name+" / Shell "+num
  }
  
  shellWin[num]=webix.ui({
      id:'ShellWin'+num,
	  view:"window",
	  height:350,
	  width:600,
	  left:250, top:250,
	  move:true,
	  resize: true,
      toFront:true,
      css:'red_toolbar',
      head: {
          view:"toolbar",
          cols:[
           { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () {
             shell[num].commands.clear()
           }},
           { view:"button", type:"icon", icon:"navicon", tooltip:'Config', width:30, click:function () {
             configShellWin[num].show();
           }},
           { view:"button", type:"icon", icon:"edit", tooltip:'New Agent Editor', width:30, click:function () {
              createEditor(editorNum,{shell:num,name:options.name});
              editorNum++;
           }},
           { view:"button", type:"icon", icon:"envelope", tooltip:'Agent Monitor', width:30, click:function () {
            if (!monitor[num]) {
              createMonitor(num,{shell:shell[num].options.shell})
            } else monitorWin[num].show();
           }},
           { view:"button", type:"icon", icon:"info", tooltip:'Node Information', width:30, click:function () {
              UI('infoShellWinLabel'+num).setValue("Info / "+Label());
              if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
              options.infoView=UI('infoShellWinScrollView'+num).addView(
                {
                  rows : updateInfo(cmd,num)
                }
              );
              infoShellWin[num].show();
           }},
           { view:"button", type:"icon", icon:"dot-circle-o", tooltip:'Scroll Auto', id:'ShellWinLogFilmBut'+num, width:30, click:function () {
             UI('ShellWinLog'+num).scrollAuto=!UI('ShellWinLog'+num).scrollAuto;
             if (UI('ShellWinLog'+num).scrollAuto) UI('ShellWinLogFilmBut'+num).define('icon','arrow-circle-down')
             else UI('ShellWinLogFilmBut'+num).define('icon','dot-circle-o');
             UI('ShellWinLogFilmBut'+num).refresh();
           }},
           { view:"label", label:"Shell "+num, id:'ShellWinLabel'+num, align:'right'},
           { view:"button", type:"icon", icon:"gear", tooltip:'New Script Editor', width:30, click:function () {
              createEditor(editorNum,{shell:num,name:options.name,script:true});
              editorNum++;
           }},
           { view:"button", type:"icon", icon:"close", tooltip:'Close Shell', width:30, click:function () {
            var self=shellWin[num];
            self.hide();
           }},
          ]
      },
	  body:{
          id : 'ShellWinLog'+num,
          view : 'scrollview',
          scroll : 'y',
          body : {
             id:'ShellWinLogText'+num,
             rows : [
                {template:('<div id="ShellWinOutput'+num+'" '+
                           'spellcheck="false" style="">'), height:"auto", borderless:true},
                {template:('<div><textarea id="ShellWinInput'+num+'" '+
                           'class="input" spellcheck="false" wrap="on" onkeydown="shell['+num+
                           '].inputKeydown(event)" rows="1" style="width:98%"></textarea></div>'), height:"auto", borderless:true},
              ]
          }
	  }
  });
  shellWin[num].show();
  
  /************** SHELL CONFIG ****************/
  configShellWin[num]=webix.ui({
      id:'configShellWin'+num,
	  view:"window",
	  width:300,
	  height:450,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      css:'gray_toolbar',
      head: {
          view:"toolbar",
          id:"configShellWinToolbarWin"+num,
          cols:[
           { view:"label", label:"Configuration", align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              configShellWin[num].hide();
              updateConfig(cmd);
              saveConfig();
           }}
          ]
      },
	  body: {
        rows:[
          { view:"form", scroll:true, width:300, height:400, elements: [
            { view:'label', label:'<b>Agent Logging</b>', height:0},
            { view:"checkbox", customCheckbox:false, labelRight:"agent", height:'18px', value:jamConfig.log.agent?1:0,
              click: function (o) {jamConfig.log.agent=Number(self.UI(o).getValue())?true:false;}}, 
            { view:"checkbox", customCheckbox:false, labelRight:"class", height:'18px', value:jamConfig.log.class?1:0,
              click: function (o) {jamConfig.log.class=Number(self.UI(o).getValue())?true:false;}}, 
            { view:"checkbox", customCheckbox:false, labelRight:"parent", height:'18px', value:jamConfig.log.parent?1:0,
              click: function (o) {jamConfig.log.parent=Number(self.UI(o).getValue())?true:false;}}, 
            { view:"checkbox", customCheckbox:false, labelRight:"time", height:'18px', value:jamConfig.log.time?1:0,
              click: function (o) {jamConfig.log.time=Number(self.UI(o).getValue())?true:false; }}, 
            { view:"checkbox", customCheckbox:false, labelRight:"Time", height:'18px', value:jamConfig.log.Time?1:0,
              click: function (o) {jamConfig.log.Time=Number(self.UI(o).getValue())?true:false;}}, 

            { view:'label', label:'<b>Shell Logging</b>', height:0},
            { view:"checkbox", customCheckbox:false, labelRight:"Time", height:'18px', value:options.log.Time?1:0,
              click: function (o) {options.log.Time=Number(self.UI(o).getValue())?true:false;}}, 
            { view:"checkbox", customCheckbox:false, labelRight:"Network", height:'18px', value:options.log.Network?1:0,
              click: function (o) {options.log.Network=Number(self.UI(o).getValue())?true:false;
                                   Network.update({verbose:options.log.Network?1:0},num)}}, 
          ]}
        ]
      }
    });

  /************** SHELL INFO ****************/
  infoShellWin[num]=webix.ui({
      id:'infoShellWin'+num,
	  view:"window",
	  height:350,
	  width:500,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      css:'gray_toolbar',
      head: {
          view:"toolbar",
          id:"infoShellWinToolbarWin"+num,
          cols:[
           { view:"button", type:"icon", icon:"repeat", tooltip:'Refresh', width:30, click:function () {
              if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
              options.infoView=UI('infoShellWinScrollView'+num).addView(
                {
                  rows : updateInfo(cmd,num)
                }
              );
           }},
           { view:"button", type:"icon", icon:"times-circle", tooltip:'Kill Agent', width:30, click:function () {
              var tree = UI('treeAgentsNode'+options.name);
              var selectedId = tree.getSelectedId();
              if (selectedId.indexOf('agent.')==0) {
                var agentid = selectedId.substring(selectedId.indexOf('.')+1,selectedId.indexOf(' '));
                cmd.kill(agentid);
              }
              if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
              options.infoView=UI('infoShellWinScrollView'+num).addView(
                {
                  rows : updateInfo(cmd,num)
                }
              );
           }},
           { view:"label", label:"Info / "+Label(), id:'infoShellWinLabel'+num, align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              infoShellWin[num].hide();
           }}
          ]
      },
	  body:{
        view : 'form',
        scroll : 'y',
        height: 300,
        id:'infoShellWinScrollView'+num,
        rows : [
          ]
     }    
    });
  shell[num].init("ShellWinInput"+num,"ShellWinOutput"+num,UI('ShellWinLog'+num));
  UI('ShellWinLog'+num).scrollAuto=false;
  jamConfig.name = options.name;
  cmd = options.cmd;
  shell[num].run=cmd;
  Marked = options.cmd.marked;
  cmd.on('link+',function (arg){
    updateConn(cmd,options.name);
  });
  cmd.on('link-',function (arg){
    updateConn(cmd,options.name);
  });
  
  shellWin[num].attachEvent("onViewMove", function(pos, e){
  });
  shellWin[num].attachEvent("onViewMoveEnd", function(pos, e){
  });
  UI('ShellWinLabel'+num).setValue(Label());
  updateMenu();
  var versions = cmd.versions();
  shell[num].print('JAMlib Version '+versions.lib+' JAMShell Version '+versions.shell+' JAMAios Version '+versions.aios);
}




// New shell widget (includes monitor, config, info, and editor widgets, too)
function createShell(num,params) {
  var shellControl;
  var config = {
    log : Object.assign({},jamConfig.log),
    verbose : jamConfig.verbose||0,
  };
  Object.assign(config,params); 
  shellControl = shell[num] = Node(config);    // shell[num].options.shell contains the jamsh object
  var options = shell[num].options;
  var cmd;
  function Label () {
    return options.name+' / Shell '+num
  }
  var shellWidget = UI.Shell.default({
    buttons:{
      'config:bars:Configuration' : function () {
        UI.Form.create({
          'Shell Logging':options.log,
          'JAM Logging':config.log,
        },{}, function (_config) {
          Object.assign(config.log,_config['JAM Logging']);
          Object.assign(options.log,_config['Shell Logging']);
          shellWidget.setLabel(Label())
          shellControl.setup(config);
          cmd.config({log:config.log});
        })
      },
      'edit:edit:New Agent Editor' : function () {
        var optionsE = {
          shell:num,
          level:1,
          arguments:'{}',
          file:'agent.js',
        }
        var numed = editorNum++;
        function Label() {
          return optionsE.file+(optionsE.acname? ' ('+optionsE.acname+')':'')+' / '+options.name+' AgE'+numed+':S'+optionsE.shell
        }
        var editorWidget = UI.Editor.default({
          label:Label(),
          mode:'js',
          //x:options.x,
          //y:options.y,
          // hide:false,
          buttons : {
            'config:bars:Configure' : function (label,ev) {
                /********** EDITOR CONFIG ****************/
                if (!configEditorWin[numed]) {
                  configEditorWin[numed]=webix.ui({
                    id:'configEditorWin'+numed,
	                  view:"window",
	                  width:300,
	                  height:450,
	                  left:ev.clientX-50, top:ev.clientY,
	                  move:true,
	                  resize: true,
                      toFront:true,
                      css:'gray_toolbar',
                      head: {
                          view:"toolbar",
                          cols:[
                           { view:"label", label:"Runtime Configuration", align:'right'},
                           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
                              optionsE.arguments = UI('configEditorAgentArgs'+numed).getValue();
                              optionsE.level = Number(UI('configEditorAgentLevel'+numed).getValue())||0;
                              optionsE.file=UI('configEditorFileName'+numed).getValue();
                              editorWidget.setLabel(Label());
                              configEditorWin[numed].hide();
                              updateMenu();
                           }}
                          ]
                      },
	                  body: {
                        rows:[
                          { view:"form", scroll:true, width:300, height:400, elements: [
                            { view:"label", label:"<b>Agent Creation</b>", height:'18px', align:'left'},
                            { view:"textarea", id:"configEditorAgentArgs"+numed, label:"Argument",  height:150, value:optionsE.arguments},
                            { view:"text", id:"configEditorAgentLevel"+numed, label:"Level",    value:optionsE.level},
                            { view:"label", label:"<b>Code</b>", height:'18px', align:'left'},
                            { view:"text", id:"configEditorFileName"+numed, label:"File Name", value:optionsE.file},
                          ]}
                        ]
                      }
                    });
                }
                configEditorWin[numed].show();
            },
            'compile:indent:Compile Code' : async function () {
              var code = editorWidget.editor.get();
              var acname = await options.shell.env.compile(code);
              if (acname) {
                optionsE.acname=acname;
                editorWidget.setLabel(Label());
                optionsE.compiled=true;
                editorWidget.code=code;
              } else {
                optionsE.compiled=false;
                info('Agent class function not compiled.');
              }
              updateMenu();
            },
            'compile:play:Start Agent' : async function () {
              var code = editorWidget.editor.get();
              if (!optionsE.compiled || code!=editorWidget.code) {
                var acname = options.shell.env.compile(code);
                if (acname) {
                  optionsE.acname=acname;
                  editorWidget.setLabel(Label());
                  optionsE.compiled=true;
                  editorWidget.code=code;
                  updateMenu();
                } else {
                  optionsE.compiled=false
                  return info('No agent started, class function not compiled.');
                }
              }
              var ac=optionsE.acname||optionsE.file.replace(/\.js$/,''),args; 
              if (optionsE.arguments!="") eval('args='+optionsE.arguments);
              else args={};
              var id=await options.shell.env.create(ac,args,optionsE.level);
              if (id) {
                info('Agent '+id+' of class '+ac+' started.');
                shellControl.commands.print('[JLB] Agent '+id+' of class '+ac+' started.'); 
              } else {
                error('Starting agent of class '+ac+' failed.');
                shellControl.commands.error('[JLB] Error: Starting agent of class '+ac+' failed.');             
              }
              
            },
            
          }
        })
        editorWin[numed]=editorWidget;
        editorWin[numed]._options=optionsE;
        editorWidget.on('file',function (filename) {
          optionsE.file=filename;
          editorWidget.setLabel(Label())
          updateMenu();
        })
        editorWidget.on('close',function () {
          delete editorWin[numed];
          updateMenu();  
        })
        editorWin[numed].editor.set(classTemplate);
        updateMenu();  
      },
      'monitor:envelope:Agent Monitor' : function () {
        if (!monitorWin[num]) {
          // createMonitor(num,{shell:shell[num].options.shell})
          // TODO
          monitorWidget=UI.Monitor.default({
            label:options.name+' / Monitor S'+num,
            close:false,
            run : function (line) {
              shellControl.question(line);
            }
          })
          monitorWin[num]=monitorWidget;
          monitorWin[num]._options=options;
          options.shell.options.outputAgent = function (line) { monitorWidget.env.print(line) };
          updateMenu();
        } else monitorWin[num].window.show();
      },
      'info:info:Node Information' : async function () {
        UI('infoShellWinLabel'+num).setValue("Info / "+Label());
        if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
        options.infoView=UI('infoShellWinScrollView'+num).addView(
          {
            rows : await updateInfo(cmd,num)
          }
        );
        infoShellWin[num].show();
      },
      'script:gear:New Script Editor': function () {
        var optionsSE = {
          shell:num,
          file:'script.js',
        }
        var numed = editorNum++;
        function Label() {
          return optionsSE.file+' / '+options.name+' ScE'+numed+':S'+optionsSE.shell
        }
        var editorWidget = UI.Editor.default({
          label:Label(),
          mode:'js',
          buttons : {
            'compile:play:Start Agent' : function () {
              var code = editorWidget.editor.get();
              shellControl.options.cmd.exec(code);
            }
          }
        })
        editorWin[numed]=editorWidget;
        editorWin[numed]._options=optionsSE;
        editorWidget.on('file',function (filename) {
          optionsSE.file=filename;
          editorWidget.setLabel(Label())
          updateMenu();
        })
        editorWidget.on('close',function () {
          delete editorWin[numed];
          updateMenu();  
        })
        updateMenu();        
      }
    },
    run : function (line,env) {
      shellControl.question(line);
    },
    label : Label(),
    close : num>1,
  })
  shellControl.init(shellWidget, async function () {
    shellWin[num]=shellWidget;
    shellWin[num]._options=options;
    shellWidget.on('close', function () {
      shellControl.stop()
    })
    // config.name = options.name;
    cmd = options.cmd;
    shell[num].run=cmd;
    cmd.on('link+',function (arg){
      updateConn(cmd,options.name);
    });
    cmd.on('link-',function (arg){
      updateConn(cmd,options.name);
    });
    if (num==1) Marked = cmd.marked;
    updateMenu();  
    var versions = await cmd.versions();
    shellControl.commands.print('JAMlib Version '+versions.lib+' JAMShell Version '+versions.shell+' JAMAios Version '+versions.aios);
    shellWidget.setLabel(Label());  
  });
  if (num>1) {
    shellWidget.on('close',function () {
      removeConn(options.name);
      shellControl.stop();
      delete shell[num];
      delete shellWin[num];
    })
  }

  /************** SHELL INFO MANAGER ****************/
  infoShellWin[num]=webix.ui({
    id:'infoShellWin'+num,
	  view:"window",
	  height:350,
	  width:500,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      css:'gray_toolbar',
      head: {
          view:"toolbar",
          id:"infoShellWinToolbarWin"+num,
          cols:[
           { view:"button", type:"icon", icon:"repeat", tooltip:'Refresh', width:30, click:async function () {
              if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
              options.infoView=UI('infoShellWinScrollView'+num).addView(
                {
                  rows : await updateInfo(cmd,num)
                }
              );
           }},
           { view:"button", type:"icon", icon:"times-circle", tooltip:'Kill Agent', width:30, click:async function () {
              var tree = UI('treeAgentsNode'+num);
              var selectedId = tree.getSelectedId();
              if (selectedId.indexOf('agent.')==0) {
                var agentid = selectedId.substring(selectedId.indexOf('.')+1,selectedId.indexOf(' '));
                await cmd.kill(agentid);
              }
              if (options.infoView) UI('infoShellWinScrollView'+num).removeView(options.infoView);
              options.infoView=UI('infoShellWinScrollView'+num).addView(
                {
                  rows : await updateInfo(cmd,num)
                }
              );
           }},
           { view:"label", label:"Info / "+Label(), id:'infoShellWinLabel'+num, align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              infoShellWin[num].hide();
           }}
          ]
      },
	  body:{
        view : 'form',
        scroll : 'y',
        height: 300,
        id:'infoShellWinScrollView'+num,
        rows : [
          ]
     }    
    });

}
createShell(shellNum,{});
shellNum++;




/************ EDITOR *****************/
   
function createEditor(num,opts) {
  var options = {
    arguments : "{}",
    level:2,
    compiled:false,
    file:'untitled',
    shell:opts.shell,  // number
    script:opts.script,
    name : opts.name
  }
  function Label() {
    if (options.script)
      return options.file+' / script / Shell '+options.shell+' / Editor '+num
    else
      return options.file+' / '+options.name+" / Shell "+options.shell+" / Editor "+num
  }
  var head = {
          view:"toolbar",
          cols:[
           { view:"button", type:"icon", icon:"folder-open", tooltip:'Open File', width:30, click:function () {
             options.compiled = false;
             loadFile(function (text,file) {
                if (text) {
                  UI('SourceText'+num).setValue(text)
                  options.file=file
                  UI('SourceTextWinLabel'+num).setValue(Label())
                  updateMenu();
                }
              });            
           }},
           { view:"button", type:"icon", icon:"save", tooltip:'Save File', width:30, click:function () {
            var code = UI('SourceText'+num).getValue();
            if (!/\.js$/.test(options.file)) options.file+='.js';
            saveFile(code,options.file,'text/plain',function (file) {
              if (file) {
                options.file=file
                UI('SourceTextWinLabel'+num).setValue(Label())
                updateMenu()
              }
            });
           }},
           { view:"button", type:"icon", icon:"file", tooltip:'New File', width:30, click:function () {
              options.compiled = false;
           }},
           opts.script?null:{ view:"button", type:"icon", icon:"navicon", tooltip:'Config', width:30, click:function () {
             configEditorWin[num].show();
           }},
           { view:"button", type:"icon", icon:"user", tooltip:'Share Code', width:30, click:function () {
             Clip.share(num);
           }},
           opts.script?null:{ view:"button", type:"icon", icon:"indent", tooltip:'Compile Class', width:30, click:function () {
            var code = UI('SourceText'+num).getValue();
            var acname = shell[options.shell].options.shell.env.compile(code);
            if (acname) {
              options.file = acname+'.js';
              UI('SourceTextWinLabel'+num).setValue(Label());
              options.compiled = true;
            }
            updateMenu();
           }},
           opts.script?{ view:"button", type:"icon", icon:"play", tooltip:'Execute script', width:30, click:function () {
              var code = 'later(1,function () {'+UI('SourceText'+num).getValue()+'})';
              shell[options.shell].ask(code);
           }}           
           :{ view:"button", type:"icon", icon:"play", tooltip:'Create Agent', width:30, click:function () {
            if (options.compiled == false) {
              var code = UI('SourceText'+num).getValue();
              var acname = shell[options.shell].options.shell.env.compile(code);
              if (acname) {
                options.file = acname+'.js';
                UI('SourceTextWinLabel'+num).setValue(Label());
              }
              options.compiled = true;
              updateMenu();
            }
            var ac=options.file.replace(/\.js$/,''),args; 
            if (options.arguments!="") eval('args='+options.arguments);
            else args={};
            var id=shell[options.shell].options.shell.env.create(ac,args,options.level);
            if (id) {
              info('Agent '+id+' of class '+ac+' started.');
              shell[options.shell].print('[JLB] Agent '+id+' of class '+ac+' started.'); 
            } else {
              error('Starting agent of class '+ac+' failed.');
              shell[options.shell].print('[JLB] Error: Starting agent of class '+ac+' failed.');             
            }
           }},
           { view:"label", label:Label(), id:'SourceTextWinLabel'+num,  align:'right'},
           { view:"button", type:"icon", icon:"close", tooltip:'Close Editor', width:30, click:function () {
            var self=editorWin[num];
            // editorWin[num]=undefined;
            self.hide();
           }},
          ].filter(function (o) { return o })
      }
  var config = {
    id:'SourceTextWin'+num,
	  view:"window",
	  height:350,
	  width:600,
	  left:250, top:50,
	  move:true,
	  resize: true,
      toFront:true,
      css:opts.script?'blue_toolbar':'green_toolbar',
	  head:head,
	  body:{
          id : 'SourceText'+num,
          view: "codemirror-editor",        
          attributes : { spellcheck:false, smartIndent:false, indentUnit:2},
      }
  }
  
  editorWin[num]=webix.ui(config);
  if (!options.script) UI('SourceText'+num).setValue(classTemplate);

  editorWin[num]._options=options;
  editorWin[num].show();
    
  /********** EDITOR CONFIG ****************/
  configEditorWin[num]=webix.ui({
      id:'configEditorWin'+num,
	  view:"window",
	  width:300,
	  height:450,
	  left:90, top:90,
	  move:true,
	  resize: true,
      toFront:true,
      css:'gray_toolbar',
      head: {
          view:"toolbar",
          cols:[
           { view:"label", label:"Configuration", align:'right'},
           { view:"button", type:"icon", icon:"check-square",  align:'center', width:30, click: function () {
              options.arguments = UI('configEditorAgentArgs'+num).getValue();
              options.level = Number(UI('configEditorAgentLevel'+num).getValue())||0;
              options.file=UI('configEditorFileName'+num).getValue();
              UI('SourceTextWinLabel'+num).setValue(Label());
              configEditorWin[num].hide();
           }}
          ]
      },
	  body: {
        rows:[
          { view:"form", scroll:true, width:300, height:400, elements: [

            { view:"label", label:"<b>Agent Creation</b>", height:'18px', align:'left'},
            { view:"textarea", id:"configEditorAgentArgs"+num, label:"Argument",  height:150, value:options.arguments},
            { view:"text", id:"configEditorAgentLevel"+num, label:"Level",    value:options.level},
            { view:"label", label:"<b>Code</b>", height:'18px', align:'left'},
            { view:"text", id:"configEditorFileName"+num, label:"File Name", value:options.file},
          ]}
        ]
      }
    });
  updateMenu();
}



/****************** MONITOR *******************/

function createMonitor(num,opts) {
  var tables={},reporter;
  opts.cmd = function (cmd) {
    // @cmd arg1 arg2 ...
    var obj,table,
        prefix=cmd.substr(0,cmd.indexOf(' ')),
        args=cmd.substr(cmd.indexOf(' ')+1); 
        console.log(prefix);
        console.log(args);
    switch (prefix) {
      case '@monitor:new':
        obj=JSON.parse(args);
        options.header=obj.header;
        options.id=obj.id;
        if (!reporterWin[num]) createReporter(num,options);
        break;
      case '@monitor:data':
        if (!reporterWin[num]) return;
        obj=JSON.parse(args);
        table=UI(reporterWin[num]._datatable.id);
        table.add(obj.row)
        break;
    } 
  }
  monitor[num]    = winShell(opts);
  var options     = monitor[num].options;
  options.shell   = opts.shell;
  options.closed  = false;
    
  function Label () {
    return jamConfig.name+" / Shell "+num+" / Agent Monitor"
  }
  monitorWin[num]=webix.ui({
      id:'MonitorWin'+num,
	  view:"window",
	  height:350,
	  width:600,
	  left:350, top:250,
	  move:true,
	  resize: true,
      toFront:true,
      css:'black_toolbar',
      head: {
          view:"toolbar",
          cols:[
            { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () {
              monitor[num].commands.clear()
            }},
            { view:"button", type:"icon", icon:"dot-circle-o", tooltip:'Scroll Auto', id:'MonitorWinLogFilmBut'+num, width:30, click:function () {
              UI('MonitorWinLog'+num).scrollAuto=!UI('MonitorWinLog'+num).scrollAuto;
              if (UI('MonitorWinLog'+num).scrollAuto) UI('MonitorWinLogFilmBut'+num).define('icon','arrow-circle-down')
              else UI('MonitorWinLogFilmBut'+num).define('icon','dot-circle-o');
              UI('MonitorWinLogFilmBut'+num).refresh();
            }},
            { view:"button", type:"icon", icon:"table", tooltip:'Data Monitor', width:30, click:function () {
              if (!reporterWin[num]) return;
              // createReporter(num,options);
              reporterWin[num].show();
            }},
            { view:"label", label:Label(), id:'MonitorWinLabel'+num, align:'right'},
            { view:"button", type:"icon", icon:"close", tooltip:'Kill', width:30, click:function () {
              monitorWin[num].hide();
            }},
          ]
      },
	  body:{
          id : 'MonitorWinLog'+num,
          view : 'scrollview',
          scroll : 'y',
          body : {
             id:'MonitorWinLogText'+num,
             rows : [
                {template:('<div id="MonitorWinOutput'+num+'" '+
                           'spellcheck="false" style="">'), height:"auto", borderless:true},
              ]
          }
	  }
  });
  UI('MonitorWinLog'+num).scrollAuto=false;
  monitorWin[num].show();
  monitor[num].init(undefined,"MonitorWinOutput"+num,UI('MonitorWinLog'+num));
  UI('MonitorWinLabel'+num).setValue(Label());
  updateMenu()
}

/****************** REPORTER *******************/

function createReporter(num,options) {
  function makeReport(tbl) {
  console.log(tbl)
    var header = tbl.shift(),
        columns = [],
        hash =[],
        data =[];
    header.forEach(function (col,i) { 
      console.log(i,col)
      columns.push({id:col,header:col,editor:'text'}); hash[i]=col; 
    });
    tbl.forEach (function (row,i) {
      var obj={id:i};
      row.forEach(function (col,j) { obj[hash[j]]=col; });
      data.push(obj);
    });
    var datatbl = {
      view:"datatable",
      id:'DataTable'+options.id+num, 
      columns:columns,
	  select:"cell",
	  multiselect:true,
	  blockselect:true,
      clipboard:"selection",
      data: data,
      on:{
				  onBeforeBlockSelect:function(start, end, fin){
					  if (start.column === "rank")
						  end.column = "votes";

					  if (fin && start.column == "rank"){
						  var mode = this.isSelected(start) ? -1 : 1;
						  this.selectRange(
							  start.row, start.column, end.row, end.column,
							  mode
						  );
						  return false;
					  }
				  }
	  },
    };
    return datatbl;          
  }
  var datatable =  makeReport([options.header.map(function (col) { return Object.keys(col)[0]})]);
  reporterWin[num]=webix.ui({
      id:'ReporterWin'+num,
	  view:"window",
	  height:350,
	  width:600,
	  left:350, top:250,
	  move:true,
	  resize: true,
      toFront:true,
      head: {
          view:"toolbar",
          cols:[
            { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:30, click:function () {
            }},
            { view:"button", type:"icon", icon:"flag", tooltip:'Scroll Auto', id:'ReporterWinLogFilmBut'+num, width:30, click:function () {
              UI('ReporterWinLog'+num).scrollAuto=!UI('ReporterWinLog'+num).scrollAuto;
              if (UI('ReporterWinLog'+num).scrollAuto) UI('ReporterWinLogFilmBut'+num).define('icon','flag-o')
              else UI('ReporterWinLogFilmBut'+num).define('icon','flag');
              UI('ReporterWinLogFilmBut'+num).refresh();
            }},
            { view:"button", type:"icon", icon:"table", tooltip:'Data Monitor', width:30, click:function () {
            }},
            { view:"label", label:"Thread "+options.thread+ " / "+options.ip+':'+options.port+" / Shell "+options.shell+" / Report "+num, id:'ReporterWinLabel'+num, align:'right'},
            { view:"button", type:"icon", icon:"close", tooltip:'Kill', width:30, click:function () {
              var self=reporterWin[num];
              self.hide();
            }},
          ]
      },
	  body:datatable
	 
  });
  reporterWin[num]._datatable=datatable;
  return reporterWin[num]
}

/* Load help files */
help.aios = SHELL.doc.aios;
help.jamsh = SHELL.doc.shell;
help.js = SHELL.doc.javascript;

/////////////
// CHAT WIN
/////////////
chatWin = webix.ui({
  id:'chatWin',
      view:"window",
      height:400,
      width:400,
      left:100, top:100,
      move:true,
      resize: true,
  toFront:true,
  head: {
      view:"toolbar",
      id:"myToolbarchatWin",
      cols:[
       { view:"button", type:"icon", icon:"eraser", tooltip:'Clear', width:50, click:function () {
        chatReset();
       }},
       { view:"button", type:"icon", icon:"user", tooltip:'Talk', width:50, click:function () {
        // interrupt request (question)
       }},
       { view:"label", label:"Agent Chat", align:'right'},
       { view:"button", type:"icon", icon:"close",  align:'center', width:30, click: function () {
          chatWin.hide();
       }}
      ]
  },
  body:{
    template : ' <div class="botui-app-container" id="chat-bot" style="width:100%;"><bot-ui></bot-ui></div>',
    borderless:false            
  }
});

chatWin.chatActions=[]; // copy(chatActionsInit,true);
chatWin.chatLastAction=undefined;
chatWin.nextChatHistory=[];
chatWin.chatHistory=[];
chatWin.chatOpen=false;

// Clear the chat
function chatReset () {
  chatWin.chatActions=[];
  chatWin.chatHistory=[];
  chatWin.nextChatHistory=[];
  chatWin.chatLastAction=undefined;
  if (chatWin.botui) {
    chatWin.botui.message.removeAll();
  }
  chatRefresh();
}
// Refresh the chat by adding old/new messages and actions

function chatRefresh () {
  if (!chatWin.botui) return;
  function exec(l,last) {
    var todo=l.shift();
    if (!todo) return;
    function doit(todo,last) {
      switch (todo.kind) {
        case 'message':
          last=chatWin.botui.message.bot(todo);
          break;
        case 'answer':
          last=chatWin.botui.message.human(todo);
          break;
        case 'wait':
          if (last) last=last.then(function () {});
          break;
        case 'button':
          chatWin.chatLastAction=[todo]; 
          if (last) last=last.then(function () { return chatWin.botui.action.button(todo); }).then(function (res) {
                                      chatWin.chatLastAction=[];
                                      return { then:function (f) { f(res) } }
                          });
          else {
            last=chatWin.botui.action.button(todo).then(function (res) {
                                      chatWin.chatLastAction=undefined;
                                      return { then:function (f) { f(res) } }
                          });
          }
          break;
        case 'value':
          chatWin.chatLastAction=[todo]; 
          if (last) last=last.then(function () { return chatWin.botui.action.text(todo); }).then(function (res) {
                                      chatWin.chatLastAction=[];
                                      return { then:function (f) { f(res) } }
                          });
          else {
            last=chatWin.botui.action.text(todo).then(function (res) {
                                      chatWin.chatLastAction=undefined;
                                      return { then:function (f) { f(res) } }
                          });
          }
          break;
        case 'checkpoint':
          if (chatWin.chatLastAction.length) chatWin.chatLastAction.push(todo);
          if (last) last=last.then(function (res) { if (todo.timer) clearTimeout(todo.timer); return todo.callback(res) });
          break;
      }
      return last
    }
    last=doit(todo,last);
    if (l.length) exec(l,last);
  }
  exec(chatWin.chatHistory);
  exec(chatWin.chatActions);  
}
// Add a chat action
function chatAction (action) {
  chatWin.chatActions.push(action);
}

// External chat requests
function chatQuestion (id, question, action, callback, timeout) {
  // TODO: Remove question after timeout!
  var actions=[],timer;
  chatAction({kind:'message', delay:0, content: id+': '+question });
  if (timeout) timer=setTimeout(function () {
    chatWin.chatLastAction=[];
    // console.log('chatQuestion timeout');
    chatWin.chatActions=chatWin.chatActions.filter(function (todo) {
      if (todo.kind=='checkpoint' || todo.action) return false;
      else return true;
    });
    if (chatWin.botui) chatWin.botui.removeAction();
    if (callback) callback();
  },timeout);
  if (action instanceof Array) {
    // Buttons/Choices
    if (typeof action[0] == 'string') action=action.map(function (s) { return { text:s, value:s } });
    chatAction({kind:'button', delay:0, action: action});
    chatAction({kind:'checkpoint', callback:callback, timer:timer});
  } else if (typeof action == 'object') {
    // Value or text
    chatAction({kind:'value', delay:0, action: action});
    chatAction({kind:'checkpoint', callback:callback, timer:timer});  
  } 
  chatRefresh();
}

function chatMessage (id,message) {
  chatAction({kind:'message', delay:0, content: id+': '+message });
  chatRefresh();
}  




/* Chat Script Interpreter */
// {question:string, choices?:[], value?:number|string, eval?:function, 
//  cond?:function, timeout?:numebr, tag?:string }
// {message:string}
// {finalize:function}
// {process:function}
// {init:function}
// ! After reply to a question the question record contains an answer attribute !
chatScript = {
  script :  [],
  id:'Anonymous',
  index : 0,
  cancel : function () {
    self.chatScript.script=[];
  },
  init: function (id,script) {
    self.chatScript.script=script;
    self.chatScript.index=0;
    self.chatScript.id=id;
  },
  next : function () {
    var process = shell[1].run.Aios.current.process;
    var next = self.chatScript.script[self.chatScript.index];
    self.chatScript.index++;
    if (!next) return 0;
    if (next.question) {
      if (next.cond && !next.cond.call(process.agent,self.chatScript.script)) return -1;
      if (next.eval) {
        var replace = next.eval.call(process.agent,self.chatScript.script);
        // TODO
      }
      var timeout = next.timeout||30000;
      chatQuestion(self.chatScript.id,
                  next.question,
                  next.choices||{value:next.value},
                  function (res) {
                    if (next.callback)
                      shell[1].run.Aios.CB(process,next.callback,res?[res.value]:null);
                    process.wakeup();
                    shell[1].run.schedule();
                  },
                  timeout
      );
      process.suspend(shell[1].run.Aios.timeout(timeout));
    } else if (next.message) {
      chatMessage(self.chatScript.id,
                  next.message)     
    } 
    else if (next.process) next.process(self.chatScript.script);
    else if (next.init) next.init.call(process.agent,self.chatScript.script);
    else if (next.finalize) next.finalize(self.chatScript.script);
    return 1;
  },
  reset : function () {
    self.chatScript.index=0;
  },
}

chatInit = function () {
  if (!chatWin.botui) {
    chatWin.nextChatHistory=[];
    chatWin.botui = new BotUI('chat-bot',{callback:function (msg) {
        // Record message history for page refresh
        if (msg.content) {
          if (msg.human) chatWin.nextChatHistory.push({kind:"answer", delay:0, content:msg.content});
          else chatWin.nextChatHistory.push({kind:"message", delay:0, content:msg.content});
        }
      }}); 
    chatMessage('Chat','Ready.');
    chatRefresh();        
  }
}
chatAgent = function (action) {
  if (!chatWin.agent) {
    if (action.load)
      return clippy.load(action.load, function (agent) {
                chatWin.agent=agent;
           });
    else return;
  }
  for(var p in action) {
    switch (p) {
      case 'play' :   chatWin.agent.play(action[p]); break;
      case 'speak':   chatWin.agent.speak(action[p]); break;
      case 'hide':    chatWin.agent.stop(); chatWin.agent.hide(); break;
      case 'moveTo':  chatWin.agent.moveTo(action[p].x,action[p].y,action[p].time); break;
      case 'load':    chatWin.agent.show(true); break;
      case 'show':    chatWin.agent.show(action[p]); break;
      case 'stop':    chatWin.agent.stop(); break;
      case 'stopCurrent': chatWin.agent.stopCurrent(); break;
    }
  }
}
shell[1].run.extend([2,3],'message',chatMessage,2);
shell[1].run.extend([2,3],'question',function (id, question, action, callback, timeout) {
        var process = shell[1].run.Aios.current.process;
        if (timeout==undefined) timeout=60000;
        chatQuestion (id, question, action, function (res) {
          shell[1].run.Aios.CB(process,callback,res?[res.value]:null);
          process.wakeup();
          shell[1].run.schedule();
        },timeout);
        process.suspend(shell[1].run.Aios.timeout(timeout));
      },[4,5]);


function openWin(num,content,options) {
  var panel = webix.ui({
      id:'Panel'+num,
	  view:"window",
	  height:350,
	  width:600,
	  left:350, top:250,
	  move:true,
	  resize: true,
      toFront:true,
      css:'black_toolbar',
      head: {
          view:"toolbar",
          cols:[
            { view:"label", label:options.title||("Window "+num), align:'right'},
            { view:"button", type:"icon", icon:"close", tooltip:'Close', width:30, click:function () {
              panel.close();
            }},
          ]
      },
      body:    {
        template : content,
        borderless:false            
      }   
  });
  panel.show();
}

Config=jamConfig;
//////////////////////////////////////////////////////////////

