<?php
// Vertically-balanced Tree Topology Generator
// Author: Miroslav Hajda hajdam@users.sf.net

include('../global.php');
global $Database, $debug;

if (array_key_exists('debug', $_GET)) $debug = $_GET['debug'];
else $debug = 0;
//$debug = 0;

class TreeItem {
  /** Index prvku v databazi */
  var $index = 0;
  /** Pozice prvku zleva */
  var $pos = 0;
  /** Pozice prvku zprava */
  var $rpos = 0;
  /** Pozice prvku shora */
  var $level = 0;
  /** Počet potomků s potomky */
  var $forkcnt = 0;
  /** Pole potomků */
  var $children = array();
  /** Předek */
  var $parentItem;
  /** Změna pořadí prvků */
  var $order = array();

  function getFirst() {
    if (count($this->children)>0) {
      if (count($this->order)>0) {
        if (count($this->children[0]->children)>0) return $this->order[$this->children[0]->index];
      }
      return $this->children[0];
    } else return null;
  }

  function getLast() {
    if (count($this->children)>0) {
      if (count($this->order)>0) {
        if (count($this->children[count($this->children)-1]->children)>0) return $this->order[$this->children[count($this->children)-1]->index];
      }
      return $this->children[count($this->children)-1];
    } else return null;
  }
}

class TreeGen {

  /** Kořenový uzel */
  var $rootNode;
  /** Hranice generátorů pozic */
  var $border = array();
  /** Maximální šířka hranice */
  var $maxborder = 0;
  /** Mód provedených operací */
  var $mode = 0;

  /** Vytvoří stromovou strukturu načtením záznamů z databáze */
  function populate() {
    global $Database;
    $TopHostName = 'nix-router';
    // rodičovský uzel
    $parentNode = null;
    // Aktuální uzel
    $currentNode = null;
    // Aktuální úroveň
    $level = 0;
    $this->border[0]=0;
    $mark = array();
    $markskip = 0;

    do {
      if ($level == 0) {
        $query = 'SELECT dev.id AS id FROM NetworkDevice dev WHERE dev.name = "'.$TopHostName.'" LIMIT 0,1';
      } else {
        $query = 'SELECT trg.device AS id FROM NetworkDevice dev, NetworkInterface ifc, NetworkLink lnk, NetworkInterface trg WHERE ifc.device = dev.id AND lnk.interface2 = ifc.id AND lnk.interface1 = trg.id AND dev.used=1 AND ';
        $query .= ' dev.id = '.$parentNode->index.' ORDER BY id';
        $query .= ' LIMIT '.(count($parentNode->children)+$markskip).',1';
      }
      $DbResult = $Database->query($query);
      $item = $DbResult->fetch_array();
      if ($item) {
//        echo $item['id'].','.$parentNode->index.','.$level.'<br/>';
//        flush();
        if (!isset($mark[$item['id']])) {
          $mark[$item['id']] = 1;
          $currentNode = new TreeItem();
          $currentNode->index = $item['id'];
          $currentNode->level = $level;
          $currentNode->parentItem = $parentNode;
          if ($level==0) {
            $this->rootNode = $currentNode;
          } else {
            if ($level>1 && count($parentNode->children)==0) $parentNode->parentItem->forkcnt++;
            array_push($parentNode->children,$currentNode);
          }
          $parentNode = $currentNode;
          $level++;
          $this->border[$level] = 0;
          $markskip = 0;
        } else {
          $markskip++;
        }
      } else {
        $currentNode = $parentNode;
        $parentNode = $currentNode->parentItem;
        $level--;
        $markskip = 0;
      }
    } while ($level >= 1);
  }

  function calc_pos($node) {
    if ($this->mode > 1) {
      return 1.5 + $this->maxborder-$node->rpos+$node->pos;
    } else return $node->pos;
  }

  /** Store specific tree node to DB */
  function store_node($node) {
      global $Database;
      $first = $node->getFirst();
      if ($first) { $first = $this->calc_pos($first); } else $first = '-1';
      $last = $node->getLast();
      if ($last) { $last = $this->calc_pos($last); } else $last = '-1';
      $Database->query("INSERT INTO NetworkTopology (Host, Depth, Pos, First, Last) '.
        'VALUES (".$node->index.','.$node->level.','.$this->calc_pos($node).','.$first.','.$last.");");
      foreach ($node->children as $key => $value) {
        $this->store_node($value);
      }
  }

  /** Store all nodes of tree to DB */
  function store() {
    global $Database;
    $Database->query('DELETE FROM NetworkTopology;');
    $this->store_node($this->rootNode);
  }

  /** Build most left tree on node and all subnodes on given border */
  function left_align($node) {
    $node->pos = $this->border[$node->level];
    $this->border[$node->level]++;
    if (count($node->children)>0) {
      if ($this->border[$node->level +1]>=$this->border[$node->level]) {
        $node->pos = $this->border[$node->level+1];
        $this->setborder($node->level, $this->border[$node->level+1]+1);
      }
      foreach ($node->children as $key => $value) {
        if ($key == count($node->children)-1) {
          if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
        }
        $this->left_align($value);
        if ($key == 0) {
          if ($this->border[$node->level] <= $this->border[$node->level+1]) {
            $node->pos = $this->border[$node->level+1]-1;
            $this->setborder($node->level, $this->border[$node->level+1]);
          }
        }
      }
    }
  }

  /** Build most right tree on node and all subnodes on given border */
  function right_align($node) {
    $this->mode = 2;
    $node->rpos = $this->border[$node->level];
    $this->border[$node->level]++;
    if (count($node->children)>0) {
      if ($this->border[$node->level +1]>=$this->border[$node->level]) {
        $node->rpos = $this->border[$node->level+1];
        $this->setborder($node->level, $this->border[$node->level+1]+1);
      }
      for ($key=count($node->children)-1;$key>=0;$key--) {
        $value = $node->children[$key];
        if ((count($value->children)>0) && count($node->order)>0) {
          $value = $node->order[$value->index];
        }
        if ($key == 0) {
          if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
        }
        $this->right_align($value);
        if ($key == count($node->children)-1) {
          if ($this->border[$node->level] <= $this->border[$node->level+1]) {
            $node->rpos = $this->border[$node->level+1]-1;
            $this->setborder($node->level, $this->border[$node->level+1]);
          }
        }
      }
    }
  }


  /** Reset construction border **/
  function reset_border() {
    foreach ($this->border as $key => $value) $this->border[$key] = 0;
    $this->maxborder = 0;
  }

  /** Heuristic reordering of fork nodes */
  function reorder_heur($node) {
    global $debug;
    $node->pos = $this->border[$node->level];
    $this->border[$node->level]++;
    $order = array(); // Seznam změněných indexů
    if ($node->forkcnt>1) {
      $preborder = $this->border;
      $premax = $this->maxborder;
      $bestmax = 0; // Nejmenší rozměr
      $bestfit = 0; // Index nejvhodnějšího kandidáta
      $forkmap = array(); // Seznam indexů vydličkových potomků
      $target = 0; // Index cílového uzlu
      $lastindex = 0; // Index poslední vydličky
      foreach ($node->children as $key => $value) {
        if (count($value->children)>0) {
          array_push($forkmap,$value);
          $order[$value->index]=0;
          $lastindex = $value->index;
        }
      }
      for ($i=0;$i<$node->forkcnt-1;$i++) {
        for ($j=0;$j<count($forkmap);$j++) {
          $this->border = $preborder;
          $this->maxborder = $premax;
          $k = 0; // index zpracovávané vydličky
          foreach ($node->children as $key => $value) {
            if (count($value->children)>0) {
              if ($order[$value->index]) {
                $value = $order[$value->index];
              } else {
                if ($k > 0) {
                  if ($k < $j) { $value = $forkmap[$k-1]; } else  $value = $forkmap[$k];
                } else {
                  $target = $value->index;
                  $value = $forkmap[$j];
                }
                $k++;
              }
            }
            if ($key == count($node->children)-1) {
              if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
            }
            $this->left_align($value);
            if ($key == 0) {
              if ($this->border[$node->level] <= $this->border[$node->level+1]) {
                $node->pos = $this->border[$node->level+1]-1;
                $this->setborder($node->level, $this->border[$node->level+1]);
              }
            }
          }
          if (($j==0) || ($this->maxborder < $bestmax)) {
            $bestmax = $this->maxborder;
            $bestfit = $j;
          }
        }
        $order[$target]=$forkmap[$bestfit];
        if ($debug) echo 'ORDER: '.$target.' > '.$forkmap[$bestfit]->index."\n";
        array_splice($forkmap, $bestfit, 1);
      }
      $order[$lastindex]=$forkmap[0];
      if ($debug) echo 'ORDER: '.$lastindex.' > '.$forkmap[0]->index."\n";
      if ($debug) echo 'ORDERSTORE: '.$node->index."\n";
      $node->order = $order;
      $this->border = $preborder;
      $this->maxborder = $premax;
    }
    if (count($node->children)>0) {
      if ($this->border[$node->level +1]>=$this->border[$node->level]) {
        $node->pos = $this->border[$node->level+1];
        $this->setborder($node->level, $this->border[$node->level+1]+1);
      }
      foreach ($node->children as $key => $value) {
        if ((count($value->children)>0) && count($order)>0) {
          $value = $order[$value->index];
        }
        if ($key == count($node->children)-1) {
          if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
        }
        $this->reorder($value);
        if ($key == 0) {
          if ($this->border[$node->level] <= $this->border[$node->level+1]) {
            $node->pos = $this->border[$node->level+1]-1;
            $this->setborder($node->level, $this->border[$node->level+1]);
          }
        }
      }
    }
  }

  /** Build most left tree on node and all subnodes on given border without leafs */
  function left_stub($node) {
    $node->pos = $this->border[$node->level];
    $this->border[$node->level]++;
    if (count($node->children)>0) {
      $stubborder = $this->border[$node->level +1] + count($node->children);
      if ($this->border[$node->level +1]>=$this->border[$node->level]) {
        $node->pos = $this->border[$node->level+1];
        $this->setborder($node->level, $this->border[$node->level+1]+1);
      }
      $forkcnt = 0; // Fork counter
      foreach ($node->children as $key => $value) {
        if ($forkcnt == count($node->children)-1) {
          if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
        }
        if (count($value->children)>0) {
          $this->left_stub($value);
          if ($forkcnt == 0) {
            if ($this->border[$node->level] <= $this->border[$node->level+1]) {
              $node->pos = $this->border[$node->level+1]-1;
              $this->setborder($node->level, $this->border[$node->level+1]);
            }
          }
          $forkcnt++;
        }
      }
      if ($stubborder > $this->border[$node->level +1]) $this->setborder($node->level+1,$stubborder);
    }
  }

  /** Full single level reordering */
  function reorder($node) {
    global $debug;
    $node->pos = $this->border[$node->level];
    $this->border[$node->level]++;
    $order = array(); // Seznam změněných indexů
    if ($node->forkcnt>1) {
      $preborder = $this->border;
      $premax = $this->maxborder;
      $bestmax = 0; // Nejmenší rozměr
      $bestfit = 0; // Index nejvhodnějšího kandidáta
      $forkmap = array(); // Seznam indexů vydličkových potomků
      $target = 0; // Index cílového uzlu
      $lastindex = 0; // Index poslední vydličky
      $fact = 1; // Faktoriál kombinací vydliček
      foreach ($node->children as $key => $value) {
        if (count($value->children)>0) {
          if ($key>0) $fact = $fact * ($key+1);
          array_push($forkmap,$value);
          $order[$value->index]=0;
          $lastindex = $value->index;
        }
      }
      for ($i=0;$i<$node->forkcnt-1;$i++) {
        for ($j=0;$j<count($forkmap);$j++) {
          $this->border = $preborder;
          $this->maxborder = $premax;
          $k = 0; // index zpracovávané vydličky
          foreach ($node->children as $key => $value) {
            if (count($value->children)>0) {
              if ($order[$value->index]) {
                $value = $order[$value->index];
              } else {
                if ($k > 0) {
                  if ($k < $j) { $value = $forkmap[$k-1]; } else  $value = $forkmap[$k];
                } else {
                  $target = $value->index;
                  $value = $forkmap[$j];
                }
                $k++;
              }
            }
            if ($key == count($node->children)-1) {
              if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
            }
            $this->left_align($value);
            if ($key == 0) {
              if ($this->border[$node->level] <= $this->border[$node->level+1]) {
                $node->pos = $this->border[$node->level+1]-1;
                $this->setborder($node->level, $this->border[$node->level+1]);
              }
            }
          }
          if (($j==0) || ($this->maxborder < $bestmax)) {
            $bestmax = $this->maxborder;
            $bestfit = $j;
          }
        }
        $order[$target]=$forkmap[$bestfit];
        if ($debug) echo 'ORDER: '.$target.' > '.$forkmap[$bestfit]->index."\n";
        array_splice($forkmap, $bestfit, 1);
      }
      $order[$lastindex]=$forkmap[0];
      if ($debug) echo 'ORDER: '.$lastindex.' > '.$forkmap[0]->index."\n";
      if ($debug) echo 'ORDERSTORE: '.$node->index."\n";
      $node->order = $order;
      $this->border = $preborder;
      $this->maxborder = $premax;
    }
    // Missing stub stuffing
    if (count($node->children)>0) {
      if ($this->border[$node->level +1]>=$this->border[$node->level]) {
        $node->pos = $this->border[$node->level+1];
        $this->setborder($node->level, $this->border[$node->level+1]+1);
      }
      foreach ($node->children as $key => $value) {
        if ((count($value->children)>0) && count($order)>0) {
          $value = $order[$value->index];
        }
        if ($key == count($node->children)-1) {
          if ($this->border[$node->level] > $this->border[$node->level+1]) $this->setborder($node->level+1, $this->border[$node->level]-1);
        }
        $this->reorder($value);
        if ($key == 0) {
          if ($this->border[$node->level] <= $this->border[$node->level+1]) {
            $node->pos = $this->border[$node->level+1]-1;
            $this->setborder($node->level, $this->border[$node->level+1]);
          }
        }
      }
    }
  }

  function setborder($level, $value) {
    if ($value > $this->maxborder) $this->maxborder = $value;
    $this->border[$level] = $value;
  }
}

// Generator Main Procedure
$mytree = new TreeGen();
$mytree->populate();
$mytree->left_align($mytree->rootNode);
//$mytree->reorder($mytree->rootNode);
//$mytree->left_stub($mytree->rootNode);
$mytree->reset_border();
$mytree->right_align($mytree->rootNode);
$mytree->store();

echo('Topologie byla vygenerovana.');
