/*
 * 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 "parameters.h"
#include "api_control.h"
#include "cmd_queue.h"
#include "cmd_toggle.h"
#include "brl_cmds.h"
#include "prefs.h"
#include "scr.h"
#include "scr_special.h"
#include "scr_menu.h"
#include "alert.h"
#include "tune.h"
#include "core.h"

typedef enum {
  TOGGLE_ERROR,
  TOGGLE_SAME,
  TOGGLE_OFF,
  TOGGLE_ON
} ToggleResult;

static ToggleResult
toggleBit (
  int *bits, int bit, int command,
  AlertIdentifier offAlert,
  AlertIdentifier onAlert
) {
  int oldBits = *bits;

  switch (command & BRL_FLG_TOGGLE_MASK) {
    case 0:
      *bits ^= bit;
      break;

    case BRL_FLG_TOGGLE_ON:
      *bits |= bit;
      break;

    case BRL_FLG_TOGGLE_OFF:
      *bits &= ~bit;
      break;

    default:
      alert(ALERT_COMMAND_REJECTED);
      return TOGGLE_ERROR;
  }

  {
    int isOn = (*bits & bit) != 0;
    AlertIdentifier identifier = isOn? onAlert: offAlert;

    alert(identifier);
    if (*bits != oldBits) return isOn? TOGGLE_ON: TOGGLE_OFF;

    tuneWait(TUNE_TOGGLE_REPEAT_DELAY);
    alert(identifier);
    return TOGGLE_SAME;
  }
}

static ToggleResult
toggleSetting (
  unsigned char *setting, int command,
  AlertIdentifier offAlert,
  AlertIdentifier onAlert
) {
  const int bit = 1;
  int bits = *setting? bit: 0;
  ToggleResult result = toggleBit(&bits, bit, command, offAlert, onAlert);

  *setting = (bits & bit)? bit: 0;
  return result;
}

static ToggleResult
togglePreferenceSetting (unsigned char *setting, int command) {
  ToggleResult result = toggleSetting(setting, command, ALERT_TOGGLE_OFF, ALERT_TOGGLE_ON);
  if (result > TOGGLE_SAME) menuScreenUpdated();
  return result;
}

static ToggleResult
toggleModeSetting (unsigned char *setting, int command) {
  return toggleSetting(setting, command, ALERT_NONE, ALERT_NONE);
}

static ToggleResult
toggleFunctionalSetting (
  int command,
  int (*get) (void),
  void (*set) (int value)
) {
  const int bit = 1;
  int bits = get()? bit: 0;

  ToggleResult result = toggleBit(
    &bits, bit, command,
    ALERT_TOGGLE_OFF, ALERT_TOGGLE_ON
  );

  set(!!(bits & bit));
  return result;
}

static int
handleToggleCommands (int command, void *data) {
  switch (command & BRL_MSK_CMD) {
    case BRL_CMD_SKPIDLNS:
      togglePreferenceSetting(&prefs.skipIdenticalLines, command);
      api.updateParameter(BRLAPI_PARAM_SKIP_IDENTICAL_LINES, 0);
      break;

    case BRL_CMD_SKPBLNKWINS:
      togglePreferenceSetting(&prefs.skipBlankBrailleWindows, command);
      break;

    case BRL_CMD_SLIDEWIN:
      togglePreferenceSetting(&prefs.slidingBrailleWindow, command);
      break;

    case BRL_CMD_SIXDOTS:
      togglePreferenceSetting(&prefs.brailleVariant, command);
      onBrailleVariantUpdated();
      break;

    case BRL_CMD_CONTRACTED:
      toggleFunctionalSetting(command, isContractedBraille, setContractedBraille);
      break;

    case BRL_CMD_COMPBRL6:
      toggleFunctionalSetting(command, isSixDotComputerBraille, setSixDotComputerBraille);
      break;

    case BRL_CMD_CSRTRK:
      toggleSetting(&ses->trackScreenCursor, command, ALERT_CURSOR_UNLINKED, ALERT_CURSOR_LINKED);

      if (ses->trackScreenCursor) {
#ifdef ENABLE_SPEECH_SUPPORT
        if (spk.track.isActive && (scr.number == spk.track.screenNumber)) {
          spk.track.speechLocation = SPK_LOC_NONE;
        } else
#endif /* ENABLE_SPEECH_SUPPORT */

        {
          trackScreenCursor(1);
        }
      }
      break;

    case BRL_CMD_CSRSIZE:
      togglePreferenceSetting(&prefs.screenCursorStyle, command);
      break;

    case BRL_CMD_CSRVIS:
      togglePreferenceSetting(&prefs.showScreenCursor, command);
      break;

    case BRL_CMD_CSRHIDE:
      toggleModeSetting(&ses->hideScreenCursor, command);
      break;

    case BRL_CMD_CSRBLINK:
      togglePreferenceSetting(&prefs.blinkingScreenCursor, command);
      break;

    case BRL_CMD_ATTRVIS:
      togglePreferenceSetting(&prefs.showAttributes, command);
      break;

    case BRL_CMD_ATTRBLINK:
      togglePreferenceSetting(&prefs.blinkingAttributes, command);
      break;

    case BRL_CMD_CAPBLINK:
      togglePreferenceSetting(&prefs.blinkingCapitals, command);
      break;

    case BRL_CMD_AUTOREPEAT:
      togglePreferenceSetting(&prefs.autorepeatEnabled, command);
      break;

    case BRL_CMD_BRLKBD:
      togglePreferenceSetting(&prefs.brailleKeyboardEnabled, command);
      break;

    case BRL_CMD_BRLUCDOTS:
      togglePreferenceSetting(&prefs.brailleTypingMode, command);
      break;

    case BRL_CMD_TOUCH_NAV:
      togglePreferenceSetting(&prefs.touchNavigation, command);
      break;

    case BRL_CMD_TUNES:
      togglePreferenceSetting(&prefs.alertTunes, command);        /* toggle sound on/off */
      api.updateParameter(BRLAPI_PARAM_AUDIBLE_ALERTS, 0);
      break;

    case BRL_CMD_AUTOSPEAK:
      togglePreferenceSetting(&prefs.autospeak, command);
      break;

    case BRL_CMD_ASPK_SEL_LINE:
      togglePreferenceSetting(&prefs.autospeakSelectedLine, command);
      break;

    case BRL_CMD_ASPK_EMP_LINE:
      togglePreferenceSetting(&prefs.autospeakEmptyLine, command);
      break;

    case BRL_CMD_ASPK_SEL_CHAR:
      togglePreferenceSetting(&prefs.autospeakSelectedCharacter, command);
      break;

    case BRL_CMD_ASPK_INS_CHARS:
      togglePreferenceSetting(&prefs.autospeakInsertedCharacters, command);
      break;

    case BRL_CMD_ASPK_DEL_CHARS:
      togglePreferenceSetting(&prefs.autospeakDeletedCharacters, command);
      break;

    case BRL_CMD_ASPK_REP_CHARS:
      togglePreferenceSetting(&prefs.autospeakReplacedCharacters, command);
      break;

    case BRL_CMD_ASPK_CMP_WORDS:
      togglePreferenceSetting(&prefs.autospeakCompletedWords, command);
      break;

    case BRL_CMD_ASPK_INDENT:
      togglePreferenceSetting(&prefs.autospeakLineIndent, command);
      break;

    case BRL_CMD_SHOW_CURR_LOCN:
      togglePreferenceSetting(&prefs.showSpeechCursor, command);
      break;

    case BRL_CMD_INFO:
      if (haveStatusCells() || !(textMaximized || statusCount)) {
        toggleModeSetting(&infoMode, command);
      } else {
        ToggleResult result = toggleModeSetting(&textMaximized, command);
        if (result > TOGGLE_SAME) reconfigureBrailleWindow();
      }
      break;

    case BRL_CMD_DISPMD:
      toggleModeSetting(&ses->displayMode, command);
      break;

    case BRL_CMD_FREEZE: {
      unsigned char setting;

      if (isMainScreen()) {
        setting = 0;
      } else if (isSpecialScreen(SCR_FROZEN)) {
        setting = 1;
      } else {
        alert(ALERT_COMMAND_REJECTED);
        break;
      }

      switch (toggleSetting(&setting, command, ALERT_SCREEN_UNFROZEN, ALERT_SCREEN_FROZEN)) {
        case TOGGLE_OFF:
          deactivateSpecialScreen(SCR_FROZEN);
          break;

        case TOGGLE_ON:
          if (!activateSpecialScreen(SCR_FROZEN)) alert(ALERT_COMMAND_REJECTED);
          break;

        default:
          break;
      }

      break;
    }

    default:
      return 0;
  }

  return 1;
}

int
addToggleCommands (void) {
  return pushCommandHandler("toggle", KTB_CTX_DEFAULT,
                            handleToggleCommands, NULL, NULL);
}
