# Handle user input
#
# This file is part of Simpleline Text UI library.
#
# Copyright (C) 2020  Red Hat, Inc.
#
# Simpleline is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Simpleline is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Simpleline.  If not, see <https://www.gnu.org/licenses/>.
#

import sys

from simpleline import App
from simpleline.event_loop.signals import InputReadySignal
from simpleline.render.widgets import TextWidget
from simpleline.input.input_threading import InputThreadManager, InputRequest

__all__ = ["InputHandler", "PasswordInputHandler"]


class InputHandler():

    def __init__(self, callback=None, source=None):
        """Class to handle input from the terminal.

        This class is designed to be instantiated on place where it should be used.
        The main method is `get_input()` which is non-blocking asynchronous call. It can be used
        as synchronous call be calling the `wait_on_input` method.

        To get result from this class use the `value` property.

        :param callback: You can specify callback which will be called when user give input.
        :type callback: Callback function with one argument which will be user input.

        :param source: Source of this input. It will be helpful in case of debugging an issue.
        :type source: Class which will process an input from this InputHandler.
        """
        super().__init__()
        self._input = None
        self._input_callback = callback
        self._input_received = False
        self._input_successful = False
        self._skip_concurrency_check = False
        self._source = source

        App.get_event_loop().register_signal_handler(InputReadySignal,
                                                     self._input_received_handler)

    def _input_received_handler(self, signal, args):
        if signal.input_handler_source != self:
            return

        self._input_received = True
        self._input_successful = signal.success

        if not self._input_successful:
            return

        self._input = signal.data

        # call async callback
        if self._input_callback is not None:
            cb = self._input_callback
            self._input_callback = None

            cb(self._input)

    @property
    def value(self):
        """Return user input.

        :returns: String or None if no is input received.
        """
        return self._input

    @property
    def source(self):
        """Get source of this input.

        :returns: Anything probably UIScreen.
        """
        return self._source

    @property
    def skip_concurrency_check(self):
        """Is this InputHandler skipping concurrency check?

        :returns bool
        """
        return self._skip_concurrency_check

    @skip_concurrency_check.setter
    def skip_concurrency_check(self, value):
        """Set if this InputHandler should skip concurrency check.

        Note if you skip this check, you can have unexpected behavior. Use with caution.

        :param value: True to skip the check, False if not.
        """
        self._skip_concurrency_check = value

    def set_callback(self, callback):
        """Set a callback to get user input asynchronously.

        :param callback: Callback called when user write their input.
        :type callback: Method with 1 argument which is user input: def cb(user_input)
        """
        self._input_callback = callback

    def input_received(self):
        """Was user input already received?

        :returns: True if yes, False otherwise.
        """
        return self._input_received

    def wait_on_input(self):
        """Blocks execution till the user input is received.

        Events will works as expected during this blocking.

        Please check the `input_successful` method to test the input.
        """
        # we already received input from user
        if self._input_received:
            return

        while not self._input_received:
            App.get_event_loop().process_signals(InputReadySignal)

    def input_successful(self):
        """Was input successful?

        :returns: bool
        """
        return self._input_successful

    def get_input(self, prompt):
        """Use prompt to ask for user input and wait (non-blocking) on user input.

        This is an asynchronous call. If you want to wait for user input then use
        the `wait_on_input` method. If you want to get results asynchronously then register
        callback in constructor or by the `set_callback` method.

        Check if user input was already received can be done by the `input_received` method call.

        :param prompt: Ask user what you want to get.
        :type prompt: String or Prompt instance.

        :returns: User input.
        :rtype: str
        """
        self._clear_input()
        self._invoke_input_thread(prompt)

    def _invoke_input_thread(self, prompt):
        thread_object = self.create_thread_object(prompt)
        InputThreadManager.get_instance().start_input_thread(thread_object,
                                                             not self._skip_concurrency_check)

    def create_thread_object(self, prompt):
        """Create thread object containing all the information how to get user input.

        :returns: Instance of class inherited from `simpleline.input.InputThread`.
        """
        return InputHandlerRequest(App.get_configuration().width, prompt, self)

    def _clear_input(self):
        self._input_received = False
        self._input = None


class InputHandlerRequest(InputRequest):
    """This is thread object to get input from user without blocking main thread."""

    def __init__(self, width, prompt, input_handler):
        """Create request object to get input in InputThreadManager.

        :param width: Width of the screen prompt.
        :type width: int

        :param prompt: Input prompt.
        :type prompt: Instance of `simpleline.render.prompt.Prompt` class.

        :param input_handler: InputHandler instance which created this object.
        :type input_handler: InputHandler based instance.
        """
        super().__init__(input_handler, input_handler.source)
        self._width = width
        self._prompt = prompt

    def get_input(self):
        """This method is responsible for interruptable user input.

        It is expected to be used in a thread started on demand
        and returns the input via the communication Queue.
        """
        # lock acquired, we can run input
        try:
            data = self._ask_input()
        except EOFError:
            data = ""

        return data

    def text_prompt(self):
        widget = TextWidget(str(self._prompt))
        widget.render(self._width)
        lines = widget.get_lines()
        return "\n".join(lines) + " "

    def _ask_input(self):
        text_prompt = self.text_prompt()
        sys.stdout.write(text_prompt)
        sys.stdout.flush()

        return self._get_input()

    @staticmethod
    def _get_input():
        return input()


class PasswordInputHandler(InputHandler):

    def __init__(self, callback=None, source=None):
        """Class to handle hidden password input from the terminal.

        This class is designed to be instantiated on place where it should be used.
        The main method is `get_input()` which is non-blocking asynchronous call. It can be used
        as synchronous call be calling the `wait_on_input` method.

        To get result from this class use the `value` property.

        :param callback: You can specify callback which will be called when user give input.
        :type callback: Callback function with one argument which will be user input.

        :param source: Source of this input. It will be helpful in case of debugging an issue.
        :type source: Class which will process an input from this InputHandler.
        """
        super().__init__(callback=callback, source=source)
        self._getpass_func = App.get_configuration().password_function

    def set_pass_func(self, getpass_func):
        """Set a function for getting passwords."""
        if not getpass_func:
            return

        self._getpass_func = getpass_func

    def create_thread_object(self, prompt):
        """Return PasswordInputThread for getting user password."""
        return PasswordInputHandlerRequest(App.get_configuration().width, prompt, self,
                                           self._getpass_func)


class PasswordInputHandlerRequest(InputHandlerRequest):
    """Similar as InputHandlerRequest but don't echo user keys."""

    def __init__(self, width, prompt, input_handler, getpass_func):
        """Create request object to get password input in InputThreadManager.

        :param width: Width of the screen prompt.
        :type width: int

        :param prompt: Input prompt.
        :type prompt: Instance of `simpleline.render.prompt.Prompt` class.

        :param input_handler: InputHandler instance which created this object.
        :type input_handler: InputHandler based instance.

        :param getpass_func: Function to get user password.
        :type getpass_func: Function which gets prompt as only parameter and returns user input
                            string.
        """
        super().__init__(width, prompt, input_handler)
        self._getpass_func = getpass_func

    def _ask_input(self):
        text_prompt = self.text_prompt()

        return self._getpass_func(text_prompt)
