//
// bus/memorymap.c: System memory mapper.
//
// CEN64: Cycle-Accurate Nintendo 64 Emulator.
// Copyright (C) 2015, Tyler J. Stachecki.
//
// This file is subject to the terms and conditions defined in
// 'LICENSE', which is part of this source code package.
//

#include "common.h"
#include "memorymap.h"

static void fixup(struct memory_map *, struct memory_map_node *);
static void rotate_left(struct memory_map *, struct memory_map_node *);
static void rotate_right(struct memory_map *, struct memory_map_node *);

// Creates a new memory map.
void create_memory_map(struct memory_map *map) {
  map->next_map_index = 1;
  map->nil = map->mappings;
  map->root = map->nil;
}

// Rebalances the tree after a node is inserted.
void fixup(struct memory_map *map, struct memory_map_node *node) {
  struct memory_map_node *cur;

  // Rebalance the whole tree as needed. 
  while (node->parent->color == MEMORY_MAP_RED) {
    if (node->parent == node->parent->parent->left) {
      cur = node->parent->parent->right;

      // Case 1: We only need to update colors.
      if (cur->color == MEMORY_MAP_RED) {
        node->parent->color = MEMORY_MAP_BLACK;
        cur->color = MEMORY_MAP_BLACK;
        node->parent->parent->color = MEMORY_MAP_RED;
        node = node->parent->parent;
      }

      else {

        // Case 2: We need to perform a left rotation.
        if (node == node->parent->right) {
          node = node->parent;
          rotate_left(map, node);
        }

        // Case 3: We need to perform a right rotation.
        node->parent->color = MEMORY_MAP_BLACK;
        node->parent->parent->color = MEMORY_MAP_RED;
        rotate_right(map, node->parent->parent);
      }
    }

    else {
      cur = node->parent->parent->left;

      // Case 1: We only need to update colors.
      if (cur->color == MEMORY_MAP_RED) {
        node->parent->color = MEMORY_MAP_BLACK;
        cur->color = MEMORY_MAP_BLACK;
        node->parent->parent->color = MEMORY_MAP_RED;
        node = node->parent->parent;
      }

      else {

        // Case 2: We need to perform a right rotation.
        if (node == node->parent->left) {
          node = node->parent;
          rotate_right(map, node);
        }

        // Case 3: We need to perform a left rotation.
        node->parent->color = MEMORY_MAP_BLACK;
        node->parent->parent->color = MEMORY_MAP_RED;
        rotate_left(map, node->parent->parent);
      }
    }
  }

  // When we rebalanced the tree, we might have accidentally colored
  // the root red, so unconditionally color if back after rebalancing.
  map->root->color = MEMORY_MAP_BLACK;
}

// Inserts a mapping into the tree.
int map_address_range(struct memory_map *map, uint32_t start, uint32_t length,
  void *instance, memory_rd_function on_read, memory_wr_function on_write) {
  struct memory_map_node *check = map->root;
  struct memory_map_node *cur = map->nil;
  uint32_t end = start + length - 1;

  struct memory_map_node *new_node;
  struct memory_mapping mapping;

  // Make sure we have enough space in the map.
  const unsigned num_mappings = sizeof(map->mappings) /
    sizeof(map->mappings[0]) - 1;

  if (unlikely(map->next_map_index >= num_mappings)) {
    debug("map_address_range: Out of free mappings.");
    return 1;
  }

  new_node =  &map->mappings[map->next_map_index++];

  // Walk down the tree.
  while (check != map->nil) {
    cur = check;

    check = (start < cur->mapping.start)
      ? check->left : check->right;
  }

  // Insert the entry.
  if (cur == map->nil)
    map->root = new_node;

  else if (start < cur->mapping.start)
    cur->left = new_node;
  else
    cur->right = new_node;

  new_node->left = map->nil;
  new_node->right = map->nil;
  new_node->parent = cur;

  // Initialize the entry.
  mapping.instance = instance;
  mapping.on_read = on_read;
  mapping.on_write = on_write;

  mapping.end = end;
  mapping.length = length;
  mapping.start = start;

  new_node->mapping = mapping;

  // Rebalance the tree.
  new_node->color = MEMORY_MAP_RED;
  fixup(map, new_node);
  return 0;
}

// Returns a pointer to a region given an address.
const struct memory_mapping *resolve_mapped_address(
  const struct memory_map *map, uint32_t address) {
  const struct memory_map_node *cur = map->root;

  do {
    if (address < cur->mapping.start)
      cur = cur->left;
    else if (address > cur->mapping.end)
      cur = cur->right;

    else
      return &cur->mapping;
  } while (cur != map->nil);

  return NULL;
}

// Performs a left rotation centered at n.
static void rotate_left(struct memory_map *map, struct memory_map_node *n) {
  struct memory_map_node *y = n->right;

  // Turn y's left subtree into n's right subtree.
  n->right = y->left;

  if (y->left != map->nil)
    y->left->parent = n;

  // Link n's parent to y.
  y->parent = n->parent;

  if (n->parent == map->nil)
    map->root = y;
  else if (n == n->parent->left)
    n->parent->left = y;
  else
    n->parent->right = y;

  // Put n on y's left.
  y->left = n;
  n->parent = y;
}

// Performs a right rotation centered at n.
static void rotate_right(struct memory_map *map, struct memory_map_node *n) {
  struct memory_map_node *y = n->left;

  // Turn y's right subtree into n's left subtree.
  n->left = y->right;

  if (y->right != map->nil)
    y->right->parent = n;

  // Link n's parent to y.
  y->parent = n->parent;

  if (n->parent == map->nil)
    map->root = y;
  else if (n == n->parent->left)
    n->parent->left = y;
  else
    n->parent->right = y;

  // Put n on y's right.
  y->right = n;
  n->parent = y;
}

