#!/usr/bin/env python3
# vim: fileencoding=utf8
# SPDXVersion: SPDX-2.3
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: © Copyright 2012-2022, 2023 by Christian Dönges
# SPDXID: SPDXRef-commandmenu-py
"""An interactive text console menu.

    # Example
    (pending)
"""

import logging
import readline


class CommandItem:
    """An item for a command menu."""
    def __init__(self, name: str,
                 default: bool = False):
        self.name = name
        self.default = default

    def __str__(self):
        """A human-readable description of the instance."""
        s = f'CommandItem<"{self.name}"'
        if self.default:
            s += ', default>'
        else:
            s += '>'
        return s


class CommandMenu:
    """Interactive text console menu."""
    # https://pymotw.com/2/readline/
    def __init__(self,
                 vocabulary: dict[str, list[str]] = {'quit': []},
                 prompt: str = '> ',
                 terminal_word: str = 'quit'):
        """Initialize the instance.

           :param dict[str, list[str]] vocabulary: A dictionary of primary terms
              with a list of secondary terms specific to the primary term.
           :param str prompt: The prompt displayed to tell the user that input
              is expected.
           :param str terminal_word: The primary term that will terminate the menu.
        """

        # Configuration members.
        self.prompt = prompt
        self.vocabulary = vocabulary
        self.terminal_word = terminal_word

        # Dynamic members.
        self.current_candidates = []

    def __str__(self):
        """A human-readable description of the instance."""
        s = 'CommandMenu<'
        first = True
        for key, item in self.vocabulary.items():
            if first:
                s += str(item)
                first = False
            else:
                s += f', {item:!s}'
        s += '>'
        return s

    def complete(self, text, state):
        """Command-line completion of the user text in the given state."""
        response = None
        if state == 0:
            # This is the first time for this text, so build a match list.
            origline = readline.get_line_buffer()
            begin = readline.get_begidx()
            end = readline.get_endidx()
            being_completed = origline[begin:end]
            words = origline.split()

            logging.debug('origline=%s', repr(origline))
            logging.debug('begin=%s', begin)
            logging.debug('end=%s', end)
            logging.debug('being_completed=%s', being_completed)
            logging.debug('words=%s', words)

            if not words:
                self.current_candidates = sorted(self.vocabulary.keys())
            else:
                try:
                    if begin == 0:
                        # first word
                        candidates = self.vocabulary.keys()
                    else:
                        # later word
                        first = words[0]
                        candidates = self.vocabulary[first]

                    if being_completed:
                        # match vocabulary with portion of input
                        # being completed
                        self.current_candidates = \
                            [w for w in candidates if w.startswith(being_completed)]
                    else:
                        # matching empty string so use all candidates
                        self.current_candidates = candidates

                    logging.debug('candidates=%s', self.current_candidates)

                except (KeyError, IndexError) as err:
                    logging.error('completion error: %s', err)
                    self.current_candidates = []

        try:
            response = self.current_candidates[state]
        except IndexError:
            response = None
        logging.debug('complete(%s, %s) => %s', repr(text), state, response)
        return response

    def input_generator(self) -> str:
        """Yield user input whenever an entry is confirmed.

           Automatically quits if `terminal_word` is typed.

           :return: The line of user input.
           :rtype: str
        """
        line = ''
        while line != self.terminal_word:
            line = input(self.prompt)
            yield line


if __name__ == '__main__':
    # Register our completer function
    cm = CommandMenu({
        'list': ['files', 'directories'],
        'print': ['byname', 'bysize'],
        'stop': [],
    }, ':> ', 'stop')
    readline.set_completer(cm.complete)

    # Use the tab key for completion
    readline.parse_and_bind('tab: complete')        # Works on Linux
    readline.parse_and_bind('bind ^I rl_complete')  # Works on macOS
    readline.parse_and_bind('set editing-mode vi')

    # Prompt the user for text
    for line in cm.input_generator():
        print(f'Input was: {line}')
