#!/usr/bin/env python
# -*- coding: utf-8 -*-

from math import sqrt
import gc
import pylink
from pylink import getEYELINK
from expyriment.stimuli import Circle
from expyriment.misc import constants
from expyriment.misc.geometry import coordinates2position

def init_eyelink(exp, dummy=False):
    """
    Initializes the eytracker for the experiment. 

    Parameters
    ----------
    exp : expyriment experiment
        The experiment object

    dummy : bool
        should tracker run in dummy mode
        
    Returns
    -------
    tracker : EyeLink object
        the tracker object
    edfFileName : str
        name of the EDF file

    """
    if dummy:
        tracker = pylink.EyeLink(None)
    else:
        tracker = pylink.EyeLink("100.1.1.1") #defaults to tracker address "100.1.1.1"

    pylink.openGraphics()

    # subject number
    if exp.subject is not None: # not in developer mode
        subject_code = str(exp.subject)
    else:
        subject_code = "XX"

    # filename    
    edfFileName = subject_code + ".EDF"
    #print edfFileName
    
    pylink.getEYELINK().openDataFile(edfFileName)
    
    pylink.flushGetkeyQueue(); #gets rid of old keys from the tracker key queue
    pylink.getEYELINK().setOfflineMode();                          

    screen_size = exp.screen.size
    #print screen_size

    #left, top, right, bottom coordinates:
    pylink.getEYELINK().sendCommand("screen_pixel_coords =  0 0 %d %d" %(screen_size[0] - 1, screen_size[1] - 1))
    pylink.getEYELINK().sendMessage("DISPLAY_COORDS  0 0 %d %d" %(screen_size[0] - 1, screen_size[1] - 1))
##    pylink.getEYELINK().sendCommand("screen_pixel_coords =  %d %d %d %d" %(-screen_size[0]/2 + 1, screen_size[1]/2 - 1, 
##                                                                           screen_size[0]/2 - 1, -screen_size[1]/2 + 1))
##    pylink.getEYELINK().sendMessage("DISPLAY_COORDS  %d %d %d %d" %(-screen_size[0]/2 + 1, screen_size[1]/2 - 1, 
##                                                                           screen_size[0]/2 - 1, -screen_size[1]/2 + 1))

    tracker_software_ver = 0
    eyelink_ver = pylink.getEYELINK().getTrackerVersion()
    if eyelink_ver == 3:
        tvstr = pylink.getEYELINK().getTrackerVersionString()
        vindex = tvstr.find("EYELINK CL")
        tracker_software_ver = int(float(tvstr[(vindex + len("EYELINK CL")):].strip()))    

    if eyelink_ver>=2:
        pylink.getEYELINK().sendCommand("select_parser_configuration 0")
    if eyelink_ver == 2: #turn off scenelink camera stuff
        pylink.getEYELINK().sendCommand("scene_camera_gazemap = NO")
    else:
        pylink.getEYELINK().sendCommand("saccade_velocity_threshold = 35")
        pylink.getEYELINK().sendCommand("saccade_acceleration_threshold = 9500")
    
    # set EDF file contents 
    pylink.getEYELINK().sendCommand("file_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,MESSAGE,BUTTON")
    if tracker_software_ver>=4:
        pylink.getEYELINK().sendCommand("file_sample_data  = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS,HTARGET")
    else:
        pylink.getEYELINK().sendCommand("file_sample_data  = LEFT,RIGHT,GAZE,AREA,GAZERES,STATUS")

    # set link data (used for gaze cursor) 
    pylink.getEYELINK().sendCommand("link_event_filter = LEFT,RIGHT,FIXATION,SACCADE,BLINK,BUTTON")
    if tracker_software_ver>=4:
        pylink.getEYELINK().sendCommand("link_sample_data  = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS,HTARGET")
    else:
        pylink.getEYELINK().sendCommand("link_sample_data  = LEFT,RIGHT,GAZE,GAZERES,AREA,STATUS")
    #button to accept fixation during drift
    pylink.getEYELINK().sendCommand("button_function %d 'accept_target_fixation'"%(5));
    #pylink.getEYELINK().sendCommand("button_function %d 'accept_target_fixation'"%(pylink.ENTER_KEY));

    bg_col = exp.background_colour
    fg_col = exp.foreground_colour
    pylink.setCalibrationColors(fg_col, bg_col)
    pylink.setTargetSize(screen_size[0] / 70, screen_size[0] / 300);    #select best size for calibration target
    pylink.setCalibrationSounds("", "", "");
    pylink.setDriftCorrectSounds("", "off", "off");

    return tracker, edfFileName

def start_recording(disable_gc = True):
    error = getEYELINK().startRecording(1, 1, 1, 1) #0 if successful, takes 10-30 ms for recording to begin

    #print getEYELINK().getTrackerMode()
    if error:
        print ("ERROR: Could not start Recording!")
        exit()

    if disable_gc:
        gc.disable() #disable python garbage collection to avoid delays
    pylink.beginRealTimeMode(100) #sets system priority highest (for windows?) and waits 100 ms

    #wait for sample data via link for max 1000 ms
    if not getEYELINK().waitForBlockStart(1000,1,0): #returns true if data available
        end_recording()
        print ("ERROR: No link samples received!")
        getEYELINK().sendMessage("TRIAL ERROR")
        return 

def end_eyelink():
    '''
    Cleanup and close tracker.
    '''
    if pylink.getEYELINK() != None:
        # File transfer and cleanup!
        pylink.getEYELINK().setOfflineMode();                          
        pylink.msecDelay(500);                 

        #Close the file and transfer it to Display PC
        pylink.getEYELINK().closeDataFile()
        #pylink.getEYELINK().receiveDataFile(edfFileName, edfFileName)
        pylink.getEYELINK().close();
        pylink.closeGraphics()


def end_recording(enable_gc = True):
    '''
    Ends recording: adds 100 msec of data to catch final events
    '''
    pylink.getEYELINK().sendMessage("REC_STOP");
    pylink.endRealTimeMode();  #set system priority back to normal (still slightly higher though)
    pylink.pumpDelay(100);       
    pylink.getEYELINK().stopRecording();
    #process and dispatch any waiting messages:
    while pylink.getEYELINK().getkey() : #0 if no key pressed
        pass;
    if enable_gc:
        gc.enable() #re-enable python garbage collection to do memory cleanup at the end of trial



def do_drift(exp):
    #The loop does drift correction at the start of each trial
    while True:
    #while pylink.getEYELINK().getkey() != pylink.ESC_KEY:
        # Checks whether we are still connected to the tracker
        if not pylink.getEYELINK().isConnected():
            return ABORT_EXPT           
        # Does drift correction and handles the re-do camera setup situations
        try:
            error = pylink.getEYELINK().doDriftCorrect(exp.screen.center_x, exp.screen.center_y, 1, 0) #0 if successful
            if error != 27: #27 = escape key pressed at tracker to enter tracker setup menu
                return 
            else:
                pylink.getEYELINK().doTrackerSetup() #switches the EyeLink tracker to the setup menu
        except:
            pylink.getEYELINK().doTrackerSetup()


class GazeDetector():

    def __init__(self, position_array, max_deviation, expyriment_screen):
        """ """
        self.position_array = position_array
        self.max_deviation = max_deviation
        self.screen = expyriment_screen
        self._prev_fixation = -1
        self._prev_gaze = [0,0]
        self.fixation_changed = False
        

    def get_fixation(self, present_gaze = False):
        """Returns the id of the position in the position array that is currently
        fixated. Returns None, if no predefined position is fixated.
        """
        
        error = getEYELINK().isRecording()  #check if still recording 
        if error:
            end_recording();
            return str(error) + 'error' #ausgabe

        fixation = None
        s = getEYELINK().getNewestSample() # check for new sample update
        if s is not None: #depending on the eye get the x,y coordinates
            if s.isRightSample():
                gaze = coordinates2position(s.getRightEye().getGaze())
            elif s.isLeftSample():
                gaze = coordinates2position(s.getLeftEye().getGaze())
            else:
                return None

            for cnt, pos in enumerate(self.position_array):
                if sqrt((pos[0]-gaze[0])**2 + (pos[1]-gaze[1])**2) < self.max_deviation:
                    fixation = cnt
                    break
        else:
            return None

        self.fixation_changed = (fixation != self._prev_fixation)
        
        self._prev_fixation = fixation
        
        if present_gaze:
            Circle(10, colour=constants.C_GREY,
                    position=self._prev_gaze).present(update=False, clear=False)
            Circle(10, colour=constants.C_RED,
                    position=gaze).present(update=False, clear=False)

            for cnt, pos in enumerate(self.position_array):
               if fixation == cnt:
                   colour = constants.C_GREEN
               else:
                   colour = constants.C_BLACK   
               Circle(self.max_deviation, colour=colour,
                    position=pos).present(update=False, clear=False)
                
            self.screen.update()
        self._prev_gaze = gaze
        return fixation
