;;-*-Lisp-*-
(in-package goal)

(defmethod call-on-load ((this generic-progress-state-entry))
  (when (and (nonzero? (-> this on-load)) (!= (-> this on-load) #f))
    ((-> this on-load)))
  (none))

(defmethod call-get-value-fn ((this menu-generic-boolean-option))
  (if (and (nonzero? (-> this get-value-fn)) (!= (-> this get-value-fn) #f))
    ((-> this get-value-fn))
    #f))

(defmethod call-on-confirm ((this menu-generic-boolean-option) (val symbol))
  (when (and (nonzero? (-> this on-confirm)) (!= (-> this on-confirm) #f))
    ((-> this on-confirm) val))
  (none))

(defmethod call-get-item-index-fn ((this menu-generic-carousel-option))
  (if (and (nonzero? (-> this get-item-index-fn)) (!= (-> this get-item-index-fn) #f))
    ((-> this get-item-index-fn))
    0))

(defmethod call-on-confirm ((this menu-generic-carousel-option) (val int) (the-progress progress))
  (when (and (nonzero? (-> this on-confirm)) (-> this on-confirm))
    ((-> this on-confirm) val the-progress))
  (none))

(defmethod call-on-confirm ((this menu-generic-button-option) (the-progress progress))
  (when (and (nonzero? (-> this on-confirm)) (-> this on-confirm))
    ((-> this on-confirm) the-progress))
  (none))

(defmethod call-on-confirm ((this menu-generic-confirm-option))
  (when (and (nonzero? (-> this on-confirm)) (!= (-> this on-confirm) #f))
    ((-> this on-confirm)))
  (none))

(defmethod call-get-value-fn ((this menu-generic-slider-option))
  (if (and (nonzero? (-> this get-value-fn)) (!= (-> this get-value-fn) #f))
    ((-> this get-value-fn))
    0.0))

(defmethod call-on-confirm ((this menu-generic-slider-option) (val float))
  (when (and (nonzero? (-> this on-confirm)) (!= (-> this on-confirm) #f))
    ((-> this on-confirm) val))
  (none))

(defmethod init! ((this progress-pc-generic-store))
  (set! (-> this clear-screen?) #f)
  (set! (-> this keybind-select-time) 0)
  (set! (-> this current-highscore-page-index) 0)
  (none))

(defmethod call-on-confirm ((this menu-generic-details-confirm-entry))
  (when (and (nonzero? (-> this on-confirm)) (!= (-> this on-confirm) #f))
    ((-> this on-confirm)))
  (none))

(defmethod navigate! ((this progress-pc-generic-store) (progress progress) (target menu-option-list) (on-load (function none)))
  (when (< (-> this history-stack-index) 9) ;; hard-coded length
    ;; if this is the first history entry, we push the current page as well
    ;; if it's not, then we update the current entry before proceeding to the next page
    (if (has-history? this)
      (let ((stack-entry (-> this history-stack (-> this history-stack-index))))
        (set! (-> stack-entry hover-index) (-> this current-menu-hover-index))
        (set! (-> stack-entry scroll-index) (the int (-> this current-menu-scroll-index)))
        (set! (-> stack-entry progress-id) (-> progress current)))
      (let ((stack-entry (-> this history-stack (-> this history-stack-index))))
        (set! (-> stack-entry ref) (-> progress current-options))
        (set! (-> stack-entry on-load) on-load)
        (set! (-> stack-entry hover-index) (-> this current-menu-hover-index))
        (set! (-> stack-entry scroll-index) (the int (-> this current-menu-scroll-index)))
        (set! (-> stack-entry progress-id) (-> progress current))))
    ;; push the target history entry now
    (inc! (-> this history-stack-index))
    ;; Create a new stack entry
    (let ((stack-entry (-> this history-stack (-> this history-stack-index))))
      (set! (-> stack-entry ref) target)
      (set! (-> stack-entry on-load) on-load)
      (set! (-> stack-entry hover-index) 0)
      (set! (-> stack-entry progress-id) 'generic-menu))
    ;; Update global state
    (set! (-> this clear-screen?) #t)
    (set! (-> progress next) 'generic-menu)
    (set! (-> progress selected-option) #f))
  (none))

(defmethod back! ((this progress-pc-generic-store) (progress progress))
  (set! (-> this history-stack-index) (max 0 (dec (-> this history-stack-index))))
  (set! (-> this clear-screen?) #t)
  (set! (-> progress next) 'generic-menu)
  (set! (-> progress selected-option) #f)
  (none))

(defmethod has-history? ((this progress-pc-generic-store))
  (> (-> this history-stack-index) 0))

(defmethod clear-history-if-empty! ((this progress-pc-generic-store))
  "if we have no history, just reset the entry.  This is so we don't preserve state after completely exiting the menu"
  (when (not (has-history? this))
    (set! (-> this history-stack-index) 0)
    (set! (-> this clear-screen?) #f)
    (set! (-> this current-menu-hover-index) 0)
    (set! (-> this current-menu-scroll-index) 0.0)
    (let ((stack-entry (-> this history-stack (-> this history-stack-index))))
      (set! (-> stack-entry hover-index) 0)
      (set! (-> stack-entry ref) #f)
      (set! (-> stack-entry on-load) #f)
      (set! (-> stack-entry progress-id) #f)))
  (none))

(defmethod clear-history! ((this progress-pc-generic-store))
  (set! (-> this history-stack-index) 0)
  (set! (-> this clear-screen?) #t)
  (set! (-> this current-menu-hover-index) 0)
  (set! (-> this current-menu-scroll-index) 0.0)
  (none))

(defmethod on-mount! ((this menu-generic-option))
  (set! (-> this mounted?) #t)
  (none))

;; TODO - call the parent methods, when there is an elegant way to do so (not having to know the ID)

(defmethod on-mount! ((this menu-generic-carousel-option))
  (set! (-> this mounted?) #t)
  (set! (-> this item-index) 0)
  (none))

(defmethod on-mount! ((this menu-generic-scrolling-page))
  (set! (-> this mounted?) #t)
  (set! (-> this selected-option-index) -1)
  (none))

(defmethod on-mount! ((this menu-generic-details-page))
  (set! (-> this mounted?) #t)
  (set! (-> this selected-entry-index) -1)
  (none))

(defmethod on-mount! ((this menu-generic-confirm-option))
  (set! (-> this mounted?) #t)
  (set! (-> this confirmed?) #f)
  (none))

(defun controller-keybind->input-index ((bind controller-keybind))
  (case bind
    (((controller-keybind l-analog-up)) 1)
    (((controller-keybind l-analog-down)) 1)
    (((controller-keybind l-analog-left)) 0)
    (((controller-keybind l-analog-right)) 0)
    (((controller-keybind r-analog-up)) 3)
    (((controller-keybind r-analog-down)) 3)
    (((controller-keybind r-analog-left)) 2)
    (((controller-keybind r-analog-right)) 2)
    (else (the int bind))))

(defmethod on-mount! ((this menu-generic-details-keybind-entry))
  (set! (-> this mounted?) #t)
  (set! (-> this bind-info port) 0) ;; always port 0 for now
  (set! (-> this bind-info device-type) (the int (-> this device-type)))
  (set! (-> this bind-info for-buttons?) #t)
  (set! (-> this bind-info analog-min-range?) #f)
  (set! (-> this bind-info input-idx) (controller-keybind->input-index (-> this keybind)))
  (case (-> this keybind)
    (((controller-keybind l-analog-up) (controller-keybind l-analog-down) (controller-keybind l-analog-left) (controller-keybind l-analog-right)
      (controller-keybind r-analog-up) (controller-keybind r-analog-down) (controller-keybind r-analog-left) (controller-keybind r-analog-right))
     (set! (-> this bind-info for-buttons?) #f)
     (case (-> this keybind)
      (((controller-keybind l-analog-up) (controller-keybind l-analog-left) (controller-keybind r-analog-up) (controller-keybind r-analog-left))
       (set! (-> this bind-info analog-min-range?) #t)))))
  (none))

;; Progress Code Overrides

(defmethod respond-to-cpad ((this progress))
  (mc-get-slot-info 0 *progress-save-info*)
  ;; ND originally did this...called this every frame in the game options, looked like a hack
  ;; there's probably a way to consistently do it synchronously
  (load-level-text-files (the-as int (-> *pc-settings* text-language)))
  (when (-> this current-options)
    (let ((option-array (-> this current-options options))
          (has-generic-history? (has-history? *progress-pc-generic-store*)))
      (when (and option-array (= (-> this menu-transition) 0.0))
        (let ((in-generic-page? (and (= (-> option-array length) 1)
                                     (or (type? (-> option-array 0) menu-generic-scrolling-page)
                                         (type? (-> option-array 0) menu-generic-details-page)))))
         (cond
          ;; If it's a scrolling menu, we have to pull from it's list of items
          (in-generic-page?
           ;; update the scrolling list
           (respond-progress
            (the-as menu-option (-> option-array 0))
            this
            (and (= (-> this menu-transition) 0.0) (-> this selected-option))))
          ;; Do a bounds check to avoid crashing
          ((>= (-> this option-index) (-> option-array length))
           (format 0 "respond-to-cpad:progress: Option index ~D out of bounds~%" (-> this option-index)))
          (else
           (respond-progress
            (the-as menu-option (-> option-array (-> this option-index)))
            this
            (and (= (-> this menu-transition) 0.0) (-> this selected-option)))))
        (cond
          ((-> this selected-option)
           (cond
             ((cpad-pressed? 0 confirm)
              (set! (-> this selected-option) #f)
              )
             ((cpad-pressed? 0 triangle)
              (when (not in-generic-page?)
                (if (= (-> this current-options) *main-options*)
                  (sound-play "window-contract")
                  (sound-play "generic-beep")))
              (set! (-> this selected-option) #f))))
          (else
            (cond
              ((cpad-pressed? 0 up l-analog-up)
               (cond
                 ((= (-> this current-options) *main-options*)
                  (sound-play "ring-select"))
                 ((!= (length option-array) 1)
                  (sound-play "roll-over")))
               ;; skip over hidden secrets menu
               (if (and (= *title-pc* (-> this current-options)) (not (memcard-unlocked-secrets? #f)) (= (-> this option-index) 4))
                   (set! (-> this option-index) 3))
               (cond
                 ((> (-> this want-option-index) 0)
                  (set! (-> this want-option-index) -1))
                 ((< -2 (-> this want-option-index))
                  (+! (-> this want-option-index) -1))))
              ((cpad-pressed? 0 down l-analog-down)
               (cond
                 ((= (-> this current-options) *main-options*)
                  (sound-play "ring-select"))
                 ((!= (length option-array) 1)
                  (sound-play "roll-over")))
               ;; skip over hidden secrets menu
               (if (and (= *title-pc* (-> this current-options)) (not (memcard-unlocked-secrets? #f)) (= (-> this option-index) 2))
                   (set! (-> this option-index) 3))
               (cond
                 ((< (-> this want-option-index) 0)
                  (set! (-> this want-option-index) 1))
                 ((< (-> this want-option-index) 2)
                  (+! (-> this want-option-index) 1))))
              ((cpad-pressed? 0 confirm)
               (cpad-clear! 0 confirm)
               (when (and (not in-generic-page?)
                          (not (-> *progress-state* clear-screen)))
                (sound-play "generic-beep"))
               ;; don't "select" the page link
               (when (!= (-> this next) 'generic-menu)
                (set! (-> this selected-option) #t)))
              ((cpad-pressed? 0 triangle)
               ;; if we are within a generic managed menu option, handle the stack elsewhere
               ;; TODO - this likely has issues if we have a nested-non-generic menu, but deal with that use-case if it's ever
               ;; actually employed
               (when (and (not has-generic-history?) (can-go-back? this))
                 (cpad-clear! 0 triangle)
                 (if (and (= (-> *progress-state* starting-state) 'main) (!= (-> this current-options) *main-options*))
                     (sound-play "window-contract")
                     (sound-play "generic-beep"))
                 (clear-history-if-empty! *progress-pc-generic-store*)
                 (pop-state this))))))))))
  (none))

;; Component implementation

;; - input handling

(defmethod respond-progress ((this menu-generic-scrolling-page) (progress progress) (selected? symbol))
  "Handle progress menu navigation logic."
  (let ((selected-item? #f))
    (if (= (-> this selected-option-index) -1)
      (cond
        ((or (cpad-pressed? 0 down l-analog-down)
             (and (cpad-hold? 0 down l-analog-down)
                  (>= (- (current-time) (the-as int (-> this last-move))) (seconds 0.2))))
          (set! (-> this last-move) (current-time))
          (cond
            ((< (-> *progress-pc-generic-store* current-menu-hover-index) (dec (-> this menu-options length)))
              (+! (-> *progress-pc-generic-store* current-menu-hover-index) 1)
              (sound-play "roll-over"))
            (else
              (set! (-> *progress-pc-generic-store* current-menu-hover-index) 0)
              (sound-play "roll-over"))))
        ((or (cpad-pressed? 0 up l-analog-up)
             (and (cpad-hold? 0 up l-analog-up)
                  (>= (- (current-time) (the-as int (-> this last-move))) (seconds 0.2))))
          (set! (-> this last-move) (current-time))
          (+! (-> *progress-pc-generic-store* current-menu-hover-index) -1)
          (sound-play "roll-over")
          (when (< (-> *progress-pc-generic-store* current-menu-hover-index) 0)
          (set! (-> *progress-pc-generic-store* current-menu-hover-index) (dec (-> this menu-options length)))))
        ((cpad-pressed? 0 confirm)
         (when (< (-> *progress-pc-generic-store* current-menu-hover-index) (-> this menu-options length))
           (let ((menu-option (-> this menu-options (-> *progress-pc-generic-store* current-menu-hover-index))))
             ;; if the option is disabled, return early
             (when (and (type? menu-option menu-generic-option)
                        (nonzero? (-> (the-as menu-generic-option menu-option) should-disable?))
                        (!= (-> (the-as menu-generic-option menu-option) should-disable?) #f)
                        ((-> (the-as menu-generic-option menu-option) should-disable?)))
               (sound-play "roll-over")
               (cpad-clear! 0 confirm)
               (return 0))
             ;; ignore confirm if it's a link
             (cond
               ;; TODO the condition for menu-generic-to-resolutions-option shouldn't be required if it
               ;; followed the existing pattern of being a page that contains options, instead of a single option that is a page
               ;; There are already components that do a similar thing (see menu-generic-details-page)
               ((or (type? menu-option menu-generic-link-option) (type? menu-option menu-generic-to-resolutions-option))
                (set! selected-item? #t))
               (else
                (set! (-> this selected-option-index) (-> *progress-pc-generic-store* current-menu-hover-index))
                (sound-play "generic-beep"))))))
        ((cpad-pressed? 0 triangle)
        ;; we are in a sub-page, time to go back
        (when (has-history? *progress-pc-generic-store*)
          (back! *progress-pc-generic-store* progress)
          (sound-play "generic-beep"))))
      ;; menu option already selected
      (cond
        ((cpad-pressed? 0 confirm)
         (set! (-> this selected-option-index) -1))
        ((cpad-pressed? 0 triangle)
         (set! (-> this selected-option-index) -1))))
    ;; propagate event to menu-option
    (when (< (-> *progress-pc-generic-store* current-menu-hover-index) (-> this menu-options length))
      (respond-progress (-> this menu-options (-> *progress-pc-generic-store* current-menu-hover-index)) progress (or selected? selected-item?))))
  0)

(defmethod respond-progress ((this menu-generic-boolean-option) (progress progress) (selected? symbol))
  (if selected?
    (cond
      ((cpad-pressed? 0 left l-analog-left right l-analog-right)
       (if (-> this value)
         (set! (-> this value) #f)
         (set! (-> this value) #t))
       (sound-play "generic-beep"))
      ((cpad-pressed? 0 confirm)
       (call-on-confirm this (-> this value))
       (sound-play "generic-beep")))
    (cond
      ((cpad-pressed? 0 confirm)
       ;; set the value, this is so we edit the component's state and not the actual underlying value
       ;; in other words, don't change the setting until the user has actually confirmed the change!
       (set! (-> this value) (call-get-value-fn this)))))
  0)

(defmethod respond-progress ((this menu-generic-carousel-option) (progress progress) (selected? symbol))
  (if selected?
    (cond
      ((cpad-pressed? 0 left l-analog-left)
       (dec! (-> this item-index))
       (when (< (-> this item-index) 0)
        (set! (-> this item-index) (dec (num-items this))))
       (sound-play "generic-beep"))
      ((cpad-pressed? 0 right l-analog-right)
       (inc! (-> this item-index))
       (when (>= (-> this item-index) (num-items this))
        (set! (-> this item-index) 0))
       (sound-play "generic-beep"))
      ((cpad-pressed? 0 confirm)
       (call-on-confirm this (-> this item-index) progress)
       (sound-play "generic-beep")))
    (cond
      ((cpad-pressed? 0 confirm)
       ;; set the value, this is so we edit the component's state and not the actual underlying value
       ;; in other words, don't change the setting until the user has actually confirmed the change!
       (set! (-> this item-index) (call-get-item-index-fn this)))))
  0)

(defmethod respond-progress ((this menu-generic-link-option) (progress progress) (selected? symbol))
  (when (and selected? (cpad-pressed? 0 confirm))
    (navigate! *progress-pc-generic-store* progress (-> this target) (-> this on-load))
    (set! (-> progress selected-option) #f)
    (sound-play "generic-beep"))
  0)

(defmethod respond-progress ((this menu-generic-button-option) (progress progress) (selected? symbol))
  (cond
    ((cpad-pressed? 0 confirm)
      (cpad-clear! 0 confirm)
      (call-on-confirm this progress)
      (sound-play "generic-beep")))
  0)

(defmethod respond-progress ((this menu-generic-confirm-option) (progress progress) (selected? symbol))
  (if selected?
    (cond
      ((cpad-pressed? 0 left l-analog-left right l-analog-right)
       (if (-> this confirmed?)
         (set! (-> this confirmed?) #f)
         (set! (-> this confirmed?) #t))
       (sound-play "generic-beep"))
      ((cpad-pressed? 0 confirm)
       (call-on-confirm this)
       (sound-play "generic-beep")))
    (cond
      ((cpad-pressed? 0 confirm)
       (set! (-> this confirmed?) #f))))
  0)

(defmethod respond-progress ((this menu-generic-slider-option) (progress progress) (selected? symbol))
  (if selected?
    (cond
      ((and (> (-> this value) (-> this min-value))
            (cpad-hold? 0 left l-analog-left))
       ;; TODO - might not work well on higher frame-rates
       (set! (-> this value) (fmax (- (-> this value) (-> this step)) (-> this min-value)))
       (when (< (seconds 0.03) (- (current-time) (-> this last-sound-played)))
        (set! (-> this last-sound-played) (current-time))
        (sound-play-by-name (static-sound-name "menu-slide") (new-sound-id) 512 0 0 (sound-group sfx) #t)))
      ((and (< (-> this value) (-> this max-value))
            (cpad-hold? 0 right l-analog-right))
       ;; TODO - might not work well on higher frame-rates
       (set! (-> this value) (fmin (+ (-> this value) (-> this step)) (-> this max-value)))
       (when (< (seconds 0.03) (- (current-time) (-> this last-sound-played)))
        (set! (-> this last-sound-played) (current-time))
        (sound-play-by-name (static-sound-name "menu-slide") (new-sound-id) 512 0 0 (sound-group sfx) #t)))
      ((cpad-pressed? 0 confirm)
       (call-on-confirm this (-> this value))
       (sound-play "generic-beep")))
    (cond
      ((cpad-pressed? 0 confirm)
       ;; set the value, this is so we edit the component's state and not the actual underlying value
       ;; in other words, don't change the setting until the user has actually confirmed the change!
       (set! (-> this value) (call-get-value-fn this)))))
  0)

(defmethod respond-progress ((this menu-generic-details-page) (progress progress) (selected? symbol))
  (if (= (-> this selected-entry-index) -1)
    (cond
      ((or (cpad-pressed? 0 down l-analog-down)
           (and (cpad-hold? 0 down l-analog-down)
                (>= (- (current-time) (the-as int (-> this last-move))) (seconds 0.2))))
       (set! (-> this last-move) (current-time))
       (cond
         ((< (-> *progress-pc-generic-store* current-menu-hover-index) (dec (-> this entries length)))
           (+! (-> *progress-pc-generic-store* current-menu-hover-index) 1)
           (sound-play "roll-over"))
         (else
           (set! (-> *progress-pc-generic-store* current-menu-hover-index) 0)
           (sound-play "roll-over"))))
      ((or (cpad-pressed? 0 up l-analog-up)
            (and (cpad-hold? 0 up l-analog-up)
                 (>= (- (current-time) (the-as int (-> this last-move))) (seconds 0.2))))
       (set! (-> this last-move) (current-time))
       (+! (-> *progress-pc-generic-store* current-menu-hover-index) -1)
       (sound-play "roll-over")
       (when (< (-> *progress-pc-generic-store* current-menu-hover-index) 0)
         (set! (-> *progress-pc-generic-store* current-menu-hover-index) (dec (-> this entries length)))))
      ((cpad-pressed? 0 confirm)
       (when (< (-> *progress-pc-generic-store* current-menu-hover-index) (-> this entries length))
         (set! (-> this selected-entry-index) (-> *progress-pc-generic-store* current-menu-hover-index))
         (sound-play "generic-beep")))
      ((cpad-pressed? 0 triangle)
       ;; we are in a sub-page, time to go back
       (when (has-history? *progress-pc-generic-store*)
         (back! *progress-pc-generic-store* progress)
         (sound-play "score-slide"))))
    ;; menu option already selected
    (cond
      ((cpad-pressed? 0 confirm)
       (set! (-> this selected-entry-index) -1))
      ((cpad-pressed? 0 triangle)
       (set! (-> this selected-entry-index) -1))
      ;; keybinds
      ((nonzero? (-> *progress-pc-generic-store* keybind-select-time))
       ;; when the bind has been set, or it's expired
       (when (or (not (pc-waiting-for-bind?))
                 (>= (- (current-time) (the-as int (-> *progress-pc-generic-store* keybind-select-time))) (seconds 5.0)))
        (set! (-> *progress-pc-generic-store* keybind-select-time) 0)
        (set! (-> this selected-entry-index) -1)
        (set! (-> progress selected-option) #f)
        (cond
         ((not (pc-waiting-for-bind?))
          (sound-play "generic-beep"))
         (else
          (pc-stop-waiting-for-bind!)
          (sound-play "roll-over")))))))
  ;; propagate event to menu-option
  (when (< (-> *progress-pc-generic-store* current-menu-hover-index) (-> this entries length))
    (respond-progress (-> this entries (-> *progress-pc-generic-store* current-menu-hover-index)) progress selected?))
  0)

(defmethod respond-progress ((this menu-generic-details-keybind-entry) (progress progress) (selected? symbol))
  (when (not selected?)
    (cond
      ((cpad-pressed? 0 confirm)
       (let ((bind-info (-> this bind-info)))
         (pc-set-waiting-for-bind! (-> bind-info device-type) (not (-> bind-info for-buttons?)) (-> bind-info analog-min-range?) (-> bind-info input-idx)))
       (set! (-> *progress-pc-generic-store* keybind-select-time) (current-time)))))
  0)

(defmethod respond-progress ((this menu-generic-details-confirm-entry) (progress progress) (selected? symbol))
  (if selected?
    (cond
      ((cpad-pressed? 0 left l-analog-left right l-analog-right)
       (if (-> this confirmed?)
         (set! (-> this confirmed?) #f)
         (set! (-> this confirmed?) #t))
       (sound-play "generic-beep"))
      ((cpad-pressed? 0 confirm)
       (call-on-confirm this)
       (sound-play "generic-beep")))
    (cond
      ((cpad-pressed? 0 confirm)
       (set! (-> this confirmed?) #f))))
  0)

;; - rest of component logic

(defmethod num-items ((this menu-generic-carousel-option))
  "Get the number of items in the carousel"
  (if (and (nonzero? (-> this items)) (!= (-> this items) #f))
    (-> this items length)
    (if (and (and (nonzero? (-> this get-max-size-fn)) (!= (-> this get-max-size-fn) #f))
             (and (nonzero? (-> this get-item-label-fn)) (!= (-> this get-max-size-fn) #f)))
      ((-> this get-max-size-fn))
      0)))

(defmethod get-item-label ((this menu-generic-carousel-option) (item-index int))
  "Gets the string label of the currently choosen item, preferring the `items` array if it exists"
  (if (and (nonzero? (-> this items)) (!= (-> this items) #f))
    (lookup-text! *common-text* (-> this items item-index) #f)
    (if (and (nonzero? (-> this get-item-label-fn)) (!= (-> this get-max-size-fn) #f))
      ((-> this get-item-label-fn) item-index)
      "#ERROR")))

;; TODO - it could be possible to map certain controllers to buttons (ie. dualshock controllers could have nice mappings instead of SDL named ones)
(defmethod get-keybind-string ((this menu-generic-details-keybind-entry))
  (case (-> this keybind)
    (((controller-keybind cross))
     ;; the PS2 TRC says it should be pronounced "ecks", hence why every game that speaks the buttons out loud says X, plus at least 80% of people also already say X (i assume in part because of that).
     ;; the official spelling is probably "cross" (see PS2 development reference manuals), but that has less weight in the context of displaying the name to the user, I think?
     ;; "cross" seems to be mainly a UK thing (those weirdos)
     (if (language? uk-english)
         "~Y~22L<~Z~Y~27L*~Z~Y~1L>~Z~Y~23L[~Z~+26H Cross"
         "~Y~22L<~Z~Y~27L*~Z~Y~1L>~Z~Y~23L[~Z~+26H X"))
    (((controller-keybind square))
     "~Y~22L<~Z~Y~24L#~Z~Y~1L>~Z~Y~23L[~Z~+26H S~+7Vq~-7Vuare")
    (((controller-keybind circle))
     "~Y~22L<~Z~Y~25L@~Z~Y~1L>~Z~Y~23L[~Z~+26H Circle")
    (((controller-keybind triangle))
     "~Y~22L<~Z~Y~26L;~Z~Y~1L>~Z~Y~23L[~Z~+26H Trian~+7Vg~-7Vle")
    (((controller-keybind dpad-up))
     "~Y~0L\ca1~Z~3L~+17H~-13V\ca2~Z~0L~+17H~+14V\ca0~Z~0L~+32H\ca3~Z~+56H D-Pad U~+7Vp~-7V")
    (((controller-keybind dpad-down))
     "~Y~0L\ca1~Z~0L~+17H~-13V\ca2~Z~3L~+17H~+14V\ca0~Z~0L~+32H\ca3~Z~+56H D-Pad Down")
    (((controller-keybind dpad-right))
     "~Y~0L\ca1~Z~0L~+17H~-13V\ca2~Z~0L~+17H~+14V\ca0~Z~3L~+32H\ca3~Z~+56H D-Pad Ri~+7Vg~-7Vht")
    (((controller-keybind dpad-left))
     "~Y~3L\ca1~Z~0L~+17H~-13V\ca2~Z~0L~+17H~+14V\ca0~Z~0L~+32H\ca3~Z~+56H D-Pad Left")
    (((controller-keybind l1))
     "~Y~22L~-2H~-12V\ca6\ca7~Z~22L~-2H~+17V\cb0\cb1~Z~1L~+4H~+3V\c95~Z~+38H L1")
    (((controller-keybind l2))
     "~Y~22L~-2H~-6V\ca8\ca9~Z~22L~-2H~+16V\cb2\cb3~Z~1L~+5H~-2V\c97~Z~+38H L2")
    (((controller-keybind l3))
     "\c91 L3")
    (((controller-keybind r1))
     "~Y~22L~-2H~-12V\ca6\ca7~Z~22L~-2H~+17V\cb0\cb1~Z~1L~+6H~+3V\c94~Z~+38H R1")
    (((controller-keybind r2))
     "~Y~22L~-2H~-6V\ca8\ca9~Z~22L~-2H~+16V\cb2\cb3~Z~1L~+5H~-2V\c96~Z~+38H R2")
    (((controller-keybind r3))
     "\c91 R3")
    (((controller-keybind select))
     "\c92 Select")
    (((controller-keybind start))
     "\c92 Start")
    (((controller-keybind l-analog-up))
     "~Y~1L\c91~Z~-17V~6L\c98~Z~+26H Left Analo~+7Vg~-7V U~+7Vp~-7V")
    (((controller-keybind l-analog-down))
     "~Y~1L\c91~Z~+15V~6L\c9c~Z~+26H Left Analo~+7Vg~-7V Down")
    (((controller-keybind l-analog-left))
     "~Y~1L\c91~Z~-17H~6L\c9e~Z~+26H Left Analo~+7Vg~-7V Left")
    (((controller-keybind l-analog-right))
     "~Y~1L\c91~Z~+17H~6L\ca4~Z~+26H Left Analo~+7Vg~-7V Ri~+7Vg~-7Vht")
    (((controller-keybind r-analog-up))
     "~Y~1L\c91~Z~-17V~6L\c98~Z~+26H Ri~+7Vg~-7Vht Analo~+7Vg~-7V U~+7Vp~-7V")
    (((controller-keybind r-analog-down))
     "~Y~1L\c91~Z~+15V~6L\c9c~Z~+26H Ri~+7Vg~-7Vht Analo~+7Vg~-7V Down")
    (((controller-keybind r-analog-left))
     "~Y~1L\c91~Z~-17H~6L\c9e~Z~+26H Ri~+7Vg~-7Vht Analo~+7Vg~-7V Left")
    (((controller-keybind r-analog-right))
     "~Y~1L\c91~Z~+17H~6L\ca4~Z~+26H Ri~+7Vg~-7Vht Analo~+7Vg~-7V Ri~+7Vg~-7Vht")
    (else
     "Unknown Bind")))
