// Simulation extension for phyiscal (behaviorual) agents
var RTree   = Require('rtree/rtree');
var RBush   = Require('rtree/rbush');
var RBushKnn = Require('rtree/rbush-knn');
var Comp = Require('com/compat');

var current=none;
var Aios = none;

// patchgrid agent instance counter
var instances = 0;

// Geometric Utiliy Functions
function sind(x) { return Math.sin(x/360*(2*Math.PI)) }
function cosd(x) { return Math.cos(x/360*(2*Math.PI)) }
function rotate(d,a) {
  return [
    int(d[0]*cosd(a)-d[1]*sind(a)),
    int(d[1]*sind(a)+d[0]*sind(a))
  ]
}
function distance2Rect (pos,bbox,scale) {
  if (!scale) scale={x:1,y:1};
  var px = pos.x,
      py = pos.y,
      x0 = bbox.x+bbox.w/2,
      y0 = bbox.y+bbox.h/2,
      dx = (Math.max(Math.abs(px - x0) - bbox.w / 2, 0))/scale.x,
      dy = (Math.max(Math.abs(py - y0) - bbox.h / 2, 0))/scale.y;

  return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
}
function distance (pos1,pos2,scale) {
  if (!scale) scale={x:1,y:1};
  var 
      dx = Math.abs(pos1.x - pos2.x) / scale.x,
      dy = Math.abs(pos1.y - pos2.y) / scale.y;

  return Math.sqrt(Math.pow(dx,2)+Math.pow(dy,2))
}

/* construct bbox {x,y,w,h} from geometric data 
  {x,y,x0,y0,x1,y1,dx,dy,w,h,dir} relative
  to current position {x,y}
  optional bounds {x0,y0,x1,y1}
 
  +----->  x
  |      N            x,y--+
  |    W X E          |    |
  v      S            +--w,h
  
  y
 
*/

function makeBbox (pos,geo,bounds) {
  bbox={x:pos.x,y:pos.y,w:0,h:0} // {x,y,w,h}
  if (typeof geo == 'number') // radius around center pos
    return {x:pos.x-geo,y:pos.y-geo,w:2*geo+1,h:2*geo+1};
 
  if (geo.x)  bbox.x=geo.x;
  if (geo.y)  bbox.y=geo.y;
  if (geo.x0) bbox.x=geo.x0;
  if (geo.y0) bbox.x=geo.y0;
  if (geo.dx) bbox.x=pos.x+geo.dx;
  if (geo.dy) bbox.y=pos.y+geo.dy;
  if (geo.w)  bbox.w=geo.w;
  if (geo.h)  bbox.w=geo.h;
  if (geo.x1) bbox.w=geo.x1-bbox.x+1;
  if (geo.y1) bbox.h=geo.y1-bbox.y+1;
  if (geo.r) return {x:bbox.x-geo.r,y:bbox.y-geo.r,w:2*geo.r+1,h:2*geo.r+1};  
  if (geo.dir) switch (geo.dir) {
    // including current position X
    // Ex. WEST:
    // ****
    // ***X
    // ****
    case Aios.DIR.NORTH: 
      if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
      bbox.x -= int(bbox.w/2); bbox.y -= (bbox.h-1);  
      break;
    case Aios.DIR.SOUTH:
      if (geo.distance) bbox.w=geo.spread||1,bbox.h=geo.distance+1;
      bbox.x -= int(bbox.w/2);
      break;
    case Aios.DIR.WEST: 
      if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
      bbox.y -= int(bbox.h/2); bbox.x -= (bbox.w-1);  
      break;
    case Aios.DIR.EAST:
      if (geo.distance) bbox.h=geo.spread||1,bbox.w=geo.distance+1;
      bbox.y -= int(bbox.h/2);  
      break;
  }
  return bbox;
}

function bbox2pp(bbox) {
  return {x0:bbox.x,y0:bbox.y,x1:bbox.x+bbox.w-1,y1:bbox.y+bbox.h-1,
          dir:bbox.dir,distance:bbox.distance}
}
function pp2bbox(pp) {
  return {x:pp.x0,y:pp.y0,w:pp.x1-pp.x0+1,h:pp.y1-pp.y0+1}
}

function whatType(what) {
  // agent-twin => agent
  var tokens = what.match(/([a-z]+)(-.+)/)
  return tokens?tokens[1]:what;
}  
function whatName(what) {
  // agent-twin => twin
  var tokens = what.match(/[a-z]+-(.+)/)
  return tokens?tokens[1]:null;
}

/* 
** Generic simulation object iterator (NetLogo comp.)
** 1. Agents
** ask('agent','*',cb)  // all
** ask('agent',null,cb) // here
** ask('agent',dir,cb)
** ask('agent',bbox,cb)
** ask('agents-class','*',cb)
** ask('agent',id:string,cb)
** ask('agent',[id1:string,id2,..],cb)  
** ask('agent',5,cb) == in-radius 5
** ask('agent',[x,y],cb) == @(x,y)
** ask('agent',{x,y,w,h},cb?) == within[x,y,x+w,y+h]
** ask('agent',{x0,y0,x1,y1},cb?) == within[x,y,x+w,y+h]
** ask('agent',{dx,dy,w,h},cb?) == within[dx+x0,dy+y0,x0+dx+w,y0+dy+h]
** ask('agent',{x,y,w,h},cb?) == within[x,y-x+w,y+h]
** => returns only physical agents!
**
** 2. Resources
** ask('resource',..)
** ask('resources-class',..)
** 
** 3. Patches
** ask('patch',..)
** ask('patches',..)
**
** Groups:
** ask('children')
** ask('parent')
**
** ask('distance',dir|number|bbox)
**
** Note: callback is executed in CURRENT agent context unless remote flag is set True:
** typeof callback = function (agent object,node object) 
**
** TODO: check for consitency when using arrow callback functions (no this rebind possible)
*/
function aiosXnet(aiosXsimu,module) {
  var self=this;
  
  Aios=module;current=Aios.current;
  
  function ask(what,who,callback,remote) {
    var type = self.gui.classObject(what)||what,
               node = current.node,nodeId,
               pos = node.position, bbox, jump,
               desc,id,pro,set=[],set2=[],set3=[],multiple=true,
               i,j,r,row,col,p,q,agent,agents,nodes,pro,
               name=what.match(/[a-z]+-(.+)/,'');
    if (!self.options.patch) return;
    if (name) name=name[1];
    if (what.indexOf(type+'s')==0) type += 's';
    switch (type) {

      case 'agent':
        multiple=false;
      case 'agents':
        if (typeof who == 'string') {
          // entire world has to be searched for agent
          if (who=='*') who=/.+/;
          else if (self.cache.agent2node[who]) {
              agent=self.cache.agent2node[who].getAgentProcess(0);
              set=[{
                agent:agent.agent.id,
                class:agent.agent.ac,
                pos:agent.node.position,
                distance:distance(pos,agent.node.position),
                obj:agent.agent
               }]
              set2=[agent.node]
          }
          if (set.length==0) {
            agents=self.world.getAgentProcess(who,name);
            if (agents) {
              if (Comp.obj.isArray(agents)) {
                set=agents.map(function (ap)  { 
                  return {
                    agent:ap.agent.id,
                    class:ap.agent.ac,
                    pos:ap.node.position,
                    distance:distance(pos,ap.node.position),
                    obj:ap.agent
                  }
                });
                set2=agents.map(function (ap) { return ap.node });                
              } else {
                agent=agents;
                self.cache.agent2node[agent.agent.id]=agent.node;
                node = agent.node;
                set.push({
                  agent:agent.agent.id,
                  class:agent.agent.ac,
                  pos:agent.node.position,
                  distance:distance(pos,agent.node.position),
                  obj:agent.agent
                });
                set2.push(node);
                if (remote) set3.push(agent);
              } 
            }
          }
        } else if ((Comp.obj.isArray(who) && who.length==2)  || who==null) {
          if (!who) who=[pos.x,pos.y];
          if (self.agentMap) {
            if (!self.checkBounds(who[0],who[1])) return [];
            row=self.agentMap[who[1]];
            col=row[who[0]];
            for(p in col) {
              agent=self.getAgentProcess(col[p],p);
              if (agent && (!name || agent.agent.ac==name)) {
                set.push({
                  agent:agent.agent.id,
                  class:agent.agent.ac,
                  pos:agent.node.position,
                  distance:distance(pos,agent.node.position),
                  obj:agent.agent
                });
                set2.push(agent.node);
                if (remote) set3.push(agent);
              }
            }
          }            
        } else if (typeof who == 'number' || Comp.obj.isObj(who)) {
          // Bounding box {}? 
          bbox=bbox2pp(makeBbox(pos,who));
          // find agents on nodes in the neighbourhood
          if (self.agentMap) {
            for(i=bbox.x0;i<=bbox.x1;i++)
              for(j=bbox.y0;j<=bbox.y1;j++) {
                if (!self.checkBounds(i,j)) continue;
                row=self.agentMap[j];
                col=row[i];
                for(p in col) {
                  agent=self.getAgentProcess(col[p],p);
                  if (agent && (!name || agent.agent.ac==name)) {
                    set.push({
                      agent:agent.agent.id,
                      class:agent.agent.ac,
                      pos:agent.node.position,
                      distance:distance(pos,agent.node.position),
                      obj:agent.agent
                    });
                    set2.push(agent.node);
                    if (remote) set3.push(agent);
                  }
                }
              }
          } else {
            if (!name) return;
            self.world.nodes.forEach(function (_node) {
              if (wihtin(_node.position,node.position,who)) {
                agents=_node.getAgentProcess(/[a-zA-Z]+/);
                for(p in agents) {
                  if (agents[p].agent.ac==name) {
                    set.push({
                      agent:agents[p].agent.id,
                      class:agents[p].agent.ac,
                      pos:_node.position,
                      distance:distance(pos,_node.position),
                      obj:agents[p].agent
                   });
                    set2.push(_node);
                  }
                }
              }
            })
          }
        }
        break;

      case 'resource':
        multiple=false;
      case 'resources':
        var px = pos.x * self.options.patch.width,
            py = pos.y * self.options.patch.height;

        if (typeof who == 'string' && who.indexOf('DIR.')<0 && who != '*') {
            var obj = self.gui.objects['resource['+who+']'];
            if (obj) set = {resource:who,
                    class:obj.class,
                    data:obj.data,
                    distance:distance2Rect({x:px,y:py},
                                           obj.visual,
                                           {x:self.options.patch.width,
                                            y:self.options.patch.height}),
                    x:int(obj.visual.x/self.options.patch.width),
                    y:int(obj.visual.y/self.options.patch.height),
                    w:int(obj.visual.w/self.options.patch.width),
                    h:int(obj.visual.h/self.options.patch.height)
            }
        } else if (typeof who == 'number' || Comp.obj.isObj(who) || who=='*' ||
              (Comp.obj.isArray(who) && who.length==2) || who==null) {
          if (!who) who=[pos.x,pos.y];
          if (Comp.obj.isArray(who)) bbox={x:who[0],y:who[1],w:1,h:1};  // or w/h=0
          else if (who=='*') bbox={x:0,y:0,w:self.options.x,h:self.options.y};
          else bbox=makeBbox(pos,who);
          set=self.rtree.search(bbox).map(function (rid) {
            var obj = self.gui.objects[rid];
            if (name && obj.class != name) return;
            return {resource:rid.replace(/resource\[([^\]]+)\]/,'$1'),
                    class:obj.class,
                    data:obj.data,
                    distance:distance2Rect({x:px,y:py},
                                           obj.visual,
                                           {x:self.options.patch.width,
                                            y:self.options.patch.height}),
                    x:int(obj.visual.x/self.options.patch.width),
                    y:int(obj.visual.y/self.options.patch.height),
                    w:int(obj.visual.w/self.options.patch.width),
                    h:int(obj.visual.h/self.options.patch.height)
            }
          }).filter (function (o) {return o});
        } 
        break;

      case 'patch':
        multiple=false;
      case 'patches':
        if ((Comp.obj.isArray(who) && who.length==2) || who==null) {
          if (!who) who=[pos.x,pos.y];
          if (!self.checkBounds(who[0],who[1])) return [];
          set=self.patches[who[1]][who[0]];
        } else if (who=='*') {
           set=self.patches;
        } else if (typeof who == 'number' || Comp.obj.isObj(who)) {
          // Bounding box? 
          bbox=bbox2pp(makeBbox(pos,who));
          // find patches in the neighbourhood
          if (name == 'array') {
            for(j=bbox.y0;j<=bbox.y1;j++) {
              for(i=bbox.x0;i<=bbox.x1;i++) {
                if (!self.checkBounds(i,j)) continue;
                set.push(self.patches[j][i])
              }
            }
          } else for(j=bbox.y0;j<=bbox.y1;j++) {
            row=[]
            for(i=bbox.x0;i<=bbox.x1;i++) {
              if (!self.checkBounds(i,j)) continue;
              row.push(self.patches[j][i])
            }
            if (row.length==1) set.push(row[0])
            else set.push(row)
          }
        }
        break;

      // groups
      case 'parent':
        if (node.parent) {
          agents = node.parent.getAgent(/.+/);
          set=agents && agents[0]?agents[0].id:undefined;
        }
        break;
      case 'children':
        multiple=true;
        if (node.children) {
          agents=[]
          node.children.forEach(function (child) {
            agents = agents.concat(child.getAgent(/.+/));
          })
          set=agents;
        }
        break;

      case 'distance':
        function lookup(x,y) {
          var row,col,p,a=[];
          if (jump && jump.x == x && jump.y == y) return; 
          row=self.agentMap[y];
          if (!row) return;
          col=row[x];
          if (!col) return;
          for(p in col) {
            a.push(self.getNode(col[p]));
          }
          return a.length?a:null;
        }
        name=name||'';
        if ((typeof who == 'string' && who.indexOf('DIR.')==0) ||
            typeof who == 'number' || Comp.obj.isObj(who)) {
          bbox={x:pos.x,y:pos.y}
          if (typeof who == 'number') bbox.distance=who;
          if (typeof who == 'string') bbox.dir=who;
          if (typeof who == 'object') bbox.dir=who.dir,bbox.distance=who.distance;
          agents=null;
          switch (bbox.dir) {
            case Aios.DIR.NORTH: 
              bbox={y1:bbox.y-1,y0:bbox.distance?(bbox.y-bbox.distance):0,
                    x0:pos.x,x1:pos.x,dir:bbox.dir}; break;
            case Aios.DIR.SOUTH: 
              bbox={y0:bbox.y+1,y1:bbox.distance?(bbox.y+bbox.distance):self.options.y-1,
                    x0:pos.x,x1:pos.x,dir:bbox.dir}; break;
            case Aios.DIR.WEST:  
              bbox={x1:bbox.x-1,x0:bbox.distance?(bbox.x+bbox.distance):0,
                    y0:pos.y,y1:pos.y,dir:bbox.dir}; break;
            case Aios.DIR.EAST:  
              bbox={x0:bbox.x+1,x1:bbox.distance?(bbox.x+bbox.distance):self.options.x-1,
                    y0:pos.y,y1:pos.y,dir:bbox.dir}; break;
          }
          if (name.indexOf('resource')<0) {
            // 1. Agents
            // Jump over attached group children            
            if (node.children) {
              jump=node.children[0].position;
            }
            switch (bbox.dir) {
              case Aios.DIR.NORTH: for(i=bbox.y1;i>=bbox.y0 && !nodes;i--) nodes=lookup(pos.x,i); break;
              case Aios.DIR.SOUTH: for(i=bbox.y0;i<=bbox.y1 && !nodes;i++) nodes=lookup(pos.x,i); break;
              case Aios.DIR.WEST:  for(i=bbox.x1;i>=bbox.x0 && !nodes;i--) nodes=lookup(i,pos.y); break;
              case Aios.DIR.EAST:  for(i=bbox.x0;i<=bbox.x1 && !nodes;i++) nodes=lookup(i,pos.y); break;
              default:
                // radius or full bbox search?
                for(r=1;r<=bbox.distance;r++) {
                  // TODO 
                } 
                break;
            }
            if (nodes) nodes = {
              distance:distance(pos,nodes[0].position),
              objects:nodes.map(function (node) { 
               var a = node.getAgent(0); 
               return a?{agent:a.id,class:a.ac,pos:node.position}:undefined 
              })
            }
           }
          if (name.indexOf('agent')<0) {
            if (name.indexOf('resource')==0) name=null; 
            // 2. Resources
            bbox=pp2bbox(bbox)
            self.rtree.search(bbox).forEach(function (rid) {
              var obj = self.gui.objects[rid];
              var px = pos.x * self.options.patch.width,
                  py = pos.y * self.options.patch.height;
              if (name && obj.class != name) return;
              // only resources with distance < nodes.distance are considered
              var d = distance2Rect({x:px,y:py},
                                     obj.visual,
                                     {x:self.options.patch.width,
                                     y:self.options.patch.height})
              rid=rid.replace(/resource\[([^\]]+)\]/,'$1');
              if (nodes && nodes.distance==d) nodes.objects.push({
                    resource:rid,
                    class:obj.class,
                    data:obj.data,
                    distance:d,
                    x:int(obj.visual.x/self.options.patch.width),
                    y:int(obj.visual.x/self.options.patch.height),
                    w:int(obj.visual.w/self.options.patch.width),
                    h:int(obj.visual.h/self.options.patch.height)                
              }); else if (!nodes || d < nodes.distance) nodes = {
                distance:d,
                objects:[{
                    resource:rid,
                    class:obj.class,
                    data:obj.data,
                    distance:d,
                    x:int(obj.visual.x/self.options.patch.width),
                    y:int(obj.visual.x/self.options.patch.height),
                    w:int(obj.visual.w/self.options.patch.width),
                    h:int(obj.visual.h/self.options.patch.height)
                }]
              }  
            })
          }
          return nodes;
       } else if (typeof who == 'string') {
          // specific object id
          // 1. Agent?
          if (self.cache.agent2node[who]) {
            return distance(pos,self.cache.agent2node[who].position)
          }  
        }
        break;
      default:
        remote=false;
        break;
    }
    if (set && callback) {
      if (Comp.obj.isMatrix(set))
        for(p in set) {
          for (q in set[p]) {
            if (set2.length)
              callback.call(current.process.agent,set[p][q],set2[p][q],current.process.agent);
            else 
              callback.call(current.process.agent,set[p][q],q,p,current.process.agent);
          }
        } 
      else if (Comp.obj.isArray(set))
        for(p in set) {
          if (remote && set3.length) {
            pro=set3[p];
            Aios.CB(pro,callback,[pro.agent])
          } else
            callback.call(current.process.agent,set[p],set2[p],current.process.agent);
        }
      else {
          if (remote && set3) {
            pro=set3;
            Aios.CB(pro,callback,[pro.agent])
          } else
            callback.call(current.process.agent,set,set2,current.process.agent);
      }
    }
    if (!multiple && set) return set.length==0?null:(set.length==1?set[0]:set);   
    else return set;
  }
  /*
  ** Generic create operation (NetLogo comp., **physical** agents)
  ** Creates node+agent pair.
  **
  ** Note: callback is executed in that agent context!
  */
  function create(what,num,callback) {
    var type = whatType(what),
               node = current.node,nodeId,
               desc,id,pro,
               set=[],
               name=whatName(what);
    if (!self.options.patch) return;
    for(var i=0;i<num;i++) switch (type) {
      case 'agent':
      case 'agents':
        // 1. Get the agent descriptor
        if (!name) self.err('create: agent class is missing')
        desc=self.model.agents[name];
        if (!desc) self.err('create: agent class '+name+' is unknown')
        if (desc.type == 'physical') {
          // Physical agent: Create a  <node,agent> tuple; create agent on this node
          nodeId = aiosXsimu.createNode(
                    name,
                    node.position.x,  // use current position
                    node.position.y,
                    name+'-'+instances);
          if (!nodeId) self.err('create: node class '+name+' cannot be created')
          id=aiosXsimu.createOn(nodeId,name,{},3);
          if (self.options.patch)
            self.agentMap[node.position.y][node.position.x][id]=nodeId;
          pro=self.getProcess(nodeId,id);
          // the callback must be executed before 
          // agent starts execution! Prevent transition after CB
          pro.notransition=true;            
          self.cache.agent2node[id]=pro.node;
          pro.type=pro.node.type='physical';
          // cover arrow functions too, no this rebind possible! first argument is self, too!
          if (callback) Aios.CB(pro,callback,[pro.agent,i]);
          set.push(id);
          instances++;
        } else {
          // Create a new computational agent on this node
          nodeId=node.id; 
          id=aiosXsimu.createOn(nodeId,name,{},3);
          pro=self.getProcess(nodeId,id);
          pro.notransition=true;            
          if (callback) Aios.CB(pro,callback,[pro.agent,i])
        }
        break;
    }
    return set.length==1? set[0]:set
  }

  function die (who) {
    var node=current.node,
        agent=current.process.agent;

    if (!who) {
      delete self.cache.agent2node[agent.id];
      Aios.kill();
      if (self.world.getNode(node.id)) aiosXsimu.deleteNode(node.id);
    } else {
      // TODO
    }
  }

  function forward(delta) {
    var i,x,y,
        node=current.node,
        _node,_agent,
        agent=current.process.agent,
        desc=self.model.agents[agent.ac],id,
        agents=[current.process],
        Delta=[0,0];
    if (!self.options.patch || desc.type != 'physical') return;
    id='node['+node.id+']';
    var visual=aiosXsimu.getVisual(id);
    if (!visual.heading) visual.heading=0;
    if (visual.heading<90) Delta[1] -= delta;   
    else if (visual.heading<180) Delta[0] += delta;   
    else if (visual.heading<270) Delta[1] += delta;   
    else if (visual.heading<360) Delta[0] -= delta;

    if (node.children) {
      node.children.forEach(function (child) {
        agents.push(child.getAgentProcess(0));
      })
    }
    // check spatial bounds of all agents to be moved
    for(i in agents) {
      _agent=agents[i];
      _node=_agent.node;
      x=_node.position.x+Delta[0];
      y=_node.position.y+Delta[1];
      if (!self.checkBounds(x,y)) return;
    }
    // passed - now move all agents
    for(i in agents) {
      _agent=agents[i];
      _node=_agent.node;
      x=_node.position.x+Delta[0];
      y=_node.position.y+Delta[1];
      aiosXnet.setxy(x,y,'agent',_agent)
    }

  }

  function globals() {
    return self.model.parameter
  }

  function get(p) {
    var agent=current.process.agent,
        node=current.node,id,
        desc=self.model.agents[agent.ac],
        visual;
    if (!self.options.patch) return;
    switch (p) {
      case 'color':
        id='node['+node.id+']';
        visual=aiosXsimu.getVisual(id);
        return visual.fill.color;
        break;   
      case 'heading':
        id='node['+node.id+']';
        visual=aiosXsimu.getVisual(id);
        return visual.heading;
        break;   
      case 'shape':
        id='node['+node.id+']';
        visual=aiosXsimu.getVisual(id);
        return visual.shape;
        break;   
    }
  }

  group = {
    add: function (parent,children,align) {
      var agent,desc,node,pos
      agent=self.world.getAgentProcess(parent);
      // if (!self.options.patch) return;
      if (agent) {
        node = agent.node;
        pos = Comp.obj.copy(node.position);
        switch (align) {
          case Aios.DIR.NORTH: pos.y--; break;
          case Aios.DIR.SOUTH: pos.y++; break;
          case Aios.DIR.WEST : pos.x--; break;
          case Aios.DIR.EAST : pos.x++; break;
        }
        if (!self.checkBounds(pos.x,pos.y)) return;
        desc=self.model.agents[agent.agent.ac];
        if (desc.type != 'physical') return;
        if (!node.children) node.children=[];
        children.forEach(function (child) {
          var agent2 = self.world.getAgentProcess(child);
          if (agent2) {
            var desc2=self.model.agents[agent2.agent.ac];
            var node2 = agent2.node;
            if (desc2.type != 'physical') return;
            var pos2 = Comp.obj.copy(pos);
            node.children.push(node2);
            node2.parent = node;
            // move node container to group position
            delete self.agentMap[node2.position.y][node2.position.x][agent2.agent.id];
            node2.position = pos2;
            self.agentMap[pos2.y][pos2.x][agent2.agent.id]=node2.id;
            self.moveObjectTo('node['+node2.id+']',
                               self.world2draw(pos2).x,self.world2draw(pos2).y);
          }
         })
      }
    },
    rem: function (parent, children) {
      var agent,desc,node,pos
      agent=self.world.getAgentProcess(parent);
      if (agent) {
        node = agent.node;
        if (!node.children) return;
        children.forEach(function (child) {
          var agent2 = self.world.getAgentProcess(child);
          if (agent2) {
            var desc2=self.model.agents[agent2.agent.ac];
            var node2 = agent2.node;
            if (desc2.type != 'physical') return;
            var pos2 = Comp.obj.copy(pos);
            node.children=node.children.filter(function (_node) { return _node.id!=node2.id });
            node2.parent = null;
          }
         })
      }
    },
  }

  // TODO send net agents a signal
  function signal (destination,signal,argument) {
    // destination is agent id;
    // we need the node id, too
    var agent = self.world.getAgentProcess(destination);
    return agent?agent.node:'??'
  }
  
  function set(p,v) {
    var node=current.node,
        agent=current.process.agent,
        desc=self.model.agents[agent.ac],
        obj;
    if (!self.options.patch) return;
    switch (p) {
      case 'color':
        if  (desc.type == 'physical') {
          // Change color of node and agent
          id='node['+node.id+']';
          aiosXsimu.changeVisual(id,{fill:{color:v}});
          id='agent['+agent.ac+':'+agent.id+':'+node.id+']';
          aiosXsimu.changeVisual(id,{fill:{color:v}});
        } else {
          // Change color of agent
        }
        break;
      case 'shape':
        if  (desc.type == 'physical') {
          // Change shape of node
          id='node['+node.id+']';
          aiosXsimu.changeVisual(id,{shape:v,align:v=='circle'?'center':undefined});
        } else {
          // Change color of agent
        }
        break;
    }
  }

  // TODO resources, ..
  function setxy(x,y,what,who) {
    var type=what||'agent',
        desc,
        pos,
        node=current.node,
        agent=current.process.agent;
    // only discrete coordinates allowed (due to pos-map tables)
    x=x|0; y=y|0;
    if (!self.options.patch) return;
    // self.log([x,y,type,agent.ac])
    switch (type) {
      case 'agent':
      case 'agents':
        if (typeof who == 'string') {
          agent=aiosXnet.ask('agent',who);
          if (!agent) return;
        } else if (typeof who == 'object') {
          agent=who.agent;  // agent process!
          node=who.node;
        } 
        desc=self.model.agents[agent.ac];
        if (!desc) return;
        if (desc.type == 'physical') {
          // move agent and its node!
          if (!self.checkBounds(x,y)) return;
          pos={x:x,y:y};
          // checkBounds(x,y)
          if (node.position) {
            // Invalidate old worldmap entry
            delete self.agentMap[node.position.y][node.position.x][agent.id];
          }
          self.agentMap[y][x][agent.id]=node.id;
          self.moveObjectTo('node['+node.id+']',
                             self.world2draw(pos).x,self.world2draw(pos).y);
          node.position=pos;

          // Move child nodes, too (but not parent vice versa)!
          if (node.children) {
            node.children.forEach(function (node2) {
              node2.processes.table.forEach(function (agent2) {
                if (!agent2) return;
                if (self.model.agents[agent2.agent.ac] && 
                    self.model.agents[agent2.agent.ac].type != 'physical') return;
                if (node2.position) {
                  // Invalidate old worldmap entry
                  delete self.agentMap[node2.position.y][node2.position.x][agent2.agent.id];
                }
                self.agentMap[y][x][agent2.agent.id]=node2.id;
              })
              self.moveObjectTo('node['+node2.id+']',
                                 self.world2draw(pos).x,self.world2draw(pos).y);
              node2.position=pos;
            })
          }
        } else {
          // not supported!
        }
        break;
    }
  }
  // Turn agent or group of agents ...
  function turn(angle) {
    var node=current.node,_node,_pos,visual,agents,
        agent=current.process.agent, relative,
        desc=self.model.agents[agent.ac],id,
        pos=node.position,delta=0,Delta=[0,0];
    switch (angle) {
      case Aios.DIR.NORTH: angle=0;    break;
      case Aios.DIR.SOUTH: angle=180;  break;
      case Aios.DIR.WEST:  angle=270;  break;
      case Aios.DIR.EAST:  angle=90;   break;
      default:
        if (typeof angle == 'number') relative=true;
    }
    if (!self.options.patch || desc.type != 'physical') return;
    id='node['+node.id+']';
    visual = aiosXsimu.getVisual(id)
    if (!visual.heading) visual.heading=0;
    if (relative) angle=(visual.heading+angle) % 360;
    delta=angle-visual.heading;
    // translate group children?
    if (node.children && node.children.length) {
      _pos=node.children[0].position;
      // All children are overlayed !?
      Delta=[_pos.x-pos.x,_pos.y-pos.y]
      self.log({from:Delta, to:rotate(Delta,delta)})
      Delta=rotate(Delta,delta);
      node.children.forEach(function (child) {
        var x=pos.x+Delta[0],y=pos.y+Delta[1];
        if (!self.checkBounds(x,y)) return;       
        var _agent = child.getAgentProcess(0);
        aiosXnet.setxy(x,y,'agent',_agent)
      });
    }
    // update visual heading parameter  
    visual.heading = angle;
  }
  function within(x,y,bbox) {
    return  x >= bbox.x && 
            y >= bbox.y && 
            x < (bbox.x+bbox.w) &&
            y < (bbox.y+bbox.h)
  }
  return {
    ask:ask,
    create:create,
    die:die,
    forward:forward,
    globals:globals,
    get:get,
    group:group,
    set:set,
    setxy:setxy,
    signal:signal,
    turn:turn,
    within:within
  }

}

module.exports = aiosXnet
