# -*- coding: utf-8 -*-
#
# This file is part of PyGaze - the open-source toolbox for eye tracking
#
#    PyGaze is a Python module for easily creating gaze contingent experiments
#    or other software (as well as non-gaze contingent experiments/software)
#    Copyright (C) 2012-2013  Edwin S. Dalmaijer
#
#    This program 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.
#
#    This program 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 this program.  If not, see <http://www.gnu.org/licenses/>

# The BaseClasses are meant to store the documentation on all methods of a
# class, but not to contain any functionality whatsoever. BaseClass is
# inherited by all of the subclasses, and the documentation is copied using
# pygaze.copy_docstr. If you intend to make your own subclass for the current
# baseclass, be sure to inherit BaseClass, copy the documentation, and
# redefine the methods as you see fit, e.g.:
#
#import pygaze
#from pygaze._display.basedisplay import BaseDisplay
#
#class DummyDisplay(BaseDisplay):
#
#    """An example child of BaseDisplay"""
#
#    def __init__(self, *args, **kwargs):
#
#        """Initializes a DummyDisplay instance"""
#
#        pygaze.copy_docstring(BaseDisplay,DummyDisplay)
#
#    def show(self):
#
#        # note that here no docstring is provided, as it is copied from
#        # the parent class
#
#        print("Display.show call at %d" % int(clock.get_time()))
#

from pygaze.py3compat import *

class BaseEyeTracker:

    """
    desc: |
        A generic Python library for eye tracking.
    """

    def __init__(self):

        """
        strict:
            False

        desc: |
            Initializes the EyeTracker object.

        arguments:
            display:
                desc:    A pygaze.display.Display instance.
                type:    Display

        keywords:
            logfile:
                desc: |
                    A logfile name (string value); path may be included in the
                    file name for SMI and for Tobii; SMI will create a `.idf`
                    and a `.txt`, Tobii will make a `.tsv`; for EyeLink PLEASE
                    DO NOT USE THIS: use `data_file` instead, see below for
                    explanation.
                type:    [unicode, str]
            eventdetection:
                desc: |
                    A string specifying the type of event detection, either
                    'native' for system default functions (not always
                    available!), of 'pygaze' for Dalmaijer et al. (2013)
                    algorithms; NOTE: will fall back to 'pygaze' if 'native' is
                    not available on your system!
                type:    [unicode, str]
            saccade_velocity_threshold:
                desc:    A saccade velocity threshold in degrees per second.
                type:    [float, int]
            saccade_acceleration_threshold:
                desc:    A saccade acceleration threshold in degrees per square
                        second.
                type:    [float, int]
            data_file:
                desc: |
                    __EyeLink only!__ IMPORTANT: use data_file in stead of
                    logfile keyword for EyeLink, as it does not
                    support filenames longer than 8 characters (so no
                    path assignment in the logfile name, as is possible
                    for SMI and Tobii).
                type:    [str, unicode]
            force_drift_correct:
                desc: |
                    __EyeLink only!__ Indicates whether an active drift
                    correction should be used. This option is relevant for
                    EyeLink 1000 devices, on which drift correction is by
                    default only a check, and not an actual single-point
                    recalibration.
                type:    bool
            pupil_size_mode:
                desc: |
                    __EyeLink only!__ Indicates whether pupil size is recorded
                    in area or diameter values.
                type:    bool
            resolution:
                desc: |
                    __EyeLink only!__ Specify the display resolution using
                    a (w,h) tuple.
                type:    tuple
            fg_color: |
                desc: |
                    __EyeLink only!__ Specify the foreground colour using
                    an (R,G,B) tuple.
                type:    tuple
            bg_color:
                desc: |
                    __EyeLink only!__ Specify the background colour using
                    an (R,G,B) tuple.
                type:    tuple
            ip:
                desc: __SMI only!__ The ip address for iViewX.
                type:    [str, unicode]
                    (default = '127.0.0.1')
            sendport:
                desc:    __SMI only!__ port number for iViewX sending.
                type:    int
            receiveport:
                desc:    __SMI only!__ port number for iViewX receiving.
                type:    int
        """

        pass


    def calibrate(self):

        """
        desc: |
            Calibrates the eye tracking system. The actual behavior of this
            function depends on the type of eye tracker and is described below.

            __EyeLink:__

            This function will activate the camera-setup screen, which allows
            you to adjust the camera, and peform a calibration/ validation
            procedure. Pressing 'q' will exit the setup routine. Pressing
            'escape' will first trigger a confirmation dialog and then, upon
            confirmation, raises an Exception.

            __EyeTribe:__

            Activates a simple calibration routine.

        returns:
            desc: |
                Returns True if calibration succeeded, or False if not; in
                addition a calibration log is added to the log file and some
                properties are updated (i.e. the thresholds for detection
                algorithms).
            type:    bool
        """

        pass

    def close(self):

        """
        desc: |
            Neatly closes connection to tracker. Saves data and sets
            `self.connected` to False.
        """

        pass


    def connected(self):

        """
        desc:
            Checks if the tracker is connected.

        returns:
            desc: |
                True if connection is established, False if not; sets
                `self.connected` to the same value.
            type:    bool
        """

        pass


    def drift_correction(self, pos=None, fix_triggered=False):

        """
        desc: |
            Performs a drift-correction procedure. The exact behavior of this
            function on the type of eye tracker and is described below. Because
            drift correction may fail, you will generally call this function in
            a loop.

            __EyeLink:__

            Pressing 'q' during drift-correction will activate the camera-setup
            screen. From there, pressing 'q' again will cause drift correction
            to fail immediately. Pressing 'escape' will give the option to abort
            the experiment, in which case an Exception is raised.

        keywords:
            pos:
                desc:    (x, y) position of the fixation dot or None for a
                        central fixation.
                type:    [tuple, NoneType]
            fix_triggered:
                desc:    Boolean indicating if drift check should be performed
                        based on gaze position (True) or on spacepress (False).
                type:    bool

        returns:
            desc:    A boolean indicating if drift check is ok (True) or not
                    (False).
            type:    bool
        """

        pass


    def fix_triggered_drift_correction(self, pos=None, min_samples=30,
        max_dev=60, reset_threshold=10):

        """
        desc: |
            Performs a fixation triggered drift correction by collecting
            a number of samples and calculating the average distance from the
            fixation position

        keywords:
            pos:
                desc:    (x, y) position of the fixation dot or None for a
                        central fixation.
                type:    [tuple, NoneType]
            min_samples:
                desc:    The minimal amount of samples after which an
                        average deviation is calculated.
                type:    int
            max_dev:
                desc:    The maximal deviation from fixation in pixels.
                type:    int
            reset_threshold:
                desc:    If the horizontal or vertical distance in pixels between
                        two consecutive samples is larger than this threshold,
                        the sample collection is reset.
                type:    int

        returns:
            desc:    A boolean indicating if drift check is ok (True) or not
                    (False).
            type:    bool
        """

        pass


    def get_eyetracker_clock_async(self):

        """
        desc:
            Returns the difference between tracker time and PyGaze time, which
            can be used to synchronize timing

        returns:
            desc:    The difference between eyetracker time and PyGaze time.
            type:    [int, float]
        """

        pass

    def log(self, msg):

        """
        desc:
            Writes a message to the log file.

        arguments:
            msg:
                desc:    A message.
                type:    [str, unicode]
        """

        pass


    def log_var(self, var, val):

        """
        desc:
            Writes a variable's name and value to the log file

        arguments:
            var:
                desc:    A variable name.
                type:    [str, unicode]
            val:
                desc:    A variable value
        """

        self.log(u"var {} {}".format(safe_decode(var), safe_decode(val)))

    def pupil_size(self):

        """
        desc:
            Returns the newest pupil size sample; size may be measured as the
            diameter or the area of the pupil, depending on your setup (note
            that pupil size mostly is given in an arbitrary units).

        returns:
            desc:    Returns pupil size for the eye that is currently
                    being tracked (as specified by self.eye_used) or -1
                    when no data is obtainable.
            type:    [int, float]
        """

        pass

    def sample(self):

        """
        desc:
            Returns newest available gaze position.

        returns:
            desc:     An (x,y) tuple or a (-1,-1) on an error.
            type:    tuple
        """

        pass

    def send_command(self, cmd):

        """
        desc:
            Directly sends a command to the eye tracker (not supported for all
            brands; might produce a warning message if your setup does not
            support direct commands).

        arguments:
            cmd:
                desc:    The command to be sent to the eye tracker.
                type:    [str, unicode]
        """

        pass

    def set_eye_used(self):

        """
        desc:
            Logs the `eye_used` variable, based on which eye was specified
            (if both eyes are being tracked, the left eye is used). Does not
            return anything.
        """

        pass

    def draw_drift_correction_target(self, x, y):

        """
        desc:
            Draws a drift-correction target.

        arguments:
            x:
                desc:    The X coordinate
                type:    int
            y:
                desc:    The Y coordinate
                type:    int
        """

        pass

    def draw_calibration_target(self, x, y):

        """
        desc:
            Draws a calibration target.

        arguments:
            x:
                desc:    The X coordinate
                type:    int
            y:
                desc:    The Y coordinate
                type:    int
        """

        pass

    def set_draw_calibration_target_func(self, func):

        """
        desc:
            Specifies a custom function to draw the calibration target. This
            will function will override the default [draw_calibration_target].

        arguments:
            func:
                desc:    The function to draw a calibration target. This function
                        should accept two parameters, for the x and y coordinate
                        of the target.
                type:    function
        """

        self.draw_calibration_target = func

    def set_draw_drift_correction_target_func(self, func):

        """
        desc:
            Specifies a custom function to draw the drift-correction target.
            This function will override the default
            [draw_drift_correction_target].

        arguments:
            func:
                desc:    The function to draw a drift-correction target. This
                        function should accept two parameters, for the x and y
                        coordinate of the target.
                type:    function
        """

        self.draw_drift_correction_target = func

    def start_recording(self):

        """
        desc:
            Starts recording. Sets `self.recording` to `True` when recording
            is successfully started.
        """

        pass

    def status_msg(self, msg):

        """
        desc:
            Sends a status message to the eye tracker, which is displayed in
            the tracker's GUI (only available for EyeLink setups).

        arguments:
            msg:
                desc: |
                        A string that is to be displayed on the experimenter PC,
                        e.g.: "current trial: %d" % trialnr.
                type:    [str, unicode]
        """

        pass


    def stop_recording(self):

        """
        desc:
            Stops recording. Sets `self.recording` to `False` when recording is
            successfully stopped.
        """

        pass


    def set_detection_type(self, eventdetection):

        """
        desc: |
            Set the event detection type to either PyGaze algorithms, or
            native algorithms as provided by the manufacturer (only if
            available: detection type will default to PyGaze if no native
            functions are available)

        arguments:
            eventdetection:
                desc: |
                        A string indicating which detection type
                        should be employed: either 'pygaze' for
                        PyGaze event detection algorithms or
                        'native' for manufacturers algorithms (only
                        if available; will default to 'pygaze' if no
                        native event detection is available)
                type:    [str, unicode]

        returns:
            desc:        Detection type for saccades, fixations and
                        blinks in a tuple, e.g.
                        ('pygaze','native','native') when 'native'
                        was passed, but native detection was not
                        available for saccade detection.
            type:        tuple
        """

        pass

    def wait_for_event(self, event):

        """
        desc:
            Waits for an event.

        arguments:
            event:
                desc: |
                    An integer event code, one of the following:

                    - 3 = STARTBLINK
                    - 4 = ENDBLINK
                    - 5 = STARTSACC
                    - 6 = ENDSACC
                    - 7 = STARTFIX
                    - 8 = ENDFIX

                type:    int

        returns:
            desc:    A `self.wait_for_*` method is called, depending on the
                    specified event; the return value of corresponding
                    method is returned.
        """

        pass


    def wait_for_blink_end(self):

        """
        desc: |
            Waits for a blink end and returns the blink ending time.
            Detection based on Dalmaijer et al. (2013) if EVENTDETECTION is set
            to 'pygaze', or using native detection functions if EVENTDETECTION
            is set to 'native' (NOTE: not every system has native functionality;
            will fall back to ;pygaze' if 'native' is not available!)

        returns:
            desc:    Blink ending time in milliseconds, as measured from
                    experiment begin time.
            type:    [int, float]
        """

        pass

    def wait_for_blink_start(self):

        """
        desc: |
            Waits for a blink start and returns the blink starting time.
            Detection based on Dalmaijer et al. (2013) if EVENTDETECTION is set
            to 'pygaze', or using native detection functions if EVENTDETECTION
            is set to 'native' (NOTE: not every system has native functionality;
            will fall back to ;pygaze' if 'native' is not available!)

        returns:
            desc:     Blink starting time in milliseconds, as measured from
                    experiment begin time
            type:    [int, float]
        """

        pass


    def wait_for_fixation_end(self):

        """
        desc: |
            Returns time and gaze position when a fixation has ended;
            function assumes that a 'fixation' has ended when a deviation of
            more than self.pxfixtresh from the initial fixation position has
            been detected (self.pxfixtresh is created in self.calibration,
            based on self.fixtresh, a property defined in self.__init__).
            Detection based on Dalmaijer et al. (2013) if EVENTDETECTION is set
            to 'pygaze', or using native detection functions if EVENTDETECTION
            is set to 'native' (NOTE: not every system has native functionality;
            will fall back to ;pygaze' if 'native' is not available!)

        returns:
            desc:     A `time, gazepos` tuple. Time is the end time in
                    milliseconds (from expstart), gazepos is a (x,y) gaze
                    position tuple of the position from which the fixation was
                    initiated.
            type:    tuple
        """

        pass


    def wait_for_fixation_start(self):

        """
        desc: |
            Returns starting time and position when a fixation is started;
            function assumes a 'fixation' has started when gaze position
            remains reasonably stable (i.e. when most deviant samples are
            within self.pxfixtresh) for five samples in a row (self.pxfixtresh
            is created in self.calibration, based on self.fixtresh, a property
            defined in self.__init__).
            Detection based on Dalmaijer et al. (2013) if EVENTDETECTION is set
            to 'pygaze', or using native detection functions if EVENTDETECTION
            is set to 'native' (NOTE: not every system has native functionality;
            will fall back to ;pygaze' if 'native' is not available!)

        returns:
            desc:     A `time, gazepos` tuple. Time is the starting time in
                    milliseconds (from expstart), gazepos is a (x,y) gaze
                    position tuple of the position from which the fixation was
                    initiated.
            type:    tuple
        """

        pass


    def wait_for_saccade_end(self):

        """
        desc: |
            Returns ending time, starting and end position when a saccade is
            ended; based on Dalmaijer et al. (2013) online saccade detection
            algorithm if EVENTDETECTION is set to 'pygaze', or using native
            detection functions if EVENTDETECTION is set to 'native' (NOTE: not
            every system has native functionality; will fall back to ;pygaze'
            if 'native' is not available!)

        returns:
            desc:    An `endtime, startpos, endpos` tuple. Endtime in
                    milliseconds (from expbegintime); startpos and endpos
                    are (x,y) gaze position tuples.
            type:    tuple
        """

        pass

    def wait_for_saccade_start(self):

        """
        desc: |
            Returns starting time and starting position when a saccade is
            started; based on Dalmaijer et al. (2013) online saccade detection
            algorithm if EVENTDETECTION is set to 'pygaze', or using native
            detection functions if EVENTDETECTION is set to 'native' (NOTE: not
            every system has native functionality; will fall back to ;pygaze'
            if 'native' is not available!)

        returns:
            desc:    An `endtime, startpos` tuple. Endtime in milliseconds (from
                    expbegintime); startpos is an (x,y) gaze position tuple.
            type:    tuple
        """

        pass
