/*
 * Copyright © 2009 CNRS
 * Copyright © 2009-2020 Inria.  All rights reserved.
 * Copyright © 2009-2013, 2015 Université Bordeaux
 * Copyright © 2009-2011 Cisco Systems, Inc.  All rights reserved.
 * See COPYING in top-level directory.
 */

#include "private/autogen/config.h"
#include "hwloc.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <time.h>

#include "lstopo.h"

#define EPOXY_R_COLOR 0xe7
#define EPOXY_G_COLOR 0xff
#define EPOXY_B_COLOR 0xb5

#define DARK_EPOXY_R_COLOR ((EPOXY_R_COLOR * 100) / 110)
#define DARK_EPOXY_G_COLOR ((EPOXY_G_COLOR * 100) / 110)
#define DARK_EPOXY_B_COLOR ((EPOXY_B_COLOR * 100) / 110)

#define DARKER_EPOXY_R_COLOR ((DARK_EPOXY_R_COLOR * 100) / 110)
#define DARKER_EPOXY_G_COLOR ((DARK_EPOXY_G_COLOR * 100) / 110)
#define DARKER_EPOXY_B_COLOR ((DARK_EPOXY_B_COLOR * 100) / 110)

#ifdef HWLOC_HAVE_GCC_W_MISSING_FIELD_INITIALIZERS
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
/* each of these colors must be declared in declare_colors() */
struct lstopo_color BLACK_COLOR = { 0, 0, 0, 0 };
struct lstopo_color WHITE_COLOR = { 0xff, 0xff, 0xff, 0 };
struct lstopo_color PACKAGE_COLOR = { DARK_EPOXY_R_COLOR, DARK_EPOXY_G_COLOR, DARK_EPOXY_B_COLOR, 0 };
struct lstopo_color DIE_COLOR = { EPOXY_R_COLOR, EPOXY_G_COLOR, EPOXY_B_COLOR, 0 };
struct lstopo_color MEMORY_COLOR = { 0xef, 0xdf, 0xde, 0 };
struct lstopo_color MEMORIES_COLOR = { 0xf2, 0xe8, 0xe8, 0}; /* slightly lighter than MEMORY_COLOR */
struct lstopo_color CORE_COLOR = { 0xbe, 0xbe, 0xbe, 0 };
struct lstopo_color THREAD_COLOR = { 0xff, 0xff, 0xff, 0 };
struct lstopo_color BINDING_COLOR = { 0, 0xff, 0, 0 };
struct lstopo_color DISALLOWED_COLOR = { 0xff, 0, 0, 0 };
struct lstopo_color CACHE_COLOR = { 0xff, 0xff, 0xff, 0 };
struct lstopo_color MACHINE_COLOR = { 0xff, 0xff, 0xff, 0 };
struct lstopo_color GROUP_IN_PACKAGE_COLOR = { EPOXY_R_COLOR, EPOXY_G_COLOR, EPOXY_B_COLOR, 0 };
struct lstopo_color MISC_COLOR = { 0xff, 0xff, 0xff, 0 };
struct lstopo_color PCI_DEVICE_COLOR = { DARKER_EPOXY_R_COLOR, DARKER_EPOXY_G_COLOR, DARKER_EPOXY_B_COLOR, 0 };
struct lstopo_color OS_DEVICE_COLOR = { 0xde, 0xde, 0xde, 0 };
struct lstopo_color BRIDGE_COLOR = { 0xff, 0xff, 0xff, 0 };
#ifdef HWLOC_HAVE_GCC_W_MISSING_FIELD_INITIALIZERS
#pragma GCC diagnostic warning "-Wmissing-field-initializers"
#endif

static struct lstopo_color *colors = NULL;

static struct lstopo_color *
declare_color(struct lstopo_output *loutput, struct lstopo_color *color)
{
  memset(&color->private, 0, sizeof(color->private));

  /* call the backend callback if any */
  if (loutput->methods->declare_color) {
    int ret = loutput->methods->declare_color(loutput, color);
    if (ret < 0)
      return NULL;
  }

  /* insert */
  color->next = colors;
  colors = color;

  return color;
}

void
declare_colors(struct lstopo_output *output)
{
  /* don't bother looking for duplicate colors here,
   * we want to be able to use those structs so always queue them
   */
  declare_color(output, &BLACK_COLOR);
  declare_color(output, &WHITE_COLOR);
  declare_color(output, &PACKAGE_COLOR);
  declare_color(output, &DIE_COLOR);
  declare_color(output, &MEMORY_COLOR);
  declare_color(output, &MEMORIES_COLOR);
  declare_color(output, &CORE_COLOR);
  declare_color(output, &THREAD_COLOR);
  declare_color(output, &BINDING_COLOR);
  declare_color(output, &DISALLOWED_COLOR);
  declare_color(output, &CACHE_COLOR);
  declare_color(output, &MACHINE_COLOR);
  declare_color(output, &GROUP_IN_PACKAGE_COLOR);
  declare_color(output, &MISC_COLOR);
  declare_color(output, &PCI_DEVICE_COLOR);
  declare_color(output, &OS_DEVICE_COLOR);
  declare_color(output, &BRIDGE_COLOR);
}

void
destroy_colors(void)
{
  struct lstopo_color *tmp = colors;

  while (tmp) {
    struct lstopo_color *next = tmp->next;

    if (tmp->free)
      free(tmp);
    tmp = next;
  }
}

static struct lstopo_color *
find_or_declare_rgb_color(struct lstopo_output *loutput, int r, int g, int b)
{
  struct lstopo_color *color, *tmp;

  for(tmp = colors; tmp; tmp = tmp->next)
    if (tmp->r == r && tmp->g == g && tmp->b == b)
      return tmp;

  color = malloc(sizeof(*color));
  if (!color)
    return NULL;

  color->r = r & 255;
  color->g = g & 255;
  color->b = b & 255;
  color->free = 1;
  tmp = declare_color(loutput, color);
  if (!tmp)
    free(color);
  return tmp;
}

static unsigned
get_textwidth(void *output,
	      const char *text, unsigned length,
	      unsigned fontsize)
{
  struct lstopo_output *loutput = output;
  unsigned width;

#ifdef HWLOC_DEBUG
  assert(loutput->methods->textsize);
#endif
  loutput->methods->textsize(output, text, length, fontsize, &width);
  width = loutput->text_xscale * ((float)width);
  return width;
}

/*
 * foo_draw functions take a OBJ, computes which size it needs, recurse into
 * sublevels with drawing=PREPARE to recursively compute the needed size
 * without actually drawing anything, then draw things about OBJ (chip draw,
 * cache size information etc) at (X,Y), recurse into sublevels again to
 * actually draw things, and return in RETWIDTH and RETHEIGHT the amount of
 * space that the drawing took.
 *
 * For generic detailed comments, see the node_draw function.
 *
 * border is added around the objects
 * separator is added between objects
 */

typedef void (*foo_draw)(struct lstopo_output *loutput, hwloc_obj_t obj, unsigned depth, unsigned x, unsigned y);

static foo_draw get_type_fun(hwloc_obj_type_t type);

/* next child, in all children list, with memory before CPU, ignoring PU if needed.
 * similar to hwloc_get_next_child() but returns memory children first.
 */
#define NEXT_CHILD_INIT_STATE -1
static hwloc_obj_t next_child(struct lstopo_output *loutput, hwloc_obj_t parent, unsigned kind, hwloc_obj_t prev, int *statep)
{
  int state;
  hwloc_obj_t obj;
  if (prev) {
    obj = prev->next_sibling;
    state = *statep;
  } else {
    obj = NULL;
    state = NEXT_CHILD_INIT_STATE;
  }

 again:
  if (!obj && state <= -1 && (kind & LSTOPO_CHILD_KIND_MEMORY)) {
    obj = parent->memory_first_child;
    state = 0;
  }
  if (!obj && state <= 0 && (kind & LSTOPO_CHILD_KIND_NORMAL)) {
    obj = parent->first_child;
    state = 1;
  }
  if (!obj && state <= 1 && (kind & LSTOPO_CHILD_KIND_IO)) {
    obj = parent->io_first_child;
    state = 2;
  }
  if (!obj && state <= 2 && (kind & LSTOPO_CHILD_KIND_MISC)) {
    obj = parent->misc_first_child;
    state = 3;
  }
  if (!obj)
    return NULL;

  if (loutput->factorize_enabled && parent->arity > loutput->factorize_min[obj->type]) {
    if (((struct lstopo_obj_userdata *)obj->userdata)->factorized < 0) {
      obj = obj->next_sibling;
      goto again;
    }
  }

  if (obj->type == HWLOC_OBJ_PU && loutput->ignore_pus) {
    obj = obj->next_sibling;
    goto again;
  }
  if (obj->type == HWLOC_OBJ_NUMANODE && loutput->ignore_numanodes) {
    obj = obj->next_sibling;
    goto again;
  }
  if (loutput->pci_collapse_enabled && obj->type == HWLOC_OBJ_PCI_DEVICE) {
    struct lstopo_obj_userdata *lud = obj->userdata;
    if (lud->pci_collapsed == -1) {
      obj = obj->next_sibling;
      goto again;
    }
  }
  *statep = state;
  return obj;
}

static float pci_link_speed(hwloc_obj_t obj)
{
  if (obj->type == HWLOC_OBJ_PCI_DEVICE)
    return obj->attr->pcidev.linkspeed;
  if (obj->type == HWLOC_OBJ_BRIDGE && obj->attr->bridge.upstream_type == HWLOC_OBJ_BRIDGE_PCI)
    return obj->attr->bridge.upstream.pci.linkspeed;
  return 0.;
}

/********************************
 * Placing children in rectangle
 */

/* preferred width/height compromise */
#define RATIO (4.f/3.f)

/* returns a score <= 1. close to 1 is better */
static __hwloc_inline
float rectangle_score(unsigned width, unsigned height, float ratio)
{
  float score = ((float) width)/height/ratio;
  if (score > 1)
    score = 1/score;
  return score;
}

static void find_children_rectangle(struct lstopo_output *loutput, hwloc_obj_t parent,
				    unsigned kind, unsigned separator,
				    unsigned *rowsp, unsigned *columnsp,
				    float ratio)
{
  unsigned rows = 0, columns = 0, n;
  unsigned numsubobjs = 0, obj_totwidth = 0, obj_totheight = 0;
  unsigned obj_avgwidth, obj_avgheight;
  unsigned area = 0;
  float idealtotheight, under_score, over_score, best_score = 0.f;
  hwloc_obj_t child;
  int ncstate;

  /* Total area for subobjects */
  child = NULL;
  while ((child=next_child(loutput, parent, kind, child, &ncstate)) != NULL) {
    struct lstopo_obj_userdata *clud = child->userdata;
    numsubobjs++;
    obj_totwidth += clud->width + separator;
    obj_totheight += clud->height + separator;
    area += (clud->width + separator) * (clud->height + separator);
  }

  /* Average object size */
  obj_avgwidth = obj_totwidth / numsubobjs;
  obj_avgheight = obj_totheight / numsubobjs;

  /* Try to find a rectangle fitting exactly */
  for (n = (unsigned) (float) floor(sqrt(numsubobjs));
       rows >= (unsigned) (float) ceil(pow(numsubobjs, 0.33)) && n > 1;
       n--) {
    float old_best_score = best_score;
    unsigned p = numsubobjs / n;
    float np_score; /* n rows x p columns */
    float pn_score; /* p rows x n columns */
    if (p <= 1 || p * n != numsubobjs)
      continue;
    /* Try both n*p and n*p rectangles */
    np_score = rectangle_score(p * obj_avgwidth, obj_avgheight * n, ratio);
    pn_score = rectangle_score(n * obj_avgwidth, obj_avgheight * p, ratio);
    if (np_score > pn_score) {
      if (np_score > best_score) {
	rows = n;
	columns = p;
	best_score = np_score;
      }
    } else {
      if (pn_score > best_score) {
	rows = p;
	columns = n;
	best_score = pn_score;
      }
    }
    if (old_best_score == best_score)
      /* Score didn't improve, no need to try further.
       * The graph score(n) is an inverted U, with np_score on the left and pn_score on the right.
       * Se start somewhere in the middle and walk to both ends simultaneously,
       * Only one side can improve towards the maximum, we stop once once it stops improving.
       * The other side can just decrease for ever.
       */
      goto done;
  }
  if (best_score)
    /* We got a rectangle fitting exactly, it may not be perfect but it's good looking */
    goto done;

  /* Try to find a rectangle with an incomplete last row */

  /* Ideal total height for spreading that area with ratio */
  idealtotheight = (float) sqrt(area/ratio);
  /* approximation of number of rows */
  rows = (unsigned) (idealtotheight / obj_avgheight);
  columns = rows ? (numsubobjs + rows - 1) / rows : 1;
  /* Score obtained by underestimation */
  under_score = rectangle_score(columns * obj_avgwidth, rows * obj_avgheight, ratio);
  /* try to overestimate too */
  rows++;
  columns = (numsubobjs + rows - 1) / rows;
  /* Score obtained by overestimation */
  over_score = rectangle_score(columns * obj_avgwidth, rows * obj_avgheight, ratio);
  /* Revert back to under estimation if it was better */
  if (rows > 1 && under_score > over_score) {
    rows--;
    columns = (numsubobjs + rows - 1) / rows;
  }

 done:
  *rowsp = rows;
  *columnsp = columns;
}

/**************************
 * Placing children
 */

static void
place_children_horiz(struct lstopo_output *loutput, hwloc_obj_t parent,
		     unsigned kind, unsigned border, unsigned separator,
		     unsigned *width, unsigned *height)
{
  unsigned curx = 0;
  unsigned maxh = 0;
  hwloc_obj_t child;
  int ncstate;
  for(child = next_child(loutput, parent, kind, NULL, &ncstate);
      child;
      child = next_child(loutput, parent, kind, child, &ncstate)) {
    struct lstopo_obj_userdata *clud = child->userdata;
    clud->xrel = curx + border;
    clud->yrel = border;
    if (clud->height > maxh)
      maxh = clud->height;
    curx += separator + clud->width;
  }
  *width = curx - separator + 2*border;
  *height = maxh + 2*border;
}

/* bridge object height: small empty box */
#define BRIDGE_HEIGHT (gridsize)

static void
place_children_vert(struct lstopo_output *loutput, hwloc_obj_t parent,
		    unsigned kind, unsigned border, unsigned separator,
		    unsigned *width, unsigned *height)
{
  unsigned cury = 0;
  unsigned maxw = 0;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  int bridge_parent_with_pcilinkspeed = parent->type == HWLOC_OBJ_BRIDGE
    && loutput->show_text_enabled && loutput->show_text[HWLOC_OBJ_BRIDGE];
  hwloc_obj_t child;
  int ncstate;
  for(child = next_child(loutput, parent, kind, NULL, &ncstate);
      child;
      child = next_child(loutput, parent, kind, child, &ncstate)) {
    struct lstopo_obj_userdata *clud = child->userdata;
    unsigned child_height = clud->height;
    clud->xrel = border;
    clud->yrel = cury + border;
    if (clud->width > maxw)
      maxw = clud->width;

    if (bridge_parent_with_pcilinkspeed && pci_link_speed(child) != 0.)
      /* make sure padding between children is enough to display pci link speed */
      if (child_height <= BRIDGE_HEIGHT + fontsize)
	child_height = BRIDGE_HEIGHT + fontsize;

    cury += separator + child_height;
  }
  *width = maxw + 2*border;
  *height = cury - separator + 2*border;
}

static void
place_children_rect(struct lstopo_output *loutput, hwloc_obj_t parent,
		    unsigned kind, unsigned border, unsigned separator,
		    unsigned *width, unsigned *height)
{
  unsigned rows, columns;
  unsigned totwidth, totheight; /* total children array size, without borders */
  unsigned rowwidth; /* current row width */
  unsigned maxheight; /* max height for current row */
  hwloc_obj_t child;
  int ncstate;
  float ratio;
  int i;

  if (parent->type == HWLOC_OBJ_CORE)
    ratio = 1/RATIO;
  else
    ratio = RATIO;
  find_children_rectangle(loutput, parent, kind, separator, &rows, &columns, ratio);

  rowwidth = 0;
  maxheight = 0;
  totwidth = 0;
  totheight = 0;
  for(i = 0, child = next_child(loutput, parent, kind, NULL, &ncstate);
      child;
      i++, child = next_child(loutput, parent, kind, child, &ncstate)) {
    struct lstopo_obj_userdata *clud = child->userdata;
    /* Newline? */
    if (i && i%columns == 0) {
      /* Update total width using new row */
      if (rowwidth > totwidth)
	totwidth = rowwidth;
      rowwidth = 0;
      /* Update total height */
      totheight += maxheight + separator;
      maxheight = 0;
    }
    /* Add new child */
    clud->xrel = rowwidth + border;
    clud->yrel = totheight + border;
    rowwidth += clud->width + separator;
    if (clud->height > maxheight)
      maxheight = clud->height;
  }
  /* Update total width using last row */
  if (rowwidth > totwidth)
    totwidth = rowwidth;
  /* Remove spurious separator on the right */
  totwidth -= separator;
  /* Update total height using last row */
  totheight += maxheight; /* no separator */

  *width = totwidth + 2*border;
  *height = totheight + 2*border;
}

static void
place__children(struct lstopo_output *loutput, hwloc_obj_t parent,
		unsigned kind,
		enum lstopo_orient_e *orientp,
		unsigned border, unsigned separator,
		unsigned *widthp, unsigned *heightp)
{
  if (*orientp == LSTOPO_ORIENT_HORIZ) {
    /* force horizontal */
    place_children_horiz(loutput, parent, kind, border, separator, widthp, heightp);

  } else if (*orientp == LSTOPO_ORIENT_VERT) {
    /* force vertical */
    place_children_vert(loutput, parent, kind, border, separator, widthp, heightp);

  } else {
    /* NONE or forced RECT, do a rectangular placement */
    place_children_rect(loutput, parent, kind, border, separator, widthp, heightp);
  }
}

/* Recurse into children to get their size.
 * Place them.
 * Save their position and the parent total size for later.
 */
static void
place_children(struct lstopo_output *loutput, hwloc_obj_t parent,
	       unsigned xrel, unsigned yrel /* position of children within parent */)
{
  struct lstopo_obj_userdata *plud = parent->userdata;
  enum lstopo_orient_e orient = loutput->force_orient[parent->type];
  unsigned border = loutput->gridsize;
  unsigned separator = loutput->gridsize;
  unsigned separator_below_cache = loutput->gridsize;
  unsigned normal_children_separator = loutput->gridsize;
  unsigned totwidth = plud->width, totheight = plud->height;
  unsigned children_width = 0, children_height = 0;
  unsigned above_children_width, above_children_height;
  unsigned existing_kinds;
  int normal_children_are_PUs;
  hwloc_obj_t child;
  int ncstate;
  unsigned i;

  /* defaults */
  plud->children.box = 0;
  plud->above_children.box = 0;

  /* list the kinds of children that exist in that parent */
  existing_kinds = (parent->arity ? LSTOPO_CHILD_KIND_NORMAL : 0)
    | (parent->memory_arity ? LSTOPO_CHILD_KIND_MEMORY : 0)
    | (parent->io_arity ? LSTOPO_CHILD_KIND_IO : 0)
    | (parent->misc_arity ? LSTOPO_CHILD_KIND_MISC : 0);
  /* now assign them below or above the parent */
  if (loutput->plain_children_order || hwloc_obj_type_is_memory(parent->type)) {
    plud->children.kinds = existing_kinds;
    plud->above_children.kinds = 0;
  } else {
    plud->children.kinds = existing_kinds & ~LSTOPO_CHILD_KIND_MEMORY;
    plud->above_children.kinds = existing_kinds & LSTOPO_CHILD_KIND_MEMORY;
  }

  /* bridge children always vertical */
  if (parent->type == HWLOC_OBJ_BRIDGE)
    orient = LSTOPO_ORIENT_VERT;

  /* if factorizing children, use horizontal by default */
  if (orient == LSTOPO_ORIENT_NONE
      && parent->symmetric_subtree
      && parent->first_child
      && loutput->factorize_enabled
      && parent->arity > loutput->factorize_min[parent->first_child->type]) {
    orient = LSTOPO_ORIENT_HORIZ;
  }

  /* if there are memory children and using plain children layout, use horizontal by default */
  if (orient == LSTOPO_ORIENT_NONE
      && parent->memory_arity
      && loutput->plain_children_order)
    orient = LSTOPO_ORIENT_HORIZ;

  /* recurse into children to prepare their sizes,
   * and check whether all normal children are PUs. */
  normal_children_are_PUs = (parent->arity > 0);
  for(i = 0, child = next_child(loutput, parent, LSTOPO_CHILD_KIND_ALL, NULL, &ncstate);
      child;
      i++, child = next_child(loutput, parent, LSTOPO_CHILD_KIND_ALL, child, &ncstate)) {
    get_type_fun(child->type)(loutput, child, 0, 0, 0);
    if (hwloc_obj_type_is_normal(child->type) && child->type != HWLOC_OBJ_PU)
      normal_children_are_PUs = 0;
  }
  if (!i)
    return;


  /* no separator between PUs */
  if (normal_children_are_PUs)
    normal_children_separator = 0;

  /* add separator between a cache parent and its children */
  if (hwloc_obj_type_is_cache(parent->type) || parent->type == HWLOC_OBJ_MEMCACHE) {
    if (normal_children_are_PUs || parent->type == HWLOC_OBJ_MEMCACHE)
      /* except between cache parent and PU children */
      separator_below_cache = 0;
    /* update children placement */
    yrel += separator_below_cache;
  }

  /* place children below the parent */
  if (plud->children.kinds)
    place__children(loutput, parent, plud->children.kinds, &orient, 0, normal_children_separator, &children_width, &children_height);

  /* place children above the parent, if any*/
  if (plud->above_children.kinds) {
    enum lstopo_orient_e morient = LSTOPO_ORIENT_HORIZ;
    int need_box;

    assert(plud->above_children.kinds == LSTOPO_CHILD_KIND_MEMORY);

    /* we need a memory children box if parent isn't a memory object
     * and if there are multiple objects in the box
     */
    need_box = !hwloc_obj_type_is_memory(parent->type)
      && (parent->memory_arity + parent->memory_first_child->memory_arity > 1);

    place__children(loutput, parent, plud->above_children.kinds, &morient, need_box ? border : 0, separator, &above_children_width, &above_children_height);
    if (parent->type == HWLOC_OBJ_MEMCACHE)
      above_children_height -= separator;

    if (need_box) {
      /* if there are multiple memory children, add a box, as large as the parent */
      if (above_children_width < children_width) {
	above_children_width = children_width;
      }
      plud->above_children.boxcolor = &MEMORIES_COLOR;
      plud->above_children.box = 1;

    } else {
      /* if there's a single memory child without wide memory box, enlarge that child */
      struct lstopo_obj_userdata *clud = parent->memory_first_child->userdata;
      if (clud->width < children_width) {
	clud->width = children_width;
	above_children_width = children_width;
      }
    }
  }

  /* adjust parent size */
  if (hwloc_obj_type_is_cache(parent->type) || parent->type == HWLOC_OBJ_MEMCACHE) {
    /* cache children are below */
    if (children_width > totwidth)
      totwidth = children_width;
    if (children_height)
      totheight += children_height + separator_below_cache;
    if (plud->above_children.kinds) {
      totheight += above_children_height + separator;
      if (above_children_width > totwidth)
	totwidth = above_children_width;
    }
  } else if (parent->type == HWLOC_OBJ_BRIDGE) {
    /* bridge children are on the right, within any space between bridge and children */
    if (children_width)
      totwidth += children_width;
    if (children_height > totheight)
      totheight = children_height;
  } else {
    /* normal objects have children inside their box, with space around them */
    if (children_width + 2*border > totwidth)
      totwidth = children_width + 2*border;
    if (children_height)
      totheight += children_height + border;
    if (plud->above_children.kinds) {
      totheight += above_children_height + separator;
      if (above_children_width + 2*border > totwidth)
	totwidth = above_children_width + 2*border;
    }
  }

  /* save config for draw_children() later */
  plud->orient = orient;
  plud->width = totwidth;
  plud->height = totheight;
  plud->children.width = children_width;
  plud->children.height = children_height;
  plud->children.xrel = xrel;
  plud->children.yrel = yrel;
  if (plud->above_children.kinds) {
    plud->above_children.width = above_children_width;
    plud->above_children.height = above_children_height;
    plud->above_children.xrel = xrel;
    plud->above_children.yrel = yrel;
    plud->children.yrel += above_children_height + separator;
  }
}

/***********************
 * Drawing children
 */

static void
draw__children(struct lstopo_output *loutput, hwloc_obj_t parent,
	       struct lstopo_children_position *children,
	       unsigned depth,
	       unsigned x, unsigned y)
{
  hwloc_obj_t child;
  int ncstate;

  if (children->box)
    loutput->methods->box(loutput, children->boxcolor, depth, x, children->width, y, children->height, parent, 1);

  for(child = next_child(loutput, parent, children->kinds, NULL, &ncstate);
      child;
      child = next_child(loutput, parent, children->kinds, child, &ncstate)) {
      struct lstopo_obj_userdata *clud = child->userdata;
      get_type_fun(child->type)(loutput, child, depth-1, x + clud->xrel, y + clud->yrel);
    }
}

static void
draw_children(struct lstopo_output *loutput, hwloc_obj_t parent, unsigned depth,
	      unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *plud = parent->userdata;

  if (plud->children.kinds)
    draw__children(loutput, parent, &plud->children, depth, x + plud->children.xrel, y + plud->children.yrel);

  if (plud->above_children.kinds)
    draw__children(loutput, parent, &plud->above_children, depth, x + plud->above_children.xrel, y + plud->above_children.yrel);
}

/*******
 * Misc
 */

static int
lstopo_obj_snprintf(struct lstopo_output *loutput, char *text, size_t textlen, hwloc_obj_t obj)
{
  enum lstopo_index_type_e index_type = loutput->index_type;
  unsigned idx;
  const char *indexprefix;
  char typestr[32];
  char indexstr[32]= "";
  char index2str[32] = "";
  char attrstr[256];
  char totmemstr[64] = "";
  int attrlen;

  /* For Misc and Group, name replaces type+index+attrs */
  if (obj->name && (obj->type == HWLOC_OBJ_MISC || obj->type == HWLOC_OBJ_GROUP)) {
    return snprintf(text, textlen, "%s", obj->name);
  }
  /* For OSDev, OSDev-type+name replaces type+index+attrs */
  if (obj->type == HWLOC_OBJ_OS_DEVICE) {
    /* consider the name as an index and remove it if LSTOPO_INDEX_TYPE_NONE */
    if (index_type != LSTOPO_INDEX_TYPE_NONE) {
      hwloc_obj_type_snprintf(typestr, sizeof(typestr), obj, 0);
      return snprintf(text, textlen, "%s %s", typestr, obj->name);
    } else {
      return hwloc_obj_type_snprintf(text, textlen, obj, 0);
    }
  }

  /* subtype replaces the basic type name */
  if (obj->subtype) {
    snprintf(typestr, sizeof(typestr), "%s", obj->subtype);
  } else {
    hwloc_obj_type_snprintf(typestr, sizeof(typestr), obj, 0);
  }

  if (index_type == LSTOPO_INDEX_TYPE_DEFAULT) {
    if (obj->type == HWLOC_OBJ_PU || obj->type == HWLOC_OBJ_NUMANODE) {
      /* by default we show logical+physical for PU/NUMA */
      idx = obj->logical_index;
      indexprefix = " L#";
    } else if (obj->type == HWLOC_OBJ_PACKAGE || obj->type == HWLOC_OBJ_DIE || obj->type == HWLOC_OBJ_CORE) {
      /* logical only for package+core (so that we see easily how many packages/cores there are */
      idx = obj->logical_index;
      indexprefix = " L#";
    } else {
      /* nothing for others */
      idx = HWLOC_UNKNOWN_INDEX;
      indexprefix = "";
    }
  } else if (index_type == LSTOPO_INDEX_TYPE_LOGICAL) {
    idx = obj->logical_index;
    indexprefix = " L#";
  } else if (index_type == LSTOPO_INDEX_TYPE_PHYSICAL) {
    idx = obj->os_index;
    indexprefix = " P#";
  } else {
    /* shutup the compiler */
    idx = 0;
    indexprefix = "";
  }

  if (loutput->show_indexes[obj->type]
      && index_type != LSTOPO_INDEX_TYPE_NONE
      && idx != (unsigned)-1 && obj->depth != 0
      && obj->type != HWLOC_OBJ_PCI_DEVICE
      && (obj->type != HWLOC_OBJ_BRIDGE || obj->attr->bridge.upstream_type == HWLOC_OBJ_BRIDGE_HOST))
    snprintf(indexstr, sizeof(indexstr), "%s%u", indexprefix, idx);

  if (index_type == LSTOPO_INDEX_TYPE_DEFAULT && obj->type == HWLOC_OBJ_NUMANODE && loutput->show_indexes[obj->type])
    /* NUMA have both P# and L# on the same line (PU is split on 2 lines) */
    snprintf(index2str, sizeof(index2str), " P#%u", obj->os_index);

  if (loutput->show_attrs_enabled && loutput->show_attrs[obj->type]) {
    attrlen = hwloc_obj_attr_snprintf(attrstr, sizeof(attrstr), obj, " ", 0);
    /* display the root total_memory (cannot be local_memory since root cannot be a NUMA node) */
    if (!obj->parent && obj->total_memory)
      snprintf(totmemstr, sizeof(totmemstr), " (%lu%s total)",
	       (unsigned long) hwloc_memory_size_printf_value(obj->total_memory, 0),
	       hwloc_memory_size_printf_unit(obj->total_memory, 0));
  } else
    attrlen = 0;

  if (attrlen > 0)
    return snprintf(text, textlen, "%s%s%s (%s)%s", typestr, indexstr, index2str, attrstr, totmemstr);
  else
    return snprintf(text, textlen, "%s%s%s%s", typestr, indexstr, index2str, totmemstr);
}

static void
lstopo__prepare_custom_styles(struct lstopo_output *loutput, hwloc_obj_t obj)
{
  struct lstopo_obj_userdata *lud = obj->userdata;
  struct lstopo_style *s = &lud->style;
  hwloc_obj_t child;
  unsigned forcer, forceg, forceb;
  const char *stylestr;
  struct lstopo_color *lcolor;

  lud->style_set = 0;

  stylestr = hwloc_obj_get_info_by_name(obj, "lstopoStyle");
  if (stylestr) {
    while (*stylestr != '\0') {
      if (sscanf(stylestr, "%02x%02x%02x", &forcer, &forceg, &forceb) == 3
	  || sscanf(stylestr, "Background=#%02x%02x%02x", &forcer, &forceg, &forceb) == 3) {
	lcolor = find_or_declare_rgb_color(loutput, forcer, forceg, forceb);
	if (lcolor) {
	  s->bg = lcolor;
	  lud->style_set |= LSTOPO_STYLE_BG;
	  if (!(lud->style_set & LSTOPO_STYLE_T)) {
	    s->t = (lcolor->r + lcolor->g + lcolor->b < 0xff) ? &WHITE_COLOR : &BLACK_COLOR;
	    lud->style_set |= LSTOPO_STYLE_T;
	  }
	}
      } else if (sscanf(stylestr, "Text=#%02x%02x%02x", &forcer, &forceg, &forceb) == 3) {
	lcolor = find_or_declare_rgb_color(loutput, forcer, forceg, forceb);
	if (lcolor) {
	  s->t = lcolor;
	  lud->style_set |= LSTOPO_STYLE_T;
	}
      } else if (sscanf(stylestr, "Text2=#%02x%02x%02x", &forcer, &forceg, &forceb) == 3) {
	lcolor = find_or_declare_rgb_color(loutput, forcer, forceg, forceb);
	if (lcolor) {
	  s->t2 = lcolor;
	  lud->style_set |= LSTOPO_STYLE_T2;
	}
      }
      stylestr = strchr(stylestr, ';');
      if (!stylestr)
	break;
      stylestr++;
    }
  }

  for_each_child(child, obj)
    lstopo__prepare_custom_styles(loutput, child);
  for_each_memory_child(child, obj)
    lstopo__prepare_custom_styles(loutput, child);
  for_each_io_child(child, obj)
    lstopo__prepare_custom_styles(loutput, child);
  for_each_misc_child(child, obj)
    lstopo__prepare_custom_styles(loutput, child);
}

void
lstopo_prepare_custom_styles(struct lstopo_output *loutput)
{
  lstopo__prepare_custom_styles(loutput, hwloc_get_root_obj(loutput->topology));
}

static void
lstopo_set_object_color(struct lstopo_output *loutput,
			hwloc_obj_t obj,
			struct lstopo_style *s)
{
  struct lstopo_obj_userdata *lud = obj->userdata;

  s->bg = &BLACK_COLOR;
  s->t = &BLACK_COLOR;
  s->t2 = &BLACK_COLOR;

  switch (obj->type) {

  case HWLOC_OBJ_MACHINE:
    s->bg = &MACHINE_COLOR;
    break;

  case HWLOC_OBJ_GROUP: {
    hwloc_obj_t parent;
    s->bg = &MISC_COLOR;
    parent = obj->parent;
    while (parent) {
      if (parent->type == HWLOC_OBJ_PACKAGE) {
	s->bg = &GROUP_IN_PACKAGE_COLOR;
	break;
      }
      parent = parent->parent;
    }
    break;
  }

  case HWLOC_OBJ_MISC:
    s->bg = &MISC_COLOR;
    break;

  case HWLOC_OBJ_NUMANODE:
    if (loutput->show_disallowed && lstopo_numa_disallowed(loutput, obj)) {
      s->bg = &DISALLOWED_COLOR;
    } else if (loutput->show_binding && lstopo_numa_binding(loutput, obj)) {
      s->bg = &BINDING_COLOR;
    } else {
      s->bg = &MEMORY_COLOR;
    }
    break;

  case HWLOC_OBJ_PACKAGE:
    s->bg = &PACKAGE_COLOR;
    break;

  case HWLOC_OBJ_DIE:
    s->bg = &DIE_COLOR;
    break;

  case HWLOC_OBJ_CORE:
    s->bg = &CORE_COLOR;
    break;

  case HWLOC_OBJ_L1CACHE:
  case HWLOC_OBJ_L2CACHE:
  case HWLOC_OBJ_L3CACHE:
  case HWLOC_OBJ_L4CACHE:
  case HWLOC_OBJ_L5CACHE:
  case HWLOC_OBJ_L1ICACHE:
  case HWLOC_OBJ_L2ICACHE:
  case HWLOC_OBJ_L3ICACHE:
  case HWLOC_OBJ_MEMCACHE:
    s->bg = &CACHE_COLOR;
    break;

  case HWLOC_OBJ_PU:
    if (loutput->show_disallowed && lstopo_pu_disallowed(loutput, obj)) {
      s->bg = &DISALLOWED_COLOR;
    } else if (loutput->show_binding && lstopo_pu_binding(loutput, obj)) {
      s->bg = &BINDING_COLOR;
    } else {
      s->bg = &THREAD_COLOR;
    }
    break;

  case HWLOC_OBJ_BRIDGE:
    s->bg = &BRIDGE_COLOR;
    break;

  case HWLOC_OBJ_PCI_DEVICE:
    s->bg = &PCI_DEVICE_COLOR;
    break;

  case HWLOC_OBJ_OS_DEVICE:
    s->bg = &OS_DEVICE_COLOR;
    break;

  default:
    assert(0);
  }

  if (lud->style_set & LSTOPO_STYLE_BG)
    s->bg = lud->style.bg;
  if (lud->style_set & LSTOPO_STYLE_T)
    s->t = lud->style.t;
  if (lud->style_set & LSTOPO_STYLE_T2)
    s->t2 = lud->style.t2;
}

static void
prepare_text(struct lstopo_output *loutput, hwloc_obj_t obj)
{
  struct lstopo_obj_userdata *lud = obj->userdata;
  unsigned fontsize = loutput->fontsize;
  unsigned i;
  int n;

  /* sane defaults */
  lud->ntext = 0;
  lud->textwidth = 0;

  if (!loutput->show_text_enabled || !loutput->show_text[obj->type])
    return;

  /* main object identifier line */
  if (obj->type == HWLOC_OBJ_PCI_DEVICE && loutput->show_attrs_enabled && loutput->show_attrs[HWLOC_OBJ_PCI_DEVICE]) {
    /* PCI text collapsing */
    char busid[32];
    char _text[64];
    lstopo_obj_snprintf(loutput, _text, sizeof(_text), obj);
    lstopo_busid_snprintf(loutput, busid, sizeof(busid), obj, lud->pci_collapsed, loutput->need_pci_domain);
    if (loutput->pci_collapse_enabled && lud->pci_collapsed > 1) {
      n = snprintf(lud->text[0].text, sizeof(lud->text[0].text), "%d x { %s %s }", lud->pci_collapsed, _text, busid);
    } else {
      n = snprintf(lud->text[0].text, sizeof(lud->text[0].text), "%s %s", _text, busid);
    }
  } else {
    /* normal object text */
    n = lstopo_obj_snprintf(loutput, lud->text[0].text, sizeof(lud->text[0].text), obj);
  }
  lud->ntext = 1;

  /* additional lines of text */

  if (HWLOC_OBJ_PU == obj->type && loutput->index_type == LSTOPO_INDEX_TYPE_DEFAULT && loutput->show_indexes[obj->type]) {
    /* PU P# is on second line */
    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text), "P#%u", obj->os_index);
  }

  if (loutput->show_attrs_enabled && loutput->show_attrs[obj->type]) {
    if (HWLOC_OBJ_OS_DEVICE == obj->type) {
      if (HWLOC_OBJ_OSDEV_COPROC == obj->attr->osdev.type && obj->subtype) {
	/* Coprocessor */
	if (!strcmp(obj->subtype, "CUDA")) {
	  /* CUDA */
	  const char *value, *value2, *value3;
	  value = hwloc_obj_get_info_by_name(obj, "CUDAGlobalMemorySize");
	  if (value) {
	    unsigned long long mb = strtoull(value, NULL, 10) / 1024;
	    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		     mb >= 10240 ? "%llu GB" : "%llu MB",
		     mb >= 10240 ? mb/1024 : mb);
	  }
	  value = hwloc_obj_get_info_by_name(obj, "CUDAL2CacheSize");
	  if (value) {
	    unsigned long long kb = strtoull(value, NULL, 10);
	    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		     kb >= 10240 ? "L2 (%llu MB)" : "L2 (%llu kB)",
		     kb >= 10240 ? kb/1024 : kb);
	  }
	  value = hwloc_obj_get_info_by_name(obj, "CUDAMultiProcessors");
	  value2 = hwloc_obj_get_info_by_name(obj, "CUDACoresPerMP");
	  value3 = hwloc_obj_get_info_by_name(obj, "CUDASharedMemorySizePerMP");
	  if (value && value2 && value3) {
	    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		     "%s MP x (%s cores + %s kB)", value, value2, value3);
	  }

	} else if (!strcmp(obj->subtype, "OpenCL")) {
	  /* OpenCL */
	  const char *value;
	  value = hwloc_obj_get_info_by_name(obj, "OpenCLComputeUnits");
	  if (value) {
	    unsigned long long cu = strtoull(value, NULL, 10);
	    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		     "%llu compute units", cu);
	  }
	  value = hwloc_obj_get_info_by_name(obj, "OpenCLGlobalMemorySize");
	  if (value) {
	    unsigned long long mb = strtoull(value, NULL, 10) / 1024;
	    snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		     mb >= 10240 ? "%llu GB" : "%llu MB",
		     mb >= 10240 ? mb/1024 : mb);
	  }
	}

      } else if (HWLOC_OBJ_OSDEV_BLOCK == obj->attr->osdev.type) {
	/* Block */
	const char *value;
	value = hwloc_obj_get_info_by_name(obj, "Size");
	if (value) {
	  unsigned long long mb = strtoull(value, NULL, 10) / 1024;
	  snprintf(lud->text[lud->ntext++].text, sizeof(lud->text[0].text),
		   mb >= 10485760 ? "%llu TB" : mb >= 10240 ? "%llu GB" : "%llu MB",
		   mb >= 10485760 ? mb/1048576 : mb >= 10240 ? mb/1024 : mb);
	}
      }
    }
  }

  lud->textwidth = 0;
  for(i=0; i<lud->ntext; i++) {
    unsigned textwidth;
    if (i) /* already computed above for n=0 */
      n = (unsigned)strlen(lud->text[i].text);
    textwidth = get_textwidth(loutput, lud->text[i].text, n, fontsize);
    lud->text[i].width = textwidth;
    lud->text[i].xoffset = 0; /* only used for PU in output_compute_pu_min_textwidth() */
    if (textwidth > lud->textwidth)
      lud->textwidth = textwidth;
  }
}

static void
draw_text(struct lstopo_output *loutput, hwloc_obj_t obj, struct lstopo_color *lcolor, unsigned depth, unsigned x, unsigned y)
{
  struct draw_methods *methods = loutput->methods;
  struct lstopo_obj_userdata *lud = obj->userdata;
  unsigned fontsize = loutput->fontsize;
  unsigned linespacing = loutput->linespacing;
  unsigned i;

  if (!loutput->show_text_enabled || !loutput->show_text[obj->type])
    return;

  for(i=0; i<lud->ntext; i++)
    methods->text(loutput, lcolor, fontsize, depth, x + lud->text[i].xoffset, y + i*(linespacing + fontsize), lud->text[i].text, obj, i);
}

static void
pci_device_draw(struct lstopo_output *loutput, hwloc_obj_t level, unsigned depth, unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *lud = level->userdata;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  unsigned overlaidoffset = 0;

  if (loutput->pci_collapse_enabled && lud->pci_collapsed > 1) {
    /* additional depths and height for overlaid boxes */
    depth -= 2;
    if (lud->pci_collapsed > 2) {
      overlaidoffset = gridsize;
    } else {
      overlaidoffset = gridsize/2;
    }
    if (loutput->no_half_lines)
      overlaidoffset *= 2;
  }

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute children size and position, our size, and save it */
    prepare_text(loutput, level);
    lud->width = gridsize + overlaidoffset;
    lud->height = gridsize + overlaidoffset;
    if (lud->ntext > 0) {
      lud->width += lud->textwidth + gridsize;
      lud->height += fontsize + gridsize;
    }
    place_children(loutput, level,
		   gridsize, lud->height);

  } else { /* LSTOPO_DRAWING_DRAW */
    struct draw_methods *methods = loutput->methods;
    struct lstopo_style style;
    unsigned totwidth, totheight;

    /* restore our size that was computed during prepare */
    totwidth = lud->width;
    totheight = lud->height;

    lstopo_set_object_color(loutput, level, &style);

    if (loutput->pci_collapse_enabled && lud->pci_collapsed > 1) {
      methods->box(loutput, style.bg, depth+2, x + overlaidoffset, totwidth - overlaidoffset, y + overlaidoffset, totheight - overlaidoffset, level, 2);
      if (lud->pci_collapsed > 2)
	methods->box(loutput, style.bg, depth+1, x + overlaidoffset/2, totwidth - overlaidoffset, y + overlaidoffset/2, totheight - overlaidoffset, level, 1);
      methods->box(loutput, style.bg, depth, x, totwidth - overlaidoffset, y, totheight - overlaidoffset, level, 0);
    } else {
      methods->box(loutput, style.bg, depth, x, totwidth, y, totheight, level, 0);
    }

    draw_text(loutput, level, style.t, depth-1, x + gridsize, y + gridsize);

    /* Draw sublevels for real */
    draw_children(loutput, level, depth-1, x, y);
  }
}

static void
bridge_draw(struct lstopo_output *loutput, hwloc_obj_t level, unsigned depth, unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *lud = level->userdata;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  /* align all children even if only some of them have a linkspeed */
  unsigned speedwidth = (loutput->show_text_enabled && loutput->show_text[HWLOC_OBJ_BRIDGE]) ? fontsize + gridsize : 0;

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute children size and position, our size, and save it */
    lud->width = 2*gridsize + gridsize + speedwidth;
    lud->height = BRIDGE_HEIGHT;
    place_children(loutput, level,
		   3*gridsize + speedwidth, 0);

  } else { /* LSTOPO_DRAWING_DRAW */
    struct draw_methods *methods = loutput->methods;
    struct lstopo_style style;

    /* Square and left link */
    lstopo_set_object_color(loutput, level, &style);
    methods->box(loutput, style.bg, depth, x, gridsize, y + BRIDGE_HEIGHT/2 - gridsize/2, gridsize, level, 0);
    methods->line(loutput, &BLACK_COLOR, depth, x + gridsize, y + BRIDGE_HEIGHT/2, x + 2*gridsize, y + BRIDGE_HEIGHT/2, level, 0);

    if (level->io_arity > 0) {
      hwloc_obj_t child = NULL;
      unsigned ymax = -1;
      unsigned ymin = (unsigned) -1;
      int ncstate;
      int i = 0;
      while ((child=next_child(loutput, level, LSTOPO_CHILD_KIND_ALL, child, &ncstate)) != NULL) {
	struct lstopo_obj_userdata *clud = child->userdata;
	unsigned ymid = y + clud->yrel + BRIDGE_HEIGHT/2;
	/* Line to PCI device */
	methods->line(loutput, &BLACK_COLOR, depth-1, x+2*gridsize, ymid, x+3*gridsize+speedwidth, ymid, level, i+2);
	if (ymin == (unsigned) -1)
	  ymin = ymid;
	ymax = ymid;
	/* Negotiated link speed */
	if (loutput->show_text_enabled && loutput->show_text[HWLOC_OBJ_BRIDGE]) {
	  float speed = pci_link_speed(child);
	  if (loutput->show_attrs_enabled && loutput->show_attrs[HWLOC_OBJ_BRIDGE] && speed != 0.) {
	    char text[4];
	    if (speed >= 10.)
	      snprintf(text, sizeof(text), "%.0f", child->attr->pcidev.linkspeed);
	    else
	      snprintf(text, sizeof(text), "%0.1f", child->attr->pcidev.linkspeed);
	    methods->text(loutput, style.t2, fontsize, depth-1, x + 2.5*gridsize, ymid + BRIDGE_HEIGHT/2, text, level, i+2);
	  }
	}
	i++;
      }
      methods->line(loutput, &BLACK_COLOR, depth-1, x+2*gridsize, ymin, x+2*gridsize, ymax, level, 1);

      /* Draw sublevels for real */
      draw_children(loutput, level, depth-1, x, y);
    }
  }
}

static void
factorized_draw(struct lstopo_output *loutput, hwloc_obj_t level, unsigned depth, unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *lud = level->userdata;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  unsigned linespacing = loutput->linespacing;
  unsigned missingseparator;

  /* we need a separator even between PUs */
  if ((unsigned)level->depth == loutput->depth-1)
    missingseparator = gridsize;
  else
    missingseparator = 0;

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute children size and position, our size, and save it */
    unsigned n, textwidth;
    lud->width = gridsize*5; /* space, box, space, box, space */
    lud->height = gridsize*2 + linespacing + fontsize + gridsize; /* space, box, linespace, text, gridsize */
    sprintf(lud->text[0].text, "%ux total", level->parent->arity);
    n = (unsigned)strlen(lud->text[0].text);
    textwidth = get_textwidth(loutput, lud->text[0].text, n, fontsize);
    lud->text[0].width = textwidth;
    if (textwidth > lud->width) {
      lud->width = textwidth;
      lud->text[0].xoffset = 0;
    } else {
      lud->text[0].xoffset = (lud->width - textwidth)/2;
    }
    lud->ntext = 1;
    lud->width += 2*missingseparator;

  } else { /* LSTOPO_DRAWING_DRAW */
    struct draw_methods *methods = loutput->methods;
    struct lstopo_style boxstyle, textstyle;
    unsigned boxoffset = (lud->width - 5*gridsize - 2*missingseparator) / 2;
    /* boxes use object style, but the text outside uses the parent style */
    lstopo_set_object_color(loutput, level, &boxstyle);
    lstopo_set_object_color(loutput, level->parent, &textstyle);
    methods->box(loutput, boxstyle.bg, depth, x + missingseparator + boxoffset, gridsize, y + gridsize, gridsize, level, 0);
    methods->box(loutput, boxstyle.bg, depth, x + missingseparator + boxoffset + 2*gridsize, gridsize, y + gridsize, gridsize, level, 0);
    methods->box(loutput, boxstyle.bg, depth, x + missingseparator + boxoffset + 4*gridsize, gridsize, y + gridsize, gridsize, level, 0);
    methods->text(loutput, textstyle.t, fontsize, depth, x + missingseparator + lud->text[0].xoffset, y + 2 * gridsize + linespacing, lud->text[0].text, level, 0);
  }
}

static void
cache_draw(struct lstopo_output *loutput, hwloc_obj_t level, unsigned depth, unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *lud = level->userdata;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;

  if (loutput->factorize_enabled
      && lud->factorized == 1
      && level->parent->arity > loutput->factorize_min[level->type]) {
    factorized_draw(loutput, level, depth, x, y);
    return;
  }

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute children size and position, our size, and save it */
    prepare_text(loutput, level);
    lud->width = gridsize;
    lud->height = gridsize;
    if (lud->ntext > 0) {
      lud->width += lud->textwidth + gridsize;
      lud->height += fontsize + gridsize;
    }
    place_children(loutput, level,
		   0, lud->height /* the callee with add vertical space if needed */);

  } else { /* LSTOPO_DRAWING_DRAW */
    struct draw_methods *methods = loutput->methods;
    struct lstopo_style style;
    unsigned totwidth;
    unsigned myoff = 0;
    unsigned myheight;

    /* restore our size that was computed during prepare */
    totwidth = lud->width;

    /* totheight also contains children outside of this actual cache box,
     * recompute our height without outside children (just like above)
     */
    myheight = gridsize;
    if (lud->ntext > 0)
      myheight += fontsize + gridsize;

    if (lud->above_children.kinds) {
      /* display above_children even above the cache itself */
      myoff = lud->above_children.height + gridsize;
      lud->above_children.yrel = 0;
    }

    lstopo_set_object_color(loutput, level, &style);
    methods->box(loutput, style.bg, depth, x, totwidth, y + myoff, myheight, level, 0);

    draw_text(loutput, level, style.t, depth-1, x + gridsize, y + gridsize + myoff);

    /* Draw sublevels for real */
    draw_children(loutput, level, depth-1, x, y);
  }
}

static void
normal_draw(struct lstopo_output *loutput, hwloc_obj_t level, unsigned depth, unsigned x, unsigned y)
{
  struct lstopo_obj_userdata *lud = level->userdata;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  unsigned linespacing = loutput->linespacing;

  if (loutput->factorize_enabled
      && lud->factorized == 1
      && level->parent->arity > loutput->factorize_min[level->type]) {
    factorized_draw(loutput, level, depth, x, y);
    return;
  }

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute children size and position, our size, and save it */
    if (level->type != HWLOC_OBJ_PU) /* PU already computed in output_compute_pu_min_textwidth() earlier */
      prepare_text(loutput, level);
    lud->width = gridsize;
    lud->height = gridsize;
    if (lud->ntext > 0) {
      lud->width += lud->textwidth + gridsize;
      lud->height += fontsize + (fontsize + linespacing) * (lud->ntext - 1) + gridsize;
    }
    place_children(loutput, level,
		   gridsize, lud->height);

  } else { /* LSTOPO_DRAWING_DRAW */
    struct draw_methods *methods = loutput->methods;
    struct lstopo_style style;
    unsigned totwidth, totheight;

    /* restore our size that was computed during prepare */
    totwidth = lud->width;
    totheight = lud->height;

    lstopo_set_object_color(loutput, level, &style);
    methods->box(loutput, style.bg, depth, x, totwidth, y, totheight, level, 0);
    draw_text(loutput, level, style.t, depth-1, x + gridsize, y + gridsize);

    /* Draw sublevels for real */
    draw_children(loutput, level, depth-1, x, y);
  }
}

static void
output_align_PU_textwidth(struct lstopo_output *loutput)
{
  hwloc_topology_t topology = loutput->topology;
  unsigned textwidth_max = 0;
  hwloc_obj_t pu;
  unsigned i;

  /* compute the max PU textwidth */
  pu = NULL;
  while ((pu = hwloc_get_next_obj_by_type(topology, HWLOC_OBJ_PU, pu)) != NULL) {
    struct lstopo_obj_userdata *lud = pu->userdata;
    prepare_text(loutput, pu);
    if (lud->textwidth > textwidth_max)
      textwidth_max = lud->textwidth;
  }

  /* update text placement to match the max textwidth */
  pu = NULL;
  while ((pu = hwloc_get_next_obj_by_type(topology, HWLOC_OBJ_PU, pu)) != NULL) {
    struct lstopo_obj_userdata *lud = pu->userdata;
    for(i=0; i<lud->ntext; i++)
      if (lud->text[i].width < textwidth_max) {
	lud->text[i].xoffset = (textwidth_max-lud->text[i].width)/2;
	lud->text[i].width = textwidth_max;
      }
    lud->textwidth = textwidth_max;
  }
}

void
output_draw(struct lstopo_output *loutput)
{
  hwloc_topology_t topology = loutput->topology;
  struct draw_methods *methods = loutput->methods;
  int legend = loutput->legend;
  unsigned gridsize = loutput->gridsize;
  unsigned fontsize = loutput->fontsize;
  unsigned linespacing = loutput->linespacing;
  hwloc_obj_t root = hwloc_get_root_obj(topology);
  struct lstopo_obj_userdata *rlud = root->userdata;
  unsigned depth = 100;
  unsigned totwidth, totheight, offset, i;
  time_t t;
  char text[3][128];
  unsigned ntext = 0;
  char hostname[122] = "";
  const char *forcedhostname = NULL;
  unsigned long hostname_size = sizeof(hostname);
  unsigned maxtextwidth = 0, textwidth;

  if (legend) {
    forcedhostname = hwloc_obj_get_info_by_name(hwloc_get_root_obj(topology), "HostName");
    if (!forcedhostname && hwloc_topology_is_thissystem(topology)) {
#if defined(HWLOC_WIN_SYS) && !defined(__CYGWIN__)
      GetComputerName(hostname, &hostname_size);
#else
      gethostname(hostname, hostname_size);
#endif
    }
    if (forcedhostname || *hostname) {
      if (forcedhostname)
	snprintf(text[ntext], sizeof(text[ntext]), "Host: %s", forcedhostname);
      else
	snprintf(text[ntext], sizeof(text[ntext]), "Host: %s", hostname);
      textwidth = get_textwidth(loutput, text[ntext], (unsigned) strlen(text[ntext]), fontsize);
      if (textwidth > maxtextwidth)
	maxtextwidth = textwidth;
      ntext++;
    }

    /* Display whether we're showing physical or logical IDs */
    if (loutput->index_type != LSTOPO_INDEX_TYPE_DEFAULT) {
      snprintf(text[ntext], sizeof(text[ntext]), "Indexes: %s", (loutput->index_type == LSTOPO_INDEX_TYPE_LOGICAL ? "logical" : "physical"));
      textwidth = get_textwidth(loutput, text[ntext], (unsigned) strlen(text[ntext]), fontsize);
      if (textwidth > maxtextwidth)
	maxtextwidth = textwidth;
      ntext++;
    }

    /* Display timestamp */
    t = time(NULL);
#ifdef HAVE_STRFTIME
    {
      struct tm *tmp;
      tmp = localtime(&t);
      strftime(text[ntext], sizeof(text[ntext]), "Date: %c", tmp);
    }
#else /* HAVE_STRFTIME */
    {
      char *date;
      unsigned n;
      date = ctime(&t);
      n = (unsigned) strlen(date);
      if (n && date[n-1] == '\n') {
        date[n-1] = 0;
      }
      snprintf(text[ntext], sizeof(text[ntext]), "Date: %s", date);
    }
#endif /* HAVE_STRFTIME */
    textwidth = get_textwidth(loutput, text[ntext], (unsigned) strlen(text[ntext]), fontsize);
    if (textwidth > maxtextwidth)
      maxtextwidth = textwidth;
    ntext++;
  }

  if (loutput->drawing == LSTOPO_DRAWING_PREPARE) {
    /* compute root size, our size, and save it */

    output_align_PU_textwidth(loutput);

    get_type_fun(root->type)(loutput, root, depth, 0, 0);

    /* loutput width is max(root, legend) */
    totwidth = rlud->width;
    if (maxtextwidth + 2*gridsize > totwidth)
      totwidth = maxtextwidth + 2*gridsize;
    loutput->width = totwidth;

    /* loutput height is sum(root, legend) */
    totheight = rlud->height;
    if (legend)
      totheight += gridsize + (ntext+loutput->legend_append_nr - 1) * (linespacing+fontsize) + fontsize + gridsize;
    loutput->height = totheight;

  } else { /* LSTOPO_DRAWING_DRAW */
    /* restore our size that was computed during prepare */
    totwidth = rlud->width;
    totheight = rlud->height;

    /* Draw root for real */
    get_type_fun(root->type)(loutput, root, depth, 0, 0);

    /* Draw legend */
    if (legend) {
      offset = rlud->height + gridsize;
      methods->box(loutput, &WHITE_COLOR, depth, 0, loutput->width, totheight, gridsize + (ntext+loutput->legend_append_nr-1) * (linespacing + fontsize) + fontsize + gridsize, NULL, 0);
      for(i=0; i<ntext; i++, offset += linespacing + fontsize)
	methods->text(loutput, &BLACK_COLOR, fontsize, depth, gridsize, offset, text[i], NULL, i);
      for(i=0; i<loutput->legend_append_nr; i++, offset += linespacing + fontsize)
	methods->text(loutput, &BLACK_COLOR, fontsize, depth, gridsize, offset, loutput->legend_append[i], NULL, i+ntext);
    }
  }
}

/*
 * given a type, return a pointer FUN to the function that draws it.
 */
static foo_draw
get_type_fun(hwloc_obj_type_t type)
{
  switch (type) {
    case HWLOC_OBJ_MACHINE:
    case HWLOC_OBJ_NUMANODE:
    case HWLOC_OBJ_PACKAGE:
    case HWLOC_OBJ_DIE:
    case HWLOC_OBJ_CORE:
    case HWLOC_OBJ_PU:
    case HWLOC_OBJ_GROUP:
    case HWLOC_OBJ_OS_DEVICE:
    case HWLOC_OBJ_MISC: return normal_draw;
    case HWLOC_OBJ_L1CACHE: return cache_draw;
    case HWLOC_OBJ_L2CACHE: return cache_draw;
    case HWLOC_OBJ_L3CACHE: return cache_draw;
    case HWLOC_OBJ_L4CACHE: return cache_draw;
    case HWLOC_OBJ_L5CACHE: return cache_draw;
    case HWLOC_OBJ_L1ICACHE: return cache_draw;
    case HWLOC_OBJ_L2ICACHE: return cache_draw;
    case HWLOC_OBJ_L3ICACHE: return cache_draw;
    case HWLOC_OBJ_MEMCACHE: return cache_draw;
    case HWLOC_OBJ_PCI_DEVICE: return pci_device_draw;
    case HWLOC_OBJ_BRIDGE: return bridge_draw;
    default:
    case HWLOC_OBJ_TYPE_MAX: assert(0);
  }
  /* for dumb compilers */
  return normal_draw;
}
