/*
 * Copyright (c) 2002-2008 LWJGL Project
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * 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.
 *
 * * Neither the name of 'LWJGL' nor the names of
 *   its contributors may be used to endorse or promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 THE COPYRIGHT OWNER 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.
 */
package org.lwjgl.opengl;

/**
 * A Cocoa implementation of a LWJGL compatible Mouse.
 * @author mojang
 * @author kappaOne <one.kappa@gmail.com>
 */

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import org.lwjgl.input.Mouse;
import org.lwjgl.LWJGLException;

import java.lang.reflect.*;
import java.lang.Integer;
import java.lang.Long;

import org.lwjgl.BufferUtils;

final class MacOSXNativeMouse extends EventQueue {
    private static final int WHEEL_SCALE = 120;
    private static final int NUM_BUTTONS = 3;
    
    private ByteBuffer window_handle;
    private MacOSXDisplay display;
    
    private boolean grabbed;
    
    /** The accumulated mouse deltas returned by poll() */
    private float accum_dx;
    private float accum_dy;
    private int accum_dz;
    
    /** The last mouse position */
    private float last_x;
    private float last_y;
    
    /** Saved control key state for ctrl-click right button emulation */
    private boolean saved_control_state;
    
    private final ByteBuffer event = ByteBuffer.allocate(Mouse.EVENT_SIZE);
    private IntBuffer delta_buffer = BufferUtils.createIntBuffer(2);
    private int skip_event;
    
    private final byte[] buttons = new byte[NUM_BUTTONS];
    
	MacOSXNativeMouse(MacOSXDisplay display, ByteBuffer window_handle) {
        super(Mouse.EVENT_SIZE);
        this.display = display;
        this.window_handle = window_handle;
	}
    
    private native void nSetCursorPosition(ByteBuffer window_handle, int x, int y);

	public static native void nGrabMouse(boolean grab);

	private native void nRegisterMouseListener(ByteBuffer window_handle);
    
	private native void nUnregisterMouseListener(ByteBuffer window_handle);
	
	private static native long nCreateCursor(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, int images_offset, IntBuffer delays, int delays_offset) throws LWJGLException;
	
	private static native void nDestroyCursor(long cursor_handle);
	
	private static native void nSetCursor(long cursor_handle) throws LWJGLException;
    
	public synchronized void register() {
		nRegisterMouseListener(window_handle);
	}
	
	public static long createCursor(int width, int height, int xHotspot, int yHotspot, int numImages, IntBuffer images, IntBuffer delays) throws LWJGLException {
		try {
			return nCreateCursor(width, height, xHotspot, yHotspot, numImages, images, images.position(), delays, delays != null ? delays.position() : -1);
		} catch (LWJGLException e) {
			throw e;
		}
	}
	
	public static void destroyCursor(long cursor_handle) {
		nDestroyCursor(cursor_handle);
	}
	
	public static void setCursor(long cursor_handle) throws LWJGLException {
		try {
			nSetCursor(cursor_handle);
		} catch (LWJGLException e) {
			throw e;
		}
	}
	
    public synchronized void setCursorPosition(int x, int y) {
    		nSetCursorPosition(window_handle, x, y);
    }
    
    public synchronized void unregister() {
        nUnregisterMouseListener(window_handle);
    }
    
    public synchronized void setGrabbed(boolean grabbed) {
        this.grabbed = grabbed;
        nGrabMouse(grabbed);
        skip_event = 1;
        accum_dx = accum_dy = 0;
    }
    
    public synchronized boolean isGrabbed() {
        return grabbed;
    }
    
    protected void resetCursorToCenter() {
        clearEvents();
        accum_dx = accum_dy = 0;
        if (display != null) {
            last_x = display.getWidth() / 2;
            last_y = display.getHeight() / 2;
        }
    }
    
	private void putMouseEvent(byte button, byte state, int dz, long nanos) {
		if (grabbed)
			putMouseEventWithCoords(button, state, 0, 0, dz, nanos);
		else
			putMouseEventWithCoords(button, state, (int)last_x, (int)last_y, dz, nanos);
	}

	protected void putMouseEventWithCoords(byte button, byte state, int coord1, int coord2, int dz, long nanos) {
		event.clear();
		event.put(button).put(state).putInt(coord1).putInt(coord2).putInt(dz).putLong(nanos);
		event.flip();
		putEvent(event);
	}

    public synchronized void poll(IntBuffer coord_buffer, ByteBuffer buttons_buffer) {
        if (grabbed) {
            coord_buffer.put(0, (int)accum_dx);
            coord_buffer.put(1, (int)accum_dy);
        } else {
            coord_buffer.put(0, (int)last_x);
            coord_buffer.put(1, (int)last_y);
        }
        coord_buffer.put(2, accum_dz);
        accum_dx = accum_dy = accum_dz = 0;
        int old_position = buttons_buffer.position();
        buttons_buffer.put(buttons, 0, buttons.length);
        buttons_buffer.position(old_position);
    }
    
	private void setCursorPos(float x, float y, long nanos) {
		if ( grabbed )
			return;
		float dx = x - last_x;
		float dy = y - last_y;
		addDelta(dx, dy);
		last_x = x;
		last_y = y;
		putMouseEventWithCoords((byte)-1, (byte)0, (int)x, (int)y, 0, nanos);
	}
    
    protected void addDelta(float dx, float dy) {
		accum_dx += dx;
		accum_dy += -dy;
	}
    
	public synchronized void setButton(int button, int state, long nanos) {
		buttons[button] = (byte)state;
		putMouseEvent((byte)button, (byte)state, 0, nanos);
	}
    
	public synchronized void mouseMoved(float x, float y, float dx, float dy, float dz, long nanos) {
        if (skip_event > 0) {
            skip_event--;
            if (skip_event == 0) {
                last_x = x;
                last_y = y;
            }
            return;
        }
		if (grabbed) {
            if ( dx != 0 || dy != 0 ) {
                putMouseEventWithCoords((byte)-1, (byte)0, (int)dx, (int)-dy, 0, nanos);
                addDelta(dx, dy);
            }
		} else {
			setCursorPos(x, y, nanos);
		}
        if ( dz != 0 ) {
        		// if no vertical wheel events, then map the horizontal wheel event to it
        		if (dy == 0) dy = dx;
        		
        		int wheel_amount = (int)(dy * WHEEL_SCALE);
            accum_dz += wheel_amount;
            putMouseEvent((byte)-1, (byte)0, wheel_amount, nanos);
        }
	}
}
