/*
 * 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-2024 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 <stdio.h>

#include "log.h"
#include "strfmt.h"
#include "ktb.h"
#include "ktb_internal.h"
#include "ktb_inspect.h"

typedef struct {
  KeyTable *table;
  const KeyContext *ctx;
  const char *path;
} KeyTableAuditorParameters;

#define KEY_TABLE_AUDITOR(name) int name (const KeyTableAuditorParameters *kta)
typedef KEY_TABLE_AUDITOR(KeyTableAuditor);

static void
reportKeyTableAudit (const char *audit) {
  logMessage(LOG_WARNING, "%s", audit);
}

static
STR_BEGIN_FORMATTER(
  formatKeyTableAuditPrefix,
  const KeyTableAuditorParameters *kta,
  const char *problem
)
  if (kta->path) STR_PRINTF("%s: ", kta->path);
  STR_PRINTF("%s", problem);
  if (kta->ctx) STR_PRINTF(": %" PRIws, kta->ctx->name);
STR_END_FORMATTER

static KEY_TABLE_AUDITOR(reportKeyContextProblems) {
  int ok = 1;

  if (kta->ctx->name && !kta->ctx->isSpecial) {
    const char *problem = NULL;

    if (!kta->ctx->isDefined) {
      problem = "undefined context";
    } else if (!kta->ctx->isReferenced) {
      problem = "unreferenced context";
    } else if (!(kta->ctx->keyBindings.count ||
                 kta->ctx->mappedKeys.count ||
                 kta->ctx->mappedKeys.superimpose ||
                 kta->ctx->hotkeys.count)) {
      problem = "empty context";
    }

    if (problem) {
      ok = 0;

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

      STR_FORMAT(formatKeyTableAuditPrefix, kta, problem);

      STR_END;
      reportKeyTableAudit(audit);
    }
  }

  return ok;
}

static KEY_TABLE_AUDITOR(reportDuplicateKeyBindings) {
  int ok = 1;
  const KeyBinding *binding = kta->ctx->keyBindings.table;
  const KeyBinding *end = binding + kta->ctx->keyBindings.count;

  while (binding < end) {
    if (binding->flags & KBF_DUPLICATE) {
      ok = 0;

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

      STR_FORMAT(formatKeyTableAuditPrefix, kta, "duplicate key binding");
      STR_PRINTF(": ");
      STR_FORMAT(formatKeyCombination, kta->table, &binding->keyCombination);

      STR_END;
      reportKeyTableAudit(audit);
    }

    binding += 1;
  }

  return ok;
}

static void
reportKeyProblem (const KeyTableAuditorParameters *kta, const KeyValue *key, const char *problem) {
  char audit[0X100];
  STR_BEGIN(audit, sizeof(audit));

  STR_FORMAT(formatKeyTableAuditPrefix, kta, problem);
  STR_PRINTF(": ");
  STR_FORMAT(formatKeyName, kta->table, key);

  STR_END;
  reportKeyTableAudit(audit);
}

static KEY_TABLE_AUDITOR(reportDuplicateHotkeys) {
  int ok = 1;
  const HotkeyEntry *hotkey = kta->ctx->hotkeys.table;
  const HotkeyEntry *end = hotkey + kta->ctx->hotkeys.count;

  while (hotkey < end) {
    if (hotkey->flags & HKF_DUPLICATE) {
      ok = 0;
      reportKeyProblem(kta, &hotkey->keyValue, "duplicate hotkey");
    }

    hotkey += 1;
  }

  return ok;
}

static KEY_TABLE_AUDITOR(reportDuplicateMappedKeys) {
  int ok = 1;
  const MappedKeyEntry *map = kta->ctx->mappedKeys.table;
  const MappedKeyEntry *end = map + kta->ctx->mappedKeys.count;

  while (map < end) {
    if (map->flags & MKF_DUPLICATE) {
      ok = 0;
      reportKeyProblem(kta, &map->keyValue, "duplicate mapped key");
    }

    map += 1;
  }

  return ok;
}

int
auditKeyTable (KeyTable *table, const char *path) {
  int ok = 1;

  for (unsigned int context=0; context<table->keyContexts.count; context+=1) {
    const KeyContext *ctx = getKeyContext(table, context);

    if (ctx) {
      static KeyTableAuditor *const auditors[] = {
        reportKeyContextProblems,
        reportDuplicateKeyBindings,
        reportDuplicateHotkeys,
        reportDuplicateMappedKeys,
        NULL
      };

      const KeyTableAuditorParameters kta = {
        .table = table,
        .ctx = ctx,
        .path = path
      };

      for (
        KeyTableAuditor *const *auditor=auditors;
        *auditor!=NULL; auditor+=1
      ) {
        if (!(*auditor)(&kta)) ok = 0;
      }
    }
  }

  return ok;
}
