/***************************************************************************************
 * Copyright (c) 2014-2022 Zihao Yu, Nanjing University
 *
 * NEMU is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan
 *PSL v2. You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
 *KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 *NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 *
 * See the Mulan PSL v2 for more details.
 ***************************************************************************************/

#include "sdb.h"
#include <common.h>
#include <stdio.h>

#define NR_WP 32

typedef struct watchpoint {
  int NO;
  struct watchpoint *next;
  word_t val;
  char *expr;
} WP;

static WP wp_pool[NR_WP] = {};
static WP *head = NULL, *tail = NULL, *free_ = NULL;
static int wp_count = 0;

void init_wp_pool() {
  int i;
  for (i = 0; i < NR_WP; i++) {
    wp_pool[i].NO = i;
    wp_pool[i].next = (i == NR_WP - 1 ? NULL : &wp_pool[i + 1]);
  }

  head = NULL;
  free_ = wp_pool;
}

static WP *wp_new() {
  if (free_ == NULL) {
    Error("wp_pool: Watchpoint pool not initialized or is full.");
    return NULL;
  }
  
  WP *ret = free_;
  free_ = free_->next;

  ret->NO = 0;
  ret->next = NULL;
  return ret;
}

static void wp_delete(WP *wp) {
  Assert(wp, "Failed to delete watchpoint from pool.");
  wp->next = free_;
  free_ = wp;
}

int wp_add(char * expr) {
  WP *wp = wp_new();
  if (wp == NULL) {
    Error("watchpoint: Failed to add watchpoint, pool is full.");
    goto failed_create;
  }

  wp->NO = wp_count++;
  if (tail == NULL) {
    head = wp;
    tail = wp;
  } else {
    tail->next = wp;
    tail = wp;
  }

  bool success = false;
  wp->val = parse_expr(expr, &success);
  if (!success) {
    Error("Failed to parse given expression `%s`", expr);
    goto failed_create;
  }

  int len = strlen(expr);
  wp->expr = malloc((len + 1) * sizeof(char));
  if (wp->expr == NULL) {
    Error("Failed to allocate memory for expression");
    goto failed_create;
  }
  strncpy(wp->expr, expr, len + 1);
  wp->expr[len] = '\0';
  return 0;

failed_create:
  wp_delete(wp);
  return 1;
}

int wp_remove_by_number(int number) {
  WP *target_prev;
  // Find previous node of target number
  for (target_prev = head; target_prev != NULL && target_prev->next->NO != number; target_prev = target_prev->next) ;
  if (target_prev == NULL) {
    Error("Watchpoint not found, you can check current watchpoints with `info w`");
    return 1;
  }
  WP *target = target_prev->next;
  target_prev->next = target->next;
  if (target == head) {
    head = target->next;
  } else if (target == tail) {
    tail = target_prev;
  }
  wp_delete(target);
  return 0;
}

static bool wp_check_change(WP* wp) {
  bool success = false;
  word_t result;

  result = parse_expr(wp->expr, &success);
  if (!success) {
    panic("Failed to evaluate expression `%s`", wp->expr);
  } 
  if (result != wp->val) {
    wp->val = result;
    return true;
  }
  return false;
}

/* 
  Check if watchpoint value changed after execution
*/
bool wp_eval_all() {
  WP *wp;
  bool value_change = false;
  for (wp = head; wp != NULL; wp = wp->next) {
    int prev_val = wp->val;
    if (wp_check_change(wp)) {
      printf("Watchpoint %d: %s\n  %u -> %u\n", wp->NO, wp->expr, prev_val, wp->val);
      value_change = true;
    }
  }
  return value_change;
}
