# -*- coding:utf-8 -*-

"""
This file is part of OpenSesame.

OpenSesame is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

OpenSesame 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with OpenSesame.  If not, see <http://www.gnu.org/licenses/>.
"""
from libopensesame.py3compat import *
from libopensesame.exceptions import OSException, ItemDoesNotExist, InvalidValue
from libopensesame.base_response_item import BaseResponseItem
from openexp._canvas.canvas import Canvas
from openexp.mouse import Mouse


class MouseResponseMixin:

    r"""A mixin class that should be inherited along with base_response_item by
    all classes that want to collect mouse responses.
    """
    _resp_codes = {
        u'none': None,
        u'timeout': None,
        u'left_button': 1,
        u'middle_button': 2,
        u'right_button': 3,
        u'left': 1,
        u'middle': 2,
        u'right': 3,
        u'scroll_up': 4,
        u'scroll_down': 5
    }

    def button_code(self, response):

        if response is None or isinstance(response, int):
            return response
        try:
            return int(response)
        except:
            return self._resp_codes[response.lower()]

    def prepare_response_func(self):
        """See base_response_item."""
        if self._allowed_responses is None:
            buttonlist = None
        else:
            buttonlist = [self.button_code(r) for r in self._allowed_responses]
        self._mouse = Mouse(
            self.experiment,
            timeout=self._timeout,
            buttonlist=buttonlist
        )
        if self.var.get(u'event_type', default=u'mouseclick') == u'mouseclick':
            return self._mouse.get_click
        if self.var.event_type == u'mouserelease':
            return self._mouse.get_click_release
        raise InvalidValue(
            f'event_type should be "mouseclick" or "mouserelease", not '
            f'{self.var.event_type}')

    def process_response(self, response_args):
        """See base_response_item."""
        response, pos, t1 = response_args
        if pos is None:
            self.experiment.var.cursor_x = u'NA'
            self.experiment.var.cursor_y = u'NA'
        else:
            self.experiment.var.cursor_x, self.experiment.var.cursor_y = pos
        if self.var.get(u'linked_sketchpad', default=u'') and pos is not None:
            if self.var.linked_sketchpad not in self.experiment.items:
                raise ItemDoesNotExist(self.var.linked_sketchpad)
            item = self.experiment.items[self.var.linked_sketchpad]
            if not hasattr(item, u'canvas') or \
                    not isinstance(item.canvas, Canvas):
                raise OSException(
                    f'Linked sketchpad does not have a canvas: '
                    f'{self.var.linked_sketchpad}')
            self.experiment.var.cursor_roi = u';'.join(
                item.canvas.elements_at(*pos)
            )
        else:
            self.experiment.var.cursor_roi = u'undefined'
        BaseResponseItem.process_response(self, (response, t1))


class MouseResponse(MouseResponseMixin, BaseResponseItem):

    r"""An item for collecting mouse responses."""
    description = u'Collects mouse responses'
    process_feedback = True

    def reset(self):
        """See item."""
        self.var.flush = u'yes'
        self.var.show_cursor = u'yes'
        self.var.timeout = u'infinite'
        self.var.duration = u'mouseclick'
        self.var.linked_sketchpad = u''
        self.var.event_type = u'mouseclick'
        self.var.unset(u'allowed_responses')
        self.var.unset(u'correct_response')

    def validate_response(self, response):
        """See base_response_item."""
        try:
            self.button_code(response)
        except:
            return False
        return True

    def response_matches(self, test, ref):
        """See base_response_item."""
        return any(self.button_code(test) == self.button_code(r) for r in ref)

    def prepare(self):
        """See item."""
        super().prepare()
        self._flush = self.var.flush == u'yes'

    def run(self):
        """See item."""
        if self._flush:
            self._mouse.flush()
        # Show cursor if necessary
        if self.var.show_cursor == u'yes':
            self._mouse.visible = True
        super().run()
        self._mouse.visible = False

    def coroutine(self):
        """See coroutines plug-in."""
        self._mouse.timeout = 0
        alive = True
        yield
        self._t0 = self.set_item_onset()
        if self._flush:
            self._mouse.flush()
        if self.var.show_cursor == u'yes':
            self._mouse.visible = True
        while alive:
            button, pos, time = self._mouse.get_click()
            if button is not None:
                break
            alive = yield
        self.process_response((button, pos, time))
        self._mouse.visible = False

    def var_info(self):
        """See item."""
        return super().var_info() + [
            (u'cursor_x', u'[Depends on response]'),
            (u'cursor_y', u'[Depends on response]'),
            (u'cursor_roi', u'[Depends on response]')
        ]


# Alias for backwards compatibility
mouse_response = MouseResponse
mouse_response_mixin = MouseResponseMixin
