/*
 * Copyright © 2013-2014 Inria.  All rights reserved.
 * See COPYING in top-level directory.
 */

#include <hwloc.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

int main(void)
{
  hwloc_topology_t topo1, topo2, topo3;
  hwloc_topology_diff_t diff, diff2, tmpdiff;
  hwloc_obj_t obj;
  char *xmlbuffer;
  int xmlbuflen;
  char *refname;
  int err;

  hwloc_topology_init(&topo1);
  hwloc_topology_load(topo1);
  printf("duplicate topo1 into topo2\n");
  hwloc_topology_dup(&topo2, topo1);

  printf("check that topo2 is identical\n");
  err = hwloc_topology_diff_build(topo1, topo2, 0, &diff);
  assert(err == 0);
  assert(!diff);

  printf("add a new info to topo2\n");
  obj = hwloc_get_root_obj(topo1);
  hwloc_obj_add_info(obj, "Foo", "Bar");
  printf("check that topo2 cannot be diff'ed from topo1\n");
  err = hwloc_topology_diff_build(topo1, topo2, 0, &diff);
  assert(err == 1);
  assert(diff->generic.type == HWLOC_TOPOLOGY_DIFF_TOO_COMPLEX);
  assert(diff->generic.next == NULL);
  assert(diff->too_complex.obj_depth == 0);
  assert(diff->too_complex.obj_index == 0);
  hwloc_topology_diff_destroy(topo1, diff);

  printf("add a similar info to topo1, and change memory sizes\n");
  obj = hwloc_get_root_obj(topo2);
  hwloc_obj_add_info(obj, "Foo", "Bar2");
  obj->memory.local_memory += 128*4096;

  obj = hwloc_get_obj_by_type(topo2, HWLOC_OBJ_NUMANODE, 0);
  if (obj)
    obj->memory.local_memory += 32*4096;
  printf("check that topo2 is now properly diff'ed\n");
  err = hwloc_topology_diff_build(topo1, topo2, 0, &diff);
  assert(err == 0);
  tmpdiff = diff;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  assert(tmpdiff->obj_attr.obj_depth == 0);
  assert(tmpdiff->obj_attr.obj_index == 0);
  assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_SIZE);
  assert(tmpdiff->obj_attr.diff.uint64.newvalue - tmpdiff->obj_attr.diff.uint64.oldvalue == 128*4096);
  tmpdiff = tmpdiff->generic.next;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  assert(tmpdiff->obj_attr.obj_depth == 0);
  assert(tmpdiff->obj_attr.obj_index == 0);
  assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_INFO);
  err = strcmp(tmpdiff->obj_attr.diff.string.name, "Foo");
  assert(!err);
  err = strcmp(tmpdiff->obj_attr.diff.string.oldvalue, "Bar");
  assert(!err);
  err = strcmp(tmpdiff->obj_attr.diff.string.newvalue, "Bar2");
  assert(!err);
  if (hwloc_get_nbobjs_by_type(topo1, HWLOC_OBJ_NUMANODE) > 0) {
    tmpdiff = tmpdiff->generic.next;
    assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
    assert(tmpdiff->obj_attr.obj_depth == (unsigned) hwloc_get_type_depth(topo1, HWLOC_OBJ_NUMANODE));
    assert(tmpdiff->obj_attr.obj_index == 0);
    assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_SIZE);
    assert(tmpdiff->obj_attr.diff.uint64.newvalue - tmpdiff->obj_attr.diff.uint64.oldvalue == 32*4096);
    assert(tmpdiff->generic.next == NULL);
  }

  printf("apply the diff to new duplicate topo3 of topo1\n");
  hwloc_topology_dup(&topo3, topo1);
  err = hwloc_topology_diff_apply(topo3, diff, 0);
  assert(!err);
  printf("check that topo2 and topo3 are identical\n");
  err = hwloc_topology_diff_build(topo2, topo3, 0, &diff2);
  assert(err == 0);
  assert(!diff2);

  printf("apply the reverse diff to topo2\n");
  err = hwloc_topology_diff_apply(topo2, diff, HWLOC_TOPOLOGY_DIFF_APPLY_REVERSE);
  assert(!err);
  printf("check that topo2 and topo1 are identical\n");
  err = hwloc_topology_diff_build(topo1, topo2, 0, &diff2);
  assert(err == 0);
  assert(!diff2);

  printf("exporting and reloading diff from XML buffer without refname\n");
  err = hwloc_topology_diff_export_xmlbuffer(topo1, diff, NULL, &xmlbuffer, &xmlbuflen);
  assert(!err);
  hwloc_topology_diff_destroy(topo1, diff);
  err = hwloc_topology_diff_load_xmlbuffer(topo1, xmlbuffer, xmlbuflen, &diff2, &refname);
  assert(!err);
  assert(diff2);
  assert(!refname);
  assert(!err);
  hwloc_free_xmlbuffer(topo1, xmlbuffer);

  printf("exporting and reloading diff from XML buffer with refname\n");
  err = hwloc_topology_diff_export_xmlbuffer(topo1, diff2, "foobar", &xmlbuffer, &xmlbuflen);
  assert(!err);
  hwloc_topology_diff_destroy(topo1, diff2);
  err = hwloc_topology_diff_load_xmlbuffer(topo1, xmlbuffer, xmlbuflen, &diff, &refname);
  assert(!err);
  assert(diff);
  err = strcmp(refname, "foobar");
  assert(!err);
  free(refname);
  hwloc_free_xmlbuffer(topo1, xmlbuffer);

  tmpdiff = diff;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  assert(tmpdiff->obj_attr.obj_depth == 0);
  assert(tmpdiff->obj_attr.obj_index == 0);
  assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_SIZE);
  assert(tmpdiff->obj_attr.diff.uint64.newvalue - tmpdiff->obj_attr.diff.uint64.oldvalue == 128*4096);
  tmpdiff = tmpdiff->generic.next;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  assert(tmpdiff->obj_attr.obj_depth == 0);
  assert(tmpdiff->obj_attr.obj_index == 0);
  assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_INFO);
  err = strcmp(tmpdiff->obj_attr.diff.string.name, "Foo");
  assert(!err);
  err = strcmp(tmpdiff->obj_attr.diff.string.oldvalue, "Bar");
  assert(!err);
  err = strcmp(tmpdiff->obj_attr.diff.string.newvalue, "Bar2");
  assert(!err);
  if (hwloc_get_nbobjs_by_type(topo1, HWLOC_OBJ_NUMANODE) > 0) {
    tmpdiff = tmpdiff->generic.next;
    assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
    assert(tmpdiff->obj_attr.obj_depth == (unsigned) hwloc_get_type_depth(topo1, HWLOC_OBJ_NUMANODE));
    assert(tmpdiff->obj_attr.obj_index == 0);
    assert(tmpdiff->obj_attr.diff.generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR_SIZE);
    assert(tmpdiff->obj_attr.diff.uint64.newvalue - tmpdiff->obj_attr.diff.uint64.oldvalue == 32*4096);
    assert(tmpdiff->generic.next == NULL);
  }

  printf("reapplying to topo2\n");
  err = hwloc_topology_diff_apply(topo2, diff, 0);
  assert(!err);
  printf("check that topo2 and topo3 are again identical\n");
  err = hwloc_topology_diff_build(topo2, topo3, 0, &diff2);
  assert(err == 0);
  assert(!diff2);

  hwloc_topology_diff_destroy(topo1, diff);

  printf("adding new key to the bottom of topo3\n");
  obj = hwloc_get_obj_by_type(topo3, HWLOC_OBJ_PU, 0);
  assert(obj);
  hwloc_obj_add_info(obj, "Bar", "Baz3");
  printf("check that diff fails at the last entry\n");
  err = hwloc_topology_diff_build(topo1, topo3, 0, &diff);
  assert(err == 1);
  assert(diff);
  tmpdiff = diff;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  tmpdiff = tmpdiff->generic.next;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  if (hwloc_get_nbobjs_by_type(topo1, HWLOC_OBJ_NUMANODE) > 0) {
    tmpdiff = tmpdiff->generic.next;
    assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_OBJ_ATTR);
  }
  tmpdiff = tmpdiff->generic.next;
  assert(tmpdiff->generic.type == HWLOC_TOPOLOGY_DIFF_TOO_COMPLEX);
  assert(tmpdiff->generic.next == NULL);
  hwloc_topology_diff_destroy(topo1, diff);

  printf("adding similar key to topo1\n");
  obj = hwloc_get_obj_by_type(topo1, HWLOC_OBJ_PU, 0);
  assert(obj);
  hwloc_obj_add_info(obj, "Bar", "Baz1");
  printf("checking that topo3 diff fails to reverse apply to topo2\n");
  err = hwloc_topology_diff_build(topo1, topo3, 0, &diff);
  assert(err == 0);
  assert(diff);
  err = hwloc_topology_diff_apply(topo2, diff, HWLOC_TOPOLOGY_DIFF_APPLY_REVERSE);
  if (hwloc_get_nbobjs_by_type(topo1, HWLOC_OBJ_NUMANODE) > 0)
    assert(err == -4);
  else
    assert(err == -3);
  hwloc_topology_diff_destroy(topo1, diff);

  hwloc_topology_destroy(topo3);
  hwloc_topology_destroy(topo2);
  hwloc_topology_destroy(topo1);

  return 0;
}
