#include <smop/base.h>
#include <smop/s0native.h>
#include <smop/dump.h>
#include <stdlib.h>
#include <stdio.h>

SMOP__ResponderInterface* SMOP__DUMP_RI;
SMOP__ResponderInterface* SMOP__DUMP_int_RI;
SMOP__ResponderInterface* SMOP__DUMP_obj_RI;
SMOP__ResponderInterface* SMOP__DUMP_obj_array_RI;


SMOP__Object* smop_bool_dump(SMOP__Object* interpreter,
                                SMOP__ResponderInterface* responder,
                                SMOP__Object* obj) {
  return smop_dump_create((SMOP__Object*[]) {
      smop_dump_attr_create("RI"),
      smop_dump_obj_create((SMOP__Object*)obj->RI),
      smop_dump_attr_create("value"),
      smop_dump_int_create(obj == SMOP__NATIVE__bool_true),
      NULL
  });
    
}
SMOP__Object* smop_idconst_dump(SMOP__Object* interpreter,
                                SMOP__ResponderInterface* responder,
                                SMOP__Object* obj) {
  int len;
  char* str = SMOP__NATIVE__idconst_fetch_with_null(obj,&len);
  return smop_dump_create((SMOP__Object*[]) {
      smop_dump_attr_create("RI"),
      smop_dump_obj_create((SMOP__Object*)obj->RI),
      smop_dump_attr_create("content"),
      smop_dump_str_create(str),
      NULL
  });
  free(str);
}


void smop_dump_init() {
  SMOP__DUMP_RI = calloc(1,sizeof(SMOP__ResponderInterface));
  SMOP__DUMP_RI->MESSAGE = smop_placeholder_message;
  SMOP__DUMP_RI->REFERENCE = smop_noop_reference;
  SMOP__DUMP_RI->RELEASE = smop_noop_release;
  SMOP__DUMP_RI->WEAKREF = smop_noop_weakref;
  SMOP__DUMP_RI->id = "result of DUMP";
  SMOP__DUMP_RI->RI = (SMOP__ResponderInterface *)SMOP__metaRI;

  SMOP__DUMP_int_RI = calloc(1,sizeof(SMOP__ResponderInterface));
  SMOP__DUMP_int_RI->MESSAGE = smop_placeholder_message;
  SMOP__DUMP_int_RI->REFERENCE = smop_noop_reference;
  SMOP__DUMP_int_RI->RELEASE = smop_noop_release;
  SMOP__DUMP_int_RI->WEAKREF = smop_noop_weakref;
  SMOP__DUMP_int_RI->id = "DUMP int";
  SMOP__DUMP_int_RI->RI = (SMOP__ResponderInterface *)SMOP__metaRI;
  

  SMOP__DUMP_obj_RI = calloc(1,sizeof(SMOP__ResponderInterface));
  SMOP__DUMP_obj_RI->MESSAGE = smop_placeholder_message;
  SMOP__DUMP_obj_RI->REFERENCE = smop_noop_reference;
  SMOP__DUMP_obj_RI->RELEASE = smop_noop_release;
  SMOP__DUMP_obj_RI->WEAKREF = smop_noop_weakref;
  SMOP__DUMP_obj_RI->id = "DUMP obj";
  SMOP__DUMP_obj_RI->RI = (SMOP__ResponderInterface *)SMOP__metaRI;

  SMOP__DUMP_obj_array_RI = calloc(1,sizeof(SMOP__ResponderInterface));
  SMOP__DUMP_obj_array_RI->MESSAGE = smop_placeholder_message;
  SMOP__DUMP_obj_array_RI->REFERENCE = smop_noop_reference;
  SMOP__DUMP_obj_array_RI->RELEASE = smop_noop_release;
  SMOP__DUMP_obj_array_RI->WEAKREF = smop_noop_weakref;
  SMOP__DUMP_obj_array_RI->id = "DUMP obj array";
  SMOP__DUMP_obj_array_RI->RI = (SMOP__ResponderInterface *)SMOP__metaRI;

  
  /* when SMOP__metaRI is created the dump modules wasn't yet loaded */
  SMOP__metaRI->RI->DUMP = smop_ri_dump;

  SMOP__NATIVE__bool_true->RI->DUMP = smop_bool_dump;
  SMOP__NATIVE__idconst_RI->DUMP = smop_idconst_dump;
}

SMOP__Object* smop_ri_dump(SMOP__Object* interpreter,
                                SMOP__ResponderInterface* responder,
                                SMOP__Object* obj) {

  SMOP__ResponderInterface* ri = (SMOP__ResponderInterface*) obj;

  return smop_dump_create((SMOP__Object*[]) {
      smop_dump_attr_create("RI"),
      smop_dump_obj_create((SMOP__Object*)ri->RI),
      smop_dump_attr_create("id"),
      smop_dump_str_create(ri->id),
      NULL
  });
}


SMOP__Object* smop_dump_create(SMOP__Object** data) {
  int size = 0;
  SMOP__Object** d = data;
  while (*d != NULL) {
    d++;
    size++;
  }
  smop_dump* dump = (smop_dump*) malloc(sizeof(smop_dump));
  dump->size = size;
  dump->data = (SMOP__Object**) malloc(sizeof(SMOP__Object*) * (size));
  dump->RI = SMOP__DUMP_RI;
  int i;
  for (i=0;i<size;i++) {
    dump->data[i] = data[i];
  }
  return (SMOP__Object*) dump;
}

SMOP__Object* smop_dump_obj_array_create(SMOP__Object** data,int size) {
  smop_dump_obj_array* array = (smop_dump_obj_array*) malloc(sizeof(smop_dump_obj_array));
  array->size = size;
  array->data = (SMOP__Object**) malloc(sizeof(SMOP__Object*) * (size));
  array->RI = SMOP__DUMP_obj_array_RI;
  int i;
  for (i=0;i<size;i++) {
    array->data[i] = data[i];
  }
  return (SMOP__Object*) array;
}


SMOP__Object* smop_dump_int_create(int value) {
  smop_dump_int* obj = malloc(sizeof(smop_dump_int));
  obj->value = value;
  obj->RI = SMOP__DUMP_int_RI;
  return (SMOP__Object*) obj;
}


SMOP__Object* smop_dump_obj_create(SMOP__Object* value) {
  smop_dump_obj* obj = malloc(sizeof(smop_dump_obj));
  obj->value = value;
  obj->RI = SMOP__DUMP_obj_RI;
  return (SMOP__Object*) obj;
}

SMOP__Object* smop_dump_str_create(char* value) {
  return SMOP__NATIVE__idconst_create(value);
}
SMOP__Object* smop_dump_attr_create(char* value) {
  return SMOP__NATIVE__idconst_create(value);
}

void smop_dump_destr() {
  free(SMOP__DUMP_RI);
  free(SMOP__DUMP_int_RI);
}

typedef struct list {
  struct list* next;
  SMOP__Object* value;
} list;
static list* list_add(list* l,SMOP__Object* value) {
  list* new_node = malloc(sizeof(list)); 
  new_node->value = value;
  new_node->next = l;
  return new_node;
}
static int in_list(list* l,SMOP__Object* value) {
  while (l) {
    if (l->value == value) return 1;
    l = l->next;
  }
  return 0;
}
static void list_destroy(list* l) {
  while (l) {
    list* old = l;
    l = l->next;
    free(old);
  }
}

static int dump_id;
void smop_dump_print(SMOP__Object* interpreter,SMOP__Object* obj,char* file_template) {

  char file[200]; 
  snprintf(file,200,file_template,dump_id);
  dump_id++;
  printf("dumping to %s\n",file);
  FILE* f = fopen(file,"w");
  if (!f) {
    perror("can't dump the object:");
  }
  fprintf(f,"dump of %p\n",obj);
  list* visited = NULL;
  list* to_visit = list_add(NULL,obj);

  SMOP__ResponderInterface* idconst_ri = SMOP__NATIVE__idconst_create("example")->RI;
  while (to_visit) {
    list* current = to_visit;
    to_visit = to_visit->next;
    if (!current->value) {
      printf("DUMPing a null\n");
      exit(0);
    } else if (!current->value->RI) {
      printf("value has no RI\n");
      exit(0);
    } else if (!current->value->RI->DUMP) {
      printf("no DUMP for %s\n",current->value->RI->id);
      exit(0);
    }
    smop_dump* dump = (smop_dump*) SMOP_DUMP(interpreter,current->value);
    current->next = visited;
    visited = current;

    fprintf(f,"dumping %p {\n",current->value);
    int i;
    for (i=0; i< dump->size;i++) {
      fprintf(f,"    ");
      if (dump->data[i]->RI == SMOP__DUMP_int_RI) { 
        fprintf(f,"%d\n",((smop_dump_int*)dump->data[i])->value);
      } else if (dump->data[i]->RI == SMOP__DUMP_obj_RI) { 
        SMOP__Object* obj = ((smop_dump_obj*)dump->data[i])->value;
        if (obj && !in_list(visited,obj) && !in_list(to_visit,obj)) {
          to_visit = list_add(to_visit,obj);
        }
        fprintf(f,"%p\n",obj);
      } else if (dump->data[i]->RI == idconst_ri) {
        int len;
        char* str = SMOP__NATIVE__idconst_fetch_with_null(dump->data[i],&len);
        /* TODO escape special chars */
        int i;
        fputc('"',f);
        for (i=0;i < len;i++) {
          if (str[i] == '\n') {
            fputc('\\',f);
            fputc('n',f);
          } else if (str[i] == '"') {
            fputc('\\',f);
            fputc('"',f);
          } else {
            fputc(str[i],f);
          }
        }
        fputc('"',f);
        fputc('\n',f);
        free(str);
      } else if (dump->data[i]->RI == SMOP__DUMP_obj_array_RI) { 
        smop_dump_obj_array* array = (smop_dump_obj_array*) dump->data[i];
        fprintf(f,"[\n");
        int i;
        for (i=0; i< array->size;i++) {
          fprintf(f,"%p\n",(void*)array->data[i]);
          if (array->data[i] && !in_list(visited,array->data[i]) && !in_list(to_visit,array->data[i])) {
            to_visit = list_add(to_visit,array->data[i]);
          }
        }
        fprintf(f,"]\n");
      } else {
        fprintf(f,"unknown %s\n",dump->data[i]->RI->id);
      }
    }
    fprintf(f,"}\n");
  }
  fclose(f);
  list_destroy(visited);
}
