/*
 * 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 "strfmt.h"
#include "cmd_queue.h"
#include "cmd_miscellaneous.h"
#include "cmd_utils.h"
#include "brl_cmds.h"
#include "prefs.h"
#include "scr_special.h"
#include "message.h"
#include "alert.h"
#include "core.h"

#ifdef ENABLE_SPEECH_SUPPORT
static
STR_BEGIN_FORMATTER(formatSpeechDate, const TimeFormattingData *fmt)
  const char *yearFormat = "%u";
  const char *monthFormat = "%s";
  const char *dayFormat = "%u";

  uint16_t year = fmt->components.year;
  uint8_t day = fmt->components.day + 1;

  char month[0X20];
  size_t length = strftime(month, sizeof(month), "%B", &fmt->components.time);
  month[length] = 0;

  switch (prefs.dateFormat) {
    default:
    case dfYearMonthDay:
      STR_PRINTF(yearFormat, year);
      STR_PRINTF(" ");
      STR_PRINTF(monthFormat, month);
      STR_PRINTF(" ");
      STR_PRINTF(dayFormat, day);
      break;

    case dfMonthDayYear:
      STR_PRINTF(monthFormat, month);
      STR_PRINTF(" ");
      STR_PRINTF(dayFormat, day);
      STR_PRINTF(", ");
      STR_PRINTF(yearFormat, year);
      break;

    case dfDayMonthYear:
      STR_PRINTF(dayFormat, day);
      STR_PRINTF(" ");
      STR_PRINTF(monthFormat, month);
      STR_PRINTF(", ");
      STR_PRINTF(yearFormat, year);
      break;
  }
STR_END_FORMATTER

static
STR_BEGIN_FORMATTER(formatSpeechTime, const TimeFormattingData *fmt)
  unsigned int hours = fmt->components.hour;
  unsigned int minutes = fmt->components.minute;
  unsigned int seconds = fmt->components.second;

  if (minutes > 0) {
    STR_PRINTF("%u", hours);
    if (minutes < 10) STR_PRINTF(" 0");
    STR_PRINTF(" %u", minutes);
  } else if (!fmt->meridian) {
    // xgettext: This is how to say when the time is exactly on (i.e. zero minutes after) an hour.
    // xgettext: (%u represents the number of hours)
    STR_PRINTF(ngettext("%u o'clock", "%u o'clock", hours), hours);
  }

  if (fmt->meridian) {
    const char *character = fmt->meridian;
    while (*character) STR_PRINTF(" %c", *character++);
  }

  if (prefs.showSeconds) {
    STR_PRINTF(", ");

    if (seconds == 0) {
      // xgettext: This is the term used when the time is exactly on (i.e. zero seconds after) a minute.
      STR_PRINTF("%s", gettext("exactly"));
    } else {
      STR_PRINTF("%s", gettext("and"));

      // xgettext: This is a number (%u) of seconds (time units).
      STR_PRINTF(ngettext("%u second", "%u seconds", seconds), seconds);
    }
  }
STR_END_FORMATTER

static void
speakTime (const TimeFormattingData *fmt) {
  char announcement[0X100];
  char time[0X80];

  STR_BEGIN(announcement, sizeof(announcement));
  formatSpeechTime(time, sizeof(time), fmt);

  if (prefs.datePosition == dpNone) {
    STR_PRINTF("%s", time);
  } else {
    char date[0X40];
    formatSpeechDate(date, sizeof(date), fmt);

    switch (prefs.datePosition) {
      case dpBeforeTime:
        STR_PRINTF("%s, %s", date, time);
        break;

      case dpAfterTime:
        STR_PRINTF("%s, %s", time, date);
        break;

      default:
        STR_PRINTF("%s", date);
        break;
    }
  }

  STR_PRINTF(".");
  STR_END;
  sayString(&spk, announcement, SAY_OPT_MUTE_FIRST);
}
#endif /* ENABLE_SPEECH_SUPPORT */

static void
showTime (const TimeFormattingData *fmt) {
  char buffer[0X80];

  formatBrailleTime(buffer, sizeof(buffer), fmt);
  message(NULL, buffer, MSG_SILENT);
}

static int
handleMiscellaneousCommands (int command, void *data) {
  switch (command & BRL_MSK_CMD) {
    case BRL_CMD_RESTARTBRL:
      brl.hasFailed = 1;
      break;

    case BRL_CMD_BRL_STOP:
      disableBrailleDriver(gettext("braille driver stopped"));
      break;

    case BRL_CMD_BRL_START:
      enableBrailleDriver();
      break;

    case BRL_CMD_SCR_STOP:
      disableScreenDriver(gettext("screen driver stopped"));
      break;

    case BRL_CMD_SCR_START:
      enableScreenDriver();
      break;

    case BRL_CMD_HELP: {
      int ok = 0;
      unsigned int pageNumber;

      if (isSpecialScreen(SCR_HELP)) {
        pageNumber = getHelpPageNumber() + 1;
        ok = 1;
      } else {
        pageNumber = haveSpecialScreen(SCR_HELP)? getHelpPageNumber(): 1;
        if (!activateSpecialScreen(SCR_HELP)) pageNumber = 0;
      }

      if (pageNumber) {
        unsigned int pageCount = getHelpPageCount();

        while (pageNumber <= pageCount) {
          if (setHelpPageNumber(pageNumber))
            if (getHelpLineCount())
              break;

          pageNumber += 1;
        }

        if (pageNumber > pageCount) {
          deactivateSpecialScreen(SCR_HELP);
        } else {
          ok = 1;
        }

        updateSessionAttributes();
      }

      if (ok) {
        infoMode = 0;
      } else {
        message(NULL, gettext("help not available"), 0);
      }

      break;
    }

    case BRL_CMD_TIME: {
      TimeFormattingData fmt;
      getTimeFormattingData(&fmt);

#ifdef ENABLE_SPEECH_SUPPORT
      if (isAutospeakActive()) speakTime(&fmt);
#endif /* ENABLE_SPEECH_SUPPORT */

      showTime(&fmt);
      break;
    }

    case BRL_CMD_REFRESH: {
      if (canRefreshBrailleDisplay(&brl)) {
        if (refreshBrailleDisplay(&brl)) {
          break;
        }
      }

      alert(ALERT_COMMAND_REJECTED);
      break;
    }

    default: {
      int arg = command & BRL_MSK_ARG;

      switch (command & BRL_MSK_BLK) {
        case BRL_CMD_BLK(DESCCHAR): {
          int column, row;

          if (getCharacterCoordinates(arg, &row, &column, NULL, 0)) {
            char description[0X80];
            STR_BEGIN(description, sizeof(description));
            STR_FORMAT(formatCharacterDescription, column, row);
            STR_END;
            message(NULL, description, 0);
          } else {
            alert(ALERT_COMMAND_REJECTED);
          }

          break;
        }

        case BRL_CMD_BLK(REFRESH_LINE): {
          if (canRefreshBrailleRow(&brl)) {
            if (refreshBrailleRow(&brl, arg)) {
              break;
            }
          }

          alert(ALERT_COMMAND_REJECTED);
          break;
        }

        case BRL_CMD_BLK(ALERT):
          alert(arg);
          break;

        default:
          return 0;
      }

      break;
    }
  }

  return 1;
}

int
addMiscellaneousCommands (void) {
  return pushCommandHandler("miscellaneous", KTB_CTX_DEFAULT,
                            handleMiscellaneousCommands, NULL, NULL);
}
