package edu.ysu.itrace;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Queue;

import org.eclipse.e4.core.services.events.IEventBroker;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.plugin.AbstractUIPlugin;
import org.osgi.framework.BundleContext;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventHandler;

import edu.ysu.itrace.exceptions.CalibrationException;
import edu.ysu.itrace.exceptions.EyeTrackerConnectException;
import edu.ysu.itrace.gaze.IGazeHandler;
import edu.ysu.itrace.gaze.IGazeResponse;
import edu.ysu.itrace.gaze.IStyledTextGazeResponse;
import edu.ysu.itrace.preferences.PluginPreferences;
import edu.ysu.itrace.solvers.ISolver;
import edu.ysu.itrace.solvers.JSONGazeExportSolver;
import edu.ysu.itrace.solvers.XMLGazeExportSolver;
import edu.ysu.itrace.trackers.IEyeTracker;

/**
 * The activator class controls the plug-in life cycle
 */
public class ITrace extends AbstractUIPlugin implements EventHandler {

    // The plug-in ID
    public static final String PLUGIN_ID = "edu.ysu.itrace"; //$NON-NLS-1$
    public long sessionStartTime;
    public Rectangle monitorBounds;
    // The shared instance
    private static ITrace plugin;
    private IEditorPart activeEditor;
    private HashMap<IEditorPart,TokenHighlighter> tokenHighlighters = new HashMap<IEditorPart,TokenHighlighter>();
    private boolean showTokenHighlights = false;
    
    private IEyeTracker tracker = null;
    private volatile boolean recording;
    private JSONGazeExportSolver jsonSolver;
    private XMLGazeExportSolver xmlSolver;
    private boolean jsonOutput = true;
    private boolean xmlOutput = true;
    
    private IActionBars actionBars;
    private IStatusLineManager statusLineManager;
    private long registerTime = 2000;
    private IEventBroker eventBroker;
    private SessionInfoHandler sessionInfo = new SessionInfoHandler();
    
    private Shell rootShell;
    
    /**
     * The constructor
     */
    public ITrace() {
    	IEditorPart editorPart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
    	/*
    	if(editorPart != null){
	    	StyledText styledText = (StyledText) editorPart.getAdapter(Control.class);
	    	if(styledText != null){
				ITextOperationTarget t = (ITextOperationTarget) activeEditor.getAdapter(ITextOperationTarget.class);
				if(t instanceof ProjectionViewer){
					ProjectionViewer projectionViewer = (ProjectionViewer)t;
					tokenHighlighters.put(activeEditor, new TokenHighlighter(styledText, showTokenHighlights, projectionViewer));
				}
			}
    	}
    	*/
    	eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class);
    	eventBroker.subscribe("iTrace/newgaze", this);
    	jsonSolver = new JSONGazeExportSolver();
    	xmlSolver = new XMLGazeExportSolver();
    	eventBroker.subscribe("iTrace/jsonOutput", jsonSolver);
    	eventBroker.subscribe("iTrace/xmlOutput", xmlSolver);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)
     */
    public void start(BundleContext context) throws Exception {
        super.start(context);
        plugin = this;
        IPreferenceStore prefStore = getDefault().getPreferenceStore();
        EyeTrackerFactory.setTrackerType(EyeTrackerFactory.TrackerType.valueOf(
                prefStore.getString(PluginPreferences.EYE_TRACKER_TYPE)));
        activeEditor = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActiveEditor();
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)
     */
    public void stop(BundleContext context) throws Exception {
    	if (recording) {
            stopTracking();
        }
        plugin = null;
        super.stop(context);
    }

    /**
     * Returns the shared instance
     *
     * @return the shared instance
     */
    public static ITrace getDefault() {
        return plugin;
    }
    
    public IEyeTracker getTracker(){
    	return tracker;
    }
    
    public void setTrackerXDrift(int drift){
    	tracker.setXDrift(drift);
    }
    public void setTrackerYDrift(int drift){
    	tracker.setYDrift(drift);
    }
    
    public Shell getRootShell(){
    	return rootShell;
    }
    public void setRootShell(Shell shell){
    	rootShell = shell;
    }
    
    public void setActionBars(IActionBars bars){
    	actionBars = bars;
    	statusLineManager = actionBars.getStatusLineManager();
    }
    
    public void setLineManager(IStatusLineManager manager){
    	statusLineManager = manager;
    }
    
    public void setJsonOutput(boolean value){
    	jsonOutput = value;
    }
    public void displayJsonExportFile(){
    	jsonSolver.displayExportFile();
    }
    
    public void setXmlOutput(boolean value){
    	xmlOutput = value;
    }
    public void displayXmlExportFile(){
    	xmlSolver.displayExportFile();
    }
    
    public boolean sessionInfoConfigured(){
    	return sessionInfo.isConfigured();
    }
    
    public void calibrateTracker(){
    	requestTracker();
        if (tracker != null) {
            try {
                tracker.calibrate();
            } catch (CalibrationException e1) {
            	eventBroker.post("iTrace/error", e1.getMessage());
            }
        } else {
            // If tracker is none, requestTracker() would have already
            // raised an error.
        }
    }
    
    public boolean startTracking() {
        if (recording) {
            eventBroker.post("iTrace/error", "Tracking is already in progress.");
            return recording;
        }
        if (!requestTracker()) {
            // Error handling occurs in requestTracker(). Just return and
            // pretend
            // nothing happened.
            return recording;
        }
        //eventBroker.subscribe("iTrace/newgaze", this);
        
        if (!sessionInfo.isConfigured()) {
        	eventBroker.post("iTrace/error", "You have not configured your Session Info.");
        	return recording;
        }
        
        try {
        	sessionInfo.export();
        } catch(IOException e) {
        	System.out.println(e.getMessage());
        }
        ITrace.getDefault().sessionStartTime = System.nanoTime();
        recording = true;
        return recording;
    }
    
    public boolean stopTracking() {
        if (!recording) {
        	eventBroker.post("iTrace/error", "Tracking is not in progress.");
            return false;
        }
        sessionInfo.reset();
        
        xmlSolver.dispose();
        jsonSolver.dispose();
        
        if (tracker != null) {
        } else {
            // If there is no tracker, tracking should not be occurring anyways.
            System.out.println("No Tracker");
        }
        statusLineManager.setMessage("");

        recording = false;
        return true;
    }
    
    public boolean toggleTracking(){
    	if(recording) return stopTracking();
    	else return startTracking();
    }
    
    public boolean displayCrosshair(boolean display){
    	if (tracker == null)
            requestTracker();
        if (tracker != null) {
        	tracker.displayCrosshair(display);
        	return display;
        }
       	return !display;
    }
    
    public void configureSessionInfo(){
    	sessionInfo.config();
    	if (sessionInfo.isConfigured()) {
    		xmlSolver.config(sessionInfo.getSessionID(),
    				sessionInfo.getDevUsername());
    		jsonSolver.config(sessionInfo.getSessionID(),
    				sessionInfo.getDevUsername());
    	}
    }
    
    public void setActiveEditor(IEditorPart editorPart){
    	activeEditor = editorPart;
    	if(activeEditor == null) return;
    	if(!tokenHighlighters.containsKey(editorPart)){
    		StyledText styledText = (StyledText) editorPart.getAdapter(Control.class);
    		if(styledText != null){
				ITextOperationTarget t = (ITextOperationTarget) activeEditor.getAdapter(ITextOperationTarget.class);
				if(t instanceof ProjectionViewer){
					ProjectionViewer projectionViewer = (ProjectionViewer)t;
					tokenHighlighters.put(activeEditor, new TokenHighlighter(styledText, showTokenHighlights, projectionViewer));
				}
			}
    	}
    	
    }
    
    public void displayEyeStatus(){
    	if (tracker == null)
            requestTracker();
        if (tracker != null){
        	EyeStatusWindow statusWindow = new EyeStatusWindow();
        	statusWindow.setVisible(true);
        }
    }
    
    public void activateHighlights(){
    	if (tracker == null)
            requestTracker();
		
        
        if (tracker != null){
        	showTokenHighLights();
        }
    }
    
    public void removeHighlighter(IEditorPart editorPart){
    	tokenHighlighters.remove(editorPart);
    }
    
    public void showTokenHighLights(){
    	showTokenHighlights = !showTokenHighlights;
    	if(activeEditor == null) return;
		if(!tokenHighlighters.containsKey(activeEditor)){
			StyledText styledText = (StyledText) activeEditor.getAdapter(Control.class);
			if(styledText != null){
				ITextOperationTarget t = (ITextOperationTarget) activeEditor.getAdapter(ITextOperationTarget.class);
				if(t instanceof ProjectionViewer){
					ProjectionViewer projectionViewer = (ProjectionViewer)t;
					tokenHighlighters.put(activeEditor, new TokenHighlighter(styledText, showTokenHighlights, projectionViewer));
				}
			}
		}
    	for(TokenHighlighter tokenHighlighter: tokenHighlighters.values()){
    		tokenHighlighter.setShow(showTokenHighlights);
    	}
    }
    
    private boolean requestTracker() {
        if (tracker != null) {
            // Already have a tracker. Don't need another.
            return true;
        }
        try {
            tracker = EyeTrackerFactory.getConcreteEyeTracker();
            if (tracker != null) {
            	tracker.startTracking();
                return true;
            }else{
            	return false;
            }
        } catch (EyeTrackerConnectException e) {
            return false;
        } catch (IOException e) {
            return false;
        }
    }
    
    /**
     * Finds the control under the specified screen coordinates and calls its
     * gaze handler on the localized point. Returns the gaze response or null if
     * the gaze is not handled.
     */
    private IGazeResponse handleGaze(int screenX, int screenY, Gaze gaze){
    	Queue<Control[]> childrenQueue = new LinkedList<Control[]>();
    	childrenQueue.add(rootShell.getChildren());
    	Rectangle monitorBounds = rootShell.getMonitor().getBounds();
        while (!childrenQueue.isEmpty()) {
            for (Control child : childrenQueue.remove()) {
                Rectangle childScreenBounds = child.getBounds();
                Point screenPos = child.toDisplay(0, 0);
                childScreenBounds.x = screenPos.x - monitorBounds.x;
                childScreenBounds.y = screenPos.y - monitorBounds.y;
                if (childScreenBounds.contains(screenX, screenY)) {
                    if (child instanceof Composite) {
                        Control[] nextChildren =
                                ((Composite) child).getChildren();
                        if (nextChildren.length > 0 && nextChildren[0] != null) {
                            childrenQueue.add(nextChildren);
                        }
                    }
                    IGazeHandler handler =
                            (IGazeHandler) child
                                    .getData(HandlerBindManager.KEY_HANDLER);
                    if (child.isVisible() && handler != null) {
                        return handler.handleGaze(screenX, screenY,
                                screenX - childScreenBounds.x, screenY
                                        - childScreenBounds.y, gaze);
                    }
                }
            }
        }
        return null;
    }
    
    @Override
	public void handleEvent(Event event) {
		if(event.getTopic() == "iTrace/newgaze"){
			String[] propertyNames = event.getPropertyNames();
			Gaze g = (Gaze)event.getProperty(propertyNames[0]);
			 if (g != null) {
	             if(!rootShell.isDisposed()){
	            	 Rectangle monitorBounds = rootShell.getMonitor().getBounds();
	            	 int screenX = (int) (g.getX() * monitorBounds.width);
		             int screenY = (int) (g.getY() * monitorBounds.height);
		             IGazeResponse response;
	            	 response = handleGaze(screenX, screenY, g);
	            	 
	            	 if (response != null) {
		                	 if(recording){
		                		 statusLineManager
		                 			.setMessage(String.valueOf(response.getGaze().getSessionTime()));
		                 		registerTime = System.currentTimeMillis();
		                 		if(xmlOutput) eventBroker.post("iTrace/xmlOutput", response);
		                 		if(jsonOutput) eventBroker.post("iTrace/jsonOutput", response);
		                	 }
		                     
		                     if(response instanceof IStyledTextGazeResponse && response != null && showTokenHighlights){
		                     	IStyledTextGazeResponse styledTextResponse = (IStyledTextGazeResponse)response;
		                     	eventBroker.post("iTrace/newstresponse", styledTextResponse);
		                     }
		             }
		         }else{
		         	if((System.currentTimeMillis()-registerTime) > 2000){
		         		statusLineManager.setMessage("");
		         	}
		         }
	         }
		}
	}
}