/*
 * 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-2021 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <string.h>

#include "log.h"
#include "strfmt.h"
#include "variables.h"
#include "queue.h"
#include "utf8.h"

typedef struct {
  const wchar_t *characters;
  int length;
} CharacterString;

static void
initializeCharacterString (CharacterString *string) {
  string->characters = WS_C("\0");
  string->length = 0;
}

static void
clearCharacterString (CharacterString *string) {
  if (string->length > 0) free((void *)string->characters);
  initializeCharacterString(string);
}

static int
setCharacterString (CharacterString *string, const wchar_t *characters, int length) {
  if (!length) {
    clearCharacterString(string);
  } else {
    wchar_t *newCharacters;

    if (!(newCharacters = malloc(ARRAY_SIZE(newCharacters, (length + 1))))) {
      logMallocError();
      return 0;
    }

    wmemcpy(newCharacters, characters, length);
    newCharacters[length] = WC_C('\0');

    clearCharacterString(string);
    string->characters = newCharacters;
    string->length = length;
  }

  return 1;
}

static void
getCharacterString (const CharacterString *string, const wchar_t **characters, int *length) {
  *characters = string->characters;
  *length = string->length;
}

struct VariableStruct {
  CharacterString name;
  CharacterString value;
};

struct VariableNestingLevelStruct {
  const char *name;
  VariableNestingLevel *previous;
  Queue *variables;
  unsigned int references;
};

VariableNestingLevel *
claimVariableNestingLevel (VariableNestingLevel *vnl) {
  vnl->references += 1;
  return vnl;
}

static void
deallocateVariable (void *item, void *data) {
  Variable *variable = item;

  clearCharacterString(&variable->name);
  clearCharacterString(&variable->value);
  free(variable);
}

VariableNestingLevel *
newVariableNestingLevel (VariableNestingLevel *previous, const char *name) {
  VariableNestingLevel *vnl;

  if ((vnl = malloc(sizeof(*vnl)))) {
    memset(vnl, 0, sizeof(*vnl));
    vnl->name = name;
    vnl->references = 0;

    if ((vnl->variables = newQueue(deallocateVariable, NULL))) {
      if ((vnl->previous = previous)) claimVariableNestingLevel(previous);
      return vnl;
    }

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

  return NULL;
}

static void
destroyVariableNestingLevel (VariableNestingLevel *vnl) {
  deallocateQueue(vnl->variables);
  free(vnl);
}

VariableNestingLevel *
removeVariableNestingLevel (VariableNestingLevel *vnl) {
  VariableNestingLevel *previous = vnl->previous;
  if (!--vnl->references) destroyVariableNestingLevel(vnl);
  return previous;
}

void
releaseVariableNestingLevel (VariableNestingLevel *vnl) {
  while (vnl && !--vnl->references) {
    VariableNestingLevel *previous = vnl->previous;
    destroyVariableNestingLevel(vnl);
    vnl = previous;
  }
}

static void
listVariableLine (const char *line) {
  logMessage(LOG_NOTICE, "%s", line);
}

static int
listVariable (void *item, void *data) {
  const Variable *variable = item;

  char line[0X100];
  STR_BEGIN(line, sizeof(line));

  STR_PRINTF("variable: ");
  STR_PRINTF("%.*" PRIws, variable->name.length, variable->name.characters);
  STR_PRINTF(" = ");
  STR_PRINTF("%.*" PRIws, variable->value.length, variable->value.characters);

  STR_END;
  listVariableLine(line);

  return 0;
}

void
listVariables (VariableNestingLevel *from) {
  listVariableLine("begin variable listing");

  while (from) {
    {
      char header[0X100];
      STR_BEGIN(header, sizeof(header));

      STR_PRINTF("variable nesting level:");
      if (from->name) STR_PRINTF(" %s", from->name);
      if (from->references != 1) STR_PRINTF(" Refs:%u", from->references);

      STR_END;
      listVariableLine(header);
    }

    processQueue(from->variables, listVariable, NULL);
    from = from->previous;
  }

  listVariableLine("end variable listing");
}

static int
testVariableName (const void *item, void *data) {
  const Variable *variable = item;
  const CharacterString *key = data;

  if (variable->name.length == key->length) {
    if (wmemcmp(variable->name.characters, key->characters, key->length) == 0) {
      return 1;
    }
  }

  return 0;
}

static Variable *
findVariable (VariableNestingLevel *vnl, const wchar_t *name, int length, int create) {
  Variable *variable;

  {
    CharacterString key = {
      .characters = name,
      .length = length
    };

    if ((variable = findItem(vnl->variables, testVariableName, &key))) return variable;
  }

  if (create) {
    if ((variable = malloc(sizeof(*variable)))) {
      memset(variable, 0, sizeof(*variable));
      initializeCharacterString(&variable->name);
      initializeCharacterString(&variable->value);

      if (setCharacterString(&variable->name, name, length)) {
        if (enqueueItem(vnl->variables, variable)) {
          return variable;
        }

        clearCharacterString(&variable->name);
      }

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

  return NULL;
}

const Variable *
findReadableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length) {
  while (vnl) {
    Variable *variable = findVariable(vnl, name, length, 0);
    if (variable) return variable;
    vnl = vnl->previous;
  }

  return NULL;
}

Variable *
findWritableVariable (VariableNestingLevel *vnl, const wchar_t *name, int length) {
  return findVariable(vnl, name, length, 1);
}

void
deleteVariables (VariableNestingLevel *vnl) {
  deleteElements(vnl->variables);
}

int
setVariable (Variable *variable, const wchar_t *value, int length) {
  return setCharacterString(&variable->value, value, length);
}

void
getVariableName (const Variable *variable, const wchar_t **characters, int *length) {
  getCharacterString(&variable->name, characters, length);
}

void
getVariableValue (const Variable *variable, const wchar_t **characters, int *length) {
  getCharacterString(&variable->value, characters, length);
}

int
setStringVariable (VariableNestingLevel *vnl, const char *name, const char *value) {
  size_t nameLength = countUtf8Characters(name);
  wchar_t nameBuffer[nameLength + 1];

  size_t valueLength = countUtf8Characters(value);
  wchar_t valueBuffer[valueLength + 1];

  {
    const char *utf8 = name;
    wchar_t *wc = nameBuffer;
    convertUtf8ToWchars(&utf8, &wc, ARRAY_COUNT(nameBuffer));
  }

  {
    const char *utf8 = value;
    wchar_t *wc = valueBuffer;
    convertUtf8ToWchars(&utf8, &wc, ARRAY_COUNT(valueBuffer));
  }

  Variable *variable = findVariable(vnl, nameBuffer, nameLength, 1);

  if (variable) {
    if (setVariable(variable, valueBuffer, valueLength)) {
      return 1;
    }
  }

  return 0;
}

int
setStringVariables (VariableNestingLevel *vnl, const VariableInitializer *initializers) {
  if (initializers) {
    const VariableInitializer *initializer = initializers;
     
    while (initializer->name) {
      if (!setStringVariable(vnl, initializer->name, initializer->value)) return 0;
      initializer += 1;
    }
  }

  return 1;
}

VariableNestingLevel *
getGlobalVariables (int create) {
  static VariableNestingLevel *globalVariables = NULL;

  if (!globalVariables) {
    VariableNestingLevel *vnl;

    if (!(vnl = newVariableNestingLevel(NULL, "global"))) {
      return NULL;
    }

    claimVariableNestingLevel(vnl);
    globalVariables = vnl;
  }

  return globalVariables;
}

int
setGlobalVariable (const char *name, const char *value) {
  VariableNestingLevel *vnl = getGlobalVariables(1);
  if (!vnl) return 0;
  return setStringVariable(vnl, name, value);
}
