/**
 * Copyright 2012 JogAmp Community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are
 * permitted provided that the following conditions are met:
 *
 *    1. Redistributions of source code must retain the above copyright notice, this list of
 *       conditions and the following disclaimer.
 *
 *    2. Redistributions in binary form must reproduce the above copyright notice, this list
 *       of conditions and the following disclaimer in the documentation and/or other materials
 *       provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * The views and conclusions contained in the software and documentation are those of the
 * authors and should not be interpreted as representing official policies, either expressed
 * or implied, of JogAmp Community.
 */

package jogamp.newt.driver.linux;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import jogamp.newt.WindowImpl;
import jogamp.newt.driver.MouseTracker;

import com.jogamp.common.util.InterruptSource;
import com.jogamp.common.util.PropertyAccess;
import com.jogamp.newt.Screen;
import com.jogamp.newt.Window;
import com.jogamp.newt.event.MouseEvent;
import com.jogamp.newt.event.WindowEvent;
import com.jogamp.newt.event.WindowListener;
import com.jogamp.newt.event.WindowUpdateEvent;

/**
 * Native mouse tracker thread for GNU/Linux.
 * <p>
 * Implementation is reading <code>/dev/input/mice</code>
 * within it's own polling thread.
 * <p>
 * This tracker can be completely disabled by setting property <code>newt.disable.LinuxMouseTracker</code>.
 * </p>
 */
public class LinuxMouseTracker implements WindowListener, MouseTracker {

    private static final boolean DISABLE = PropertyAccess.isPropertyDefined("newt.disable.LinuxMouseTracker", true);
    private static LinuxMouseTracker lmt = null;

    public static synchronized LinuxMouseTracker getSingleton() {
        if(!DISABLE) {
            if( null == lmt ) {
                lmt = new LinuxMouseTracker();
                final Thread t = new InterruptSource.Thread(null, lmt.mouseDevicePoller, "NEWT-MouseTracker");
                t.setDaemon(true);
                t.start();
            }
        }
        return lmt;
    }

    private volatile boolean stop = false;
    private int x = 0;
    private int y = 0;
    private short buttonDown = 0;
    private int old_x = 0;
    private int old_y = 0;
    private volatile int lastFocusedX = 0;
    private volatile int lastFocusedY = 0;
    private short old_buttonDown = 0;
    private WindowImpl focusedWindow = null;
    private final MouseDevicePoller mouseDevicePoller = new MouseDevicePoller();

    private LinuxMouseTracker() {}

    public final int getLastX() { return lastFocusedX; }
    public final int getLastY() { return lastFocusedY; }

    @Override
    public void windowResized(final WindowEvent e) { }

    @Override
    public void windowMoved(final WindowEvent e) { }

    @Override
    public void windowDestroyNotify(final WindowEvent e) {
        final Object s = e.getSource();
        if(focusedWindow == s) {
            focusedWindow = null;
        }
    }

    @Override
    public void windowDestroyed(final WindowEvent e) { }

    @Override
    public void windowGainedFocus(final WindowEvent e) {
        final Object s = e.getSource();
        if(s instanceof WindowImpl) {
            focusedWindow = (WindowImpl) s;
        }
    }

    @Override
    public void windowLostFocus(final WindowEvent e) {
        final Object s = e.getSource();
        if(focusedWindow == s) {
            focusedWindow = null;
        }
    }

    @Override
    public void windowRepaint(final WindowUpdateEvent e) { }

    class MouseDevicePoller implements Runnable {
        @Override
        public void run() {
            final byte[] b = new byte[3];
            final File f = new File("/dev/input/mice");
            f.setReadOnly();
            if(Window.DEBUG_MOUSE_EVENT) {
                System.out.println("LinuxMouseTracker: Started "+f);
            }
            InputStream fis;
            try {
                fis = new FileInputStream(f);
            } catch (final FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                return;
            }
            int xd=0,yd=0; //x/y movement delta
            boolean xo=false,yo=false; // x/y overflow (out of range -255 to +255)
            boolean lb=false,mb=false,rb=false,hs=false,vs=false; //left/middle/right mousebutton
            while(!stop) {
                int remaining=3;
                while(remaining>0) {
                    int read = 0;
                    try {
                        read = fis.read(b, 0, remaining);
                    } catch (final IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    if(read<0) {
                        stop = true; // EOF of mouse !?
                    } else {
                        remaining -= read;
                    }
                }
                lb=(b[0]&1)>0;
                rb=(b[0]&2)>0;
                mb=(b[0]&4)>0;
                hs=(b[0]&16)>0;
                vs=(b[0]&32)>0;
                xo=(b[0]&64)>0;
                yo=(b[0]&128)>0;
                xd=b[1];
                yd=b[2];

                x+=xd;
                y-=yd;

                if(x<0) {
                    x=0;
                }
                if(y<0) {
                    y=0;
                }

                buttonDown = 0;
                if(lb) {
                    buttonDown = MouseEvent.BUTTON1;
                }
                if(mb) {
                    buttonDown = MouseEvent.BUTTON2;
                }
                if(rb) {
                    buttonDown = MouseEvent.BUTTON3;
                }

                if(null != focusedWindow) {
                    // Clip to Screen Size
                    {
                        final Screen focusedScreen = focusedWindow.getScreen();
                        final int sw = focusedScreen.getWidth();
                        final int sh = focusedScreen.getHeight();
                        if( x >= sw ) {
                            x = sw - 1;
                        }
                        if( y >= sh ) {
                            y = sh - 1;
                        }
                    }
                    final int[] winScreenPos = focusedWindow.convertToPixelUnits(new int[] { focusedWindow.getX(), focusedWindow.getY() });
                    final int wx = x - winScreenPos[0], wy = y - winScreenPos[1];
                    if(old_x != x || old_y != y) {
                        // mouse moved
                        lastFocusedX = wx;
                        lastFocusedY = wy;
                        focusedWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_MOVED, 0, wx, wy, (short)0, 0 );
                    }

                    if(old_buttonDown != buttonDown) {
                        // press/release
                        if( 0 != buttonDown ) {
                            focusedWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_PRESSED, 0, wx, wy, buttonDown, 0 );
                        } else {
                            focusedWindow.sendMouseEvent(MouseEvent.EVENT_MOUSE_RELEASED, 0, wx, wy, old_buttonDown, 0 );
                        }
                    }
                } else {
                    if(Window.DEBUG_MOUSE_EVENT) {
                        System.out.println(x+"/"+y+", hs="+hs+",vs="+vs+",lb="+lb+",rb="+rb+",mb="+mb+",xo="+xo+",yo="+yo+"xd="+xd+",yd="+yd);
                    }
                }

                old_x = x;
                old_y = y;
                old_buttonDown = buttonDown;

            }
            if(null != fis) {
                try {
                    fis.close();
                } catch (final IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            if(Window.DEBUG_MOUSE_EVENT) {
                System.out.println("LinuxMouseTracker: Stopped "+f);
            }
        }
    }
}
