package artistmetrics.actions;

import artistmetrics.actions.X11;

import java.util.HashMap;
import java.util.Map;

import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;

public class AFKTracker extends Thread
{
    //The amount of time (in milliseconds between checks)
    private int sleepTime = 1000;
    private int TIME_BEFORE_IDLE = 30;
    
    private State status = State.ONLINE; 
    
    private int UNX_xpos = 0;
    private int UNX_ypos = 0;
    
    //The difference in time (in milliseconds) between 1/1/1970 and when the cursor last moved...
    private long cursorLastMovedAt = 0;
    private int lastInactiveTime = 0;
    
    private User user;
    
    public AFKTracker(User user)
    {
        this.user = user;
        this.start();
    }
    
    public void run()
    {
        user.writeToLog("Starting AFK timer...");
        
        while(true)
        {
            try
            {
                int inactiveTime = getInactiveTime() / 1000;

                if(inactiveTime > TIME_BEFORE_IDLE && status != State.AWAY)
                {
                    user.writeToLog("You are now idle.");
                    Map<String,String> map = new HashMap<String,String>();
                    map.put("idle", "");
                    user.sendMetric("idle-start", map);
                    
                    //Record that the user is now AFK...
                    status = State.AWAY;
                }
                else if(status == State.AWAY && inactiveTime < TIME_BEFORE_IDLE)
                {
                    //Record the user as back at their computer.
                    user.writeToLog("You are back at your computer after being idle for: " + lastInactiveTime + " seconds.");
                    
                    Map<String,String> map = new HashMap<String,String>();
                    map.put("idletime", "" + lastInactiveTime);
                    user.sendMetric("idle-end", map);
                    
                    status = State.ONLINE;
                    
                    lastInactiveTime = 0;
                    cursorLastMovedAt = 0;
                }
                
                lastInactiveTime = inactiveTime;
                
                Thread.sleep(sleepTime);
            }
            catch (Exception ex)
            {
            }
        }
    }
    
    public int getInactiveTime()
    {
        if(System.getProperty("os.name").contains("Windows"))
            return DoWindows();
        else
            return DoUnix();
    }
    
    enum State 
    {
        ONLINE, AWAY, IDLE
    };
    
    //WINDOWS - Kernel32
    public interface Kernel32 extends StdCallLibrary
    {
        Kernel32 INSTANCE = (Kernel32)Native.loadLibrary("kernel32", Kernel32.class);
    
        /**
        * Retrieves the number of milliseconds that have elapsed since the system was started.
        * @see http://msdn2.microsoft.com/en-us/library/ms724408.aspx
        * @return number of milliseconds that have elapsed since the system was started.
        */
        public int GetTickCount();
    };

    //WINDOWS - User32
    public interface User32 extends StdCallLibrary
    { 
        User32 INSTANCE = (User32)Native.loadLibrary("user32", User32.class);
    
        /**
         * Contains the time of the last input.
         * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputstructures/lastinputinfo.asp
         */
        public static class LASTINPUTINFO extends Structure 
        {
            public int cbSize = 8;
            
            /// Tick count of when the last input event was received.
            public int dwTime;
        }
        
        /**
            * Retrieves the time of the last input event.
            * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/getlastinputinfo.asp
            * @return time of the last input event, in milliseconds
            */
        public boolean GetLastInputInfo(LASTINPUTINFO result);
    };
    
    /**
     * Get the amount of milliseconds that have elapsed since the last input event
     * (mouse or keyboard)
     * @return idle time in milliseconds
     */
    public static int getIdleTimeMillisWin32() 
    {
        User32.LASTINPUTINFO lastInputInfo = new User32.LASTINPUTINFO();
        User32.INSTANCE.GetLastInputInfo(lastInputInfo);
        return Kernel32.INSTANCE.GetTickCount() - lastInputInfo.dwTime;
    }
    
    
    public int DoWindows()
    {
        return getIdleTimeMillisWin32();
    }

    public int DoUnix()
    {
        int idleTime = 0;
        
        //Create an instance of the X11 handler class...
        X11 x = X11.INSTANCE;
        X11.Display display = x.XOpenDisplay(null);
        X11.Window window = x.XRootWindow(display, 0);
        
        X11.WindowByReference root = new X11.WindowByReference();
        X11.WindowByReference child = new X11.WindowByReference();
        IntByReference root_x = new IntByReference();
        IntByReference root_y = new IntByReference();
        IntByReference win_x = new IntByReference();
        IntByReference win_y = new IntByReference();
        IntByReference mask_return = new IntByReference();
        
        if(x.XQueryPointer(display,window,root,child,root_x,root_y,win_x,win_y,mask_return))
        {
            if(UNX_xpos == root_x.getValue() && UNX_ypos == root_y.getValue())
            {
                if(status != State.IDLE && status != State.AWAY)
                {
                    status = State.IDLE;
                    cursorLastMovedAt = System.currentTimeMillis() - sleepTime;
                    idleTime = sleepTime;
                }
                else
                {
                    idleTime = (int)(System.currentTimeMillis() - cursorLastMovedAt);
                }
                
            }
            else
            {
                UNX_xpos = root_x.getValue();
                UNX_ypos = root_y.getValue();
            }
        }
        
        x.XCloseDisplay(display);
        
        return idleTime;
    }
}
