# -*- coding:utf-8 -*-
#############################################################################
# Copyright (c) 2023 Huawei Technologies Co.,Ltd.
#
# openGauss is licensed under Mulan PSL v2.
# You can use this software according to the terms
# and conditions of the Mulan PSL v2.
# You may obtain a copy of Mulan PSL v2 at:
#
#          http://license.coscl.org.cn/MulanPSL2
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS,
# WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
# See the Mulan PSL v2 for more details.
# ----------------------------------------------------------------------------
# Description  : Provides a series of methods for dialogue, question and input.
#############################################################################

import os
from gspylib.common.GaussLog import GaussLog


class DialogError(Exception):
    def __init__(self, complete_question, msg):
        self.complete_question = complete_question
        self.msg = msg

    def __str__(self):
        return self.msg


class DialogUtil(object):
    """
    Provides a set of interfaces for dialogue and interactive questioning.

    Each interface has its own label.

    If it is a question, you can select whether it is required, and the
    required question will be marked with an '*'.
    """
    @staticmethod
    def Message(message):
        """
        :param message: message
        :return: NA
        """
        complete_message = f'[ Message ] {message}\n'
        GaussLog.printMessage(complete_message)

    @staticmethod
    def yesOrNot(question, required=True, max_retry=99999):
        """
        raise a question, and want a yes or not.

        example:
            *[ yesOrNot ] Am I handsome? (y/n)? y

        :param question: question
        :param required: must input something. if not, we can return None
        :param max_retry: max retry times
        :return: True, False, None
        """
        question_tag = '{0}[ yesOrNot ]'.format('*' if required else '')
        complete_question = '{0} {1}(y/n)? '.format(
            question_tag, question
        )
        answer = input(complete_question)
        while True:
            if not required and answer == '':
                return

            if answer.lower() in ('y', 'n'):
                return answer.lower() == 'y'

            max_retry -= 1
            if max_retry < 0:
                raise DialogError(complete_question, 'Too many failed retry.')

            answer = input('Please input y or n: ')

    @staticmethod
    def singleAnswerQuestion(question, options, required=True, max_retry=99999):
        """
        raise a question, and want an option.

        example:
            *[ Single Answer Question ] What would you like for dinner?
                A. carrot
                B. rice
                C. chicken
            Single answer: a

        :param question: question
        :param options: options to choose.
        :param required: must input something. if not, we can return None
        :param max_retry: max retry times
        :return: the index of option which user choose, or None
        """
        assert len(options) <= 26
        question_tag = '{0}[ Single Answer Question ]'.format('*' if required else '')
        code_id = [chr(c) for c in range(ord('A'), ord('A') + len(options))]
        question_options = ''
        for i, option in enumerate(options):
            question_options += '  {0}. {1}\n'.format(code_id[i], option)

        complete_question = "{0} {1}\n{2}Single answer: ".format(
            question_tag, question, question_options)
        answer = input(complete_question)
        while True:
            if not required and answer == '':
                return
            if answer.upper() in code_id:
                return code_id.index(answer.upper())

            max_retry -= 1
            if max_retry < 0:
                raise DialogError(complete_question, 'Too many failed retry.')

            answer = input('Please input A~{}:'.format(code_id[-1]))

    @staticmethod
    def multipleAnswerQuestion(question, options, required=True, max_retry=99999):
        """
        raise a question, and want an option.

        example:
            *[ Multiple Answer Question ] What would you like for dinner?
                A. carrot
                B. rice
                C. chicken
            Multiple answer: ac

        :param question: question
        :param options: options to choose
        :param required: must input something. if not, we can return None
        :param max_retry: max retry times
        :return: the index list of option which user choose, or []
        """
        assert len(options) <= 26
        code_id = [chr(c) for c in range(ord('A'), ord('A') + len(options))]
        question_options = ''
        for i, option in enumerate(options):
            question_options += ' {0}. {1} \n'.format(code_id[i], option)
        complete_question = "{0}[ Multiple Answer Question ] {1}\n{2}Multiple answer: ".format(
            '*' if required else '', question, question_options)

        def _analyse_answer(_answer):
            _chosen = set()
            for c in _answer:
                if c == ' ' or c == '\t' or c == ',':
                    continue
                if c.upper() not in code_id:
                    GaussLog.printMessage(f'Invalid select code: {c}.', end='')
                    return
                _chosen.add(ord(c.upper()) - ord('A'))
            _res = list(_chosen)
            _res.sort()
            if required and len(_res) == 0:
                GaussLog.printMessage('Please make at least one choice.')
                return

            return _res

        answer = input(complete_question)
        while True:
            res = _analyse_answer(answer)
            if res is not None:
                return res

            max_retry -= 1
            if max_retry < 0:
                raise DialogError(complete_question, 'Too many failed retry.')

            answer = input('Please input A~{}:'.format(code_id[-1]))

    @staticmethod
    def askANumber(question, check_func=None, required=True, max_retry=99999):
        """
        ask a number.

        for example:
            *[ Input number ] How old are you?
            Please answer: 1000

        :param question: question
        :param check_func: Verify that the number entered is legitimate. Otherwise, return an error msg
        :param required: must input something. if not, we can return None
        :param max_retry: max retry times
        :return: the number, or None
        """
        complete_question = '{0}[ Input number ] {1}\nPlease answer: '.format(
            '*' if required else '', question
        )
        answer = input(complete_question)
        while True:
            retry_msg = None
            if not required and answer == '':
                return

            if not answer.isdigit():
                retry_msg = 'Invalid integer, please again: '
            elif check_func is not None:
                errmsg = check_func(int(answer))
                if errmsg is None:
                    return int(answer)
                retry_msg = f'{errmsg}. please again: '
            else:
                return int(answer)

            max_retry -= 1
            if max_retry < 0:
                raise DialogError(complete_question, 'Too many failed retry.')

            answer = input(retry_msg)

    @staticmethod
    def askAPath(question, check_access=None, required=True, max_retry=99999):
        """
        ask a path.

        for example:
            *[ Input path ] Where is the file?
            Please input the path: /home/user/abc

        :param question: question
        :param check_access: check the path is access.
        :param required: must input something. if not, we can return None
        :param max_retry: max retry times
        :return: the path, or None
        """
        complete_question = '{0}[ Input path ] {1} \nPlease input the path: '.format(
            '*' if required else '', question
        )
        answer = input(complete_question)
        while True:
            retry_msg = None
            if not required and answer == '':
                return

            if check_access and not os.access(answer, os.F_OK):
                retry_msg = 'Could not access path, please again: '
            else:
                return answer

            max_retry -= 1
            if max_retry < 0:
                raise DialogError(complete_question, 'Too many failed retry.')

            answer = input(retry_msg)


if __name__ == '__main__':
    res = DialogUtil.multipleAnswerQuestion(
        'test question',
        ['x', 'xx', 'xxx']
    )
    GaussLog.printMessage(res)

