/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2017 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU General Public License, as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any
 * later version. Please see the file LICENSE-GPL for details.
 *
 * Web Page: http://brltty.com/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#include "addresses.h"
#include "log.h"
#include "dynld.h"
#include "program.h"

typedef struct {
  void *address;
  char name[0];
} AddressEntry;

static AddressEntry **addressTable = NULL;
static int addressCount = 0;
static int addressLimit = 0;

static void
exitAddressTable (void *data) {
  if (addressTable) free(addressTable);

  addressTable = NULL;
  addressCount = 0;
  addressLimit = 0;
}

static int
findAddressIndex (int *index, void *address) {
  int first = 0;
  int last = addressCount - 1;

  while (first <= last) {
    int current = (first + last) / 2;
    AddressEntry *entry = addressTable[current];

    if (entry->address == address) {
      *index = current;
      return 1;
    }

    if (address < entry->address) {
      last = current - 1;
    } else {
      first = current + 1;
    }
  }

  *index = first;
  return 0;
}

static void
moveAddressSlice (int to, int from, int count) {
  memmove(&addressTable[to], &addressTable[from], ARRAY_SIZE(addressTable, count));
}

static int
insertAddressEntry (int index, AddressEntry *entry) {
  if (addressCount == addressLimit) {
    int newLimit = addressLimit + 1;
    AddressEntry **newTable = realloc(addressTable, ARRAY_SIZE(newTable, newLimit));

    if (!newTable) {
      logMallocError();
      return 0;
    }

    if (!addressTable) {
      onProgramExit("address-table", exitAddressTable, NULL);
    }

    addressTable = newTable;
    addressLimit = newLimit;
  }

  moveAddressSlice(index+1, index, (addressCount++ - index));
  addressTable[index] = entry;
  return 1;
}

static void
removeAddressEntry (int index) {
  free(addressTable[index]);
  moveAddressSlice(index, index+1, (--addressCount - index));
}

int
setAddressName (void *address, const char *format, ...) {
  char name[0X1000];
  AddressEntry *entry;
  size_t size;

  {
    va_list arguments;

    va_start(arguments, format);
    vsnprintf(name, sizeof(name), format, arguments);
    va_end(arguments);
  }

  size = sizeof(*entry) + strlen(name) + 1;

  if ((entry = malloc(size))) {
    int index;

    memset(entry, 0, sizeof(*entry));
    entry->address = address;
    strcpy(entry->name, name);

    if (findAddressIndex(&index, address)) {
      free(addressTable[index]);
      addressTable[index] = entry;
      return 1;
    }

    if (insertAddressEntry(index, entry)) return 1;

    free(entry);
  } else {
    logMallocError();
  }

  return 0;
}

void
unsetAddressName (void *address) {
  int index;

  if (findAddressIndex(&index, address)) {
    removeAddressEntry(index);
  }
}

const char *
getAddressName (void *address, ptrdiff_t *offset) {
  {
    int index;

    if (findAddressIndex(&index, address)) {
      if (offset) *offset = 0;
      return addressTable[index]->name;
    }
  }

  return getSharedSymbolName(address, offset);
}
