/*
 * %W% %E%
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*****************************************************************************
* Copyright (c) 2003 Sun Microsystems, Inc.  All Rights Reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistribution of source code must retain the above copyright notice,
*   this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright notice,
*   this list of conditions and the following disclaimer in the documentation
*   and/or other materails provided with the distribution.
*
* Neither the name Sun Microsystems, Inc. or the names of the contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind.
* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
* ANY IMPLIED WARRANT OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
* NON-INFRINGEMEN, ARE HEREBY EXCLUDED.  SUN MICROSYSTEMS, INC. ("SUN") AND
* ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS
* A RESULT OF USING, MODIFYING OR DESTRIBUTING THIS SOFTWARE OR ITS
* DERIVATIVES.  IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST
* REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL,
* INCIDENTAL OR PUNITIVE DAMAGES.  HOWEVER CAUSED AND REGARDLESS OF THE THEORY
* OF LIABILITY, ARISING OUT OF THE USE OF OUR INABILITY TO USE THIS SOFTWARE,
* EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for us in
* the design, construction, operation or maintenance of any nuclear facility
*
*****************************************************************************/
package net.java.games.input;

import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Iterator;
import java.util.logging.Logger;

/** OSX HIDManager implementation
* @author elias
* @author gregorypierce
* @version 1.0
*/
final class OSXHIDDevice {
	private final static Logger log = Logger.getLogger(OSXHIDDevice.class.getName());
	private final static int AXIS_DEFAULT_MIN_VALUE = 0;
	private final static int AXIS_DEFAULT_MAX_VALUE = 64*1024;
	
	private final static String kIOHIDTransportKey                  = "Transport";
	private final static String kIOHIDVendorIDKey                   = "VendorID";
	private final static String kIOHIDVendorIDSourceKey             = "VendorIDSource";
	private final static String kIOHIDProductIDKey                  = "ProductID";
	private final static String kIOHIDVersionNumberKey              = "VersionNumber";
	private final static String kIOHIDManufacturerKey               = "Manufacturer";
	private final static String kIOHIDProductKey                    = "Product";
	private final static String kIOHIDSerialNumberKey               = "SerialNumber";
	private final static String kIOHIDCountryCodeKey                = "CountryCode";
	private final static String kIOHIDLocationIDKey                 = "LocationID";
	private final static String kIOHIDDeviceUsageKey                = "DeviceUsage";
	private final static String kIOHIDDeviceUsagePageKey            = "DeviceUsagePage";
	private final static String kIOHIDDeviceUsagePairsKey           = "DeviceUsagePairs";
	private final static String kIOHIDPrimaryUsageKey               = "PrimaryUsage";
	private final static String kIOHIDPrimaryUsagePageKey           = "PrimaryUsagePage";
	private final static String kIOHIDMaxInputReportSizeKey     = "MaxInputReportSize";
	private final static String kIOHIDMaxOutputReportSizeKey        = "MaxOutputReportSize";
	private final static String kIOHIDMaxFeatureReportSizeKey       = "MaxFeatureReportSize";

	private final static String kIOHIDElementKey                    = "Elements";

	private final static String kIOHIDElementCookieKey              = "ElementCookie";
	private final static String kIOHIDElementTypeKey                = "Type";
	private final static String kIOHIDElementCollectionTypeKey      = "CollectionType";
	private final static String kIOHIDElementUsageKey               = "Usage";
	private final static String kIOHIDElementUsagePageKey           = "UsagePage";
	private final static String kIOHIDElementMinKey                 = "Min";
	private final static String kIOHIDElementMaxKey                 = "Max";
	private final static String kIOHIDElementScaledMinKey           = "ScaledMin";
	private final static String kIOHIDElementScaledMaxKey           = "ScaledMax";
	private final static String kIOHIDElementSizeKey                = "Size";
	private final static String kIOHIDElementReportSizeKey          = "ReportSize";
	private final static String kIOHIDElementReportCountKey         = "ReportCount";
	private final static String kIOHIDElementReportIDKey            = "ReportID";
	private final static String kIOHIDElementIsArrayKey             = "IsArray";
	private final static String kIOHIDElementIsRelativeKey          = "IsRelative";
	private final static String kIOHIDElementIsWrappingKey          = "IsWrapping";
	private final static String kIOHIDElementIsNonLinearKey         = "IsNonLinear";
	private final static String kIOHIDElementHasPreferredStateKey   = "HasPreferredState";
	private final static String kIOHIDElementHasNullStateKey        = "HasNullState";
	private final static String kIOHIDElementUnitKey                = "Unit";
	private final static String kIOHIDElementUnitExponentKey        = "UnitExponent";
	private final static String kIOHIDElementNameKey                = "Name";
	private final static String kIOHIDElementValueLocationKey       = "ValueLocation";
	private final static String kIOHIDElementDuplicateIndexKey      = "DuplicateIndex";
	private final static String kIOHIDElementParentCollectionKey    = "ParentCollection";

	private final long device_address;
	private final long device_interface_address;
	private final Map properties;

	private boolean released;

	public OSXHIDDevice(long device_address, long device_interface_address) throws IOException {
		this.device_address = device_address;
		this.device_interface_address = device_interface_address;
		this.properties = getDeviceProperties();
		open();
	}

	public final Controller.PortType getPortType() {
		String transport = (String)properties.get(kIOHIDTransportKey);
		if (transport == null)
			return Controller.PortType.UNKNOWN;
		if (transport.equals("USB")) {
			return Controller.PortType.USB;
		} else {
			return Controller.PortType.UNKNOWN;
		}
	}

	public final String getProductName() {
		return (String)properties.get(kIOHIDProductKey);
	}

	private final OSXHIDElement createElementFromElementProperties(Map element_properties) {
	/*	long size = getLongFromProperties(element_properties, kIOHIDElementSizeKey);
		// ignore elements that can't fit into the 32 bit value field of a hid event
		if (size > 32)
			return null;*/
		long element_cookie = getLongFromProperties(element_properties, kIOHIDElementCookieKey);
		int element_type_id = getIntFromProperties(element_properties, kIOHIDElementTypeKey);
		ElementType element_type = ElementType.map(element_type_id);
		int min = (int)getLongFromProperties(element_properties, kIOHIDElementMinKey, AXIS_DEFAULT_MIN_VALUE);
		int max = (int)getLongFromProperties(element_properties, kIOHIDElementMaxKey, AXIS_DEFAULT_MAX_VALUE);
/*		long scaled_min = getLongFromProperties(element_properties, kIOHIDElementScaledMinKey, Long.MIN_VALUE);
		long scaled_max = getLongFromProperties(element_properties, kIOHIDElementScaledMaxKey, Long.MAX_VALUE);*/
		UsagePair device_usage_pair = getUsagePair();
		boolean default_relative = device_usage_pair != null && (device_usage_pair.getUsage() == GenericDesktopUsage.POINTER || device_usage_pair.getUsage() == GenericDesktopUsage.MOUSE);
		
		boolean is_relative = getBooleanFromProperties(element_properties, kIOHIDElementIsRelativeKey, default_relative);
/*		boolean is_wrapping = getBooleanFromProperties(element_properties, kIOHIDElementIsWrappingKey);
		boolean is_non_linear = getBooleanFromProperties(element_properties, kIOHIDElementIsNonLinearKey);
		boolean has_preferred_state = getBooleanFromProperties(element_properties, kIOHIDElementHasPreferredStateKey);
		boolean has_null_state = getBooleanFromProperties(element_properties, kIOHIDElementHasNullStateKey);*/
		int usage = getIntFromProperties(element_properties, kIOHIDElementUsageKey);
		int usage_page = getIntFromProperties(element_properties, kIOHIDElementUsagePageKey);
		UsagePair usage_pair = createUsagePair(usage_page, usage);
		if (usage_pair == null || (element_type != ElementType.INPUT_MISC && element_type != ElementType.INPUT_BUTTON && element_type != ElementType.INPUT_AXIS)) {
			//log.info("element_type = 0x" + element_type + " | usage = " + usage + " | usage_page = " + usage_page);
			return null;
		} else {
			return new OSXHIDElement(this, usage_pair, element_cookie, element_type, min, max, is_relative);
		}
	}

	private final void addElements(List elements, Map properties) {
		Object[] elements_properties = (Object[])properties.get(kIOHIDElementKey);
		if (elements_properties == null)
			return;
		for (int i = 0; i < elements_properties.length; i++) {
			Map element_properties = (Map)elements_properties[i];
			OSXHIDElement element = createElementFromElementProperties(element_properties);
			if (element != null) {
				elements.add(element);
			}
			addElements(elements, element_properties);
		}
	}
	
	public final List getElements() {
		List elements = new ArrayList();
		addElements(elements, properties);
		return elements;
	}
	
	private final static long getLongFromProperties(Map properties, String key, long default_value) {
		Long long_obj = (Long)properties.get(key);
		if (long_obj == null)
			return default_value;
		return long_obj.longValue();
	}

	private final static boolean getBooleanFromProperties(Map properties, String key, boolean default_value) {
		return getLongFromProperties(properties, key, default_value ? 1 : 0) != 0;
	}

	private final static int getIntFromProperties(Map properties, String key) {
		return (int)getLongFromProperties(properties, key);
	}

	private final static long getLongFromProperties(Map properties, String key) {
		Long long_obj = (Long)properties.get(key);
		return long_obj.longValue();
	}

	private final static UsagePair createUsagePair(int usage_page_id, int usage_id) {
		UsagePage usage_page = UsagePage.map(usage_page_id);
		if (usage_page != null) {
			Usage usage = usage_page.mapUsage(usage_id);
			if (usage != null)
				return new UsagePair(usage_page, usage);
		}
		return null;
	}

	public final UsagePair getUsagePair() {
		int usage_page_id = getIntFromProperties(properties, kIOHIDPrimaryUsagePageKey);
		int usage_id = getIntFromProperties(properties, kIOHIDPrimaryUsageKey);
		return createUsagePair(usage_page_id, usage_id);
	}
	
/*
	public final List getUsagePairs() {
		List usage_pairs_list = new ArrayList();
		Object[] usage_pairs = (Object[])properties.get(kIOHIDDeviceUsagePairsKey);
		if (usage_pairs == null) {
			int usage_page_id = getIntFromProperties(properties, kIOHIDPrimaryUsagePageKey);
			int usage_id = getIntFromProperties(properties, kIOHIDPrimaryUsageKey);
			UsagePair pair = createUsagePair(usage_page_id, usage_id);
			if (pair != null)
				usage_pairs_list.add(pair);
		}
		for (int i = 0; i < usage_pairs.length; i++) {
			Map usage_pair = (Map)usage_pairs[i];
			int usage_page_id = getIntFromProperties(usage_pair, kIOHIDDeviceUsagePageKey);
			int usage_id = getIntFromProperties(usage_pair, kIOHIDDeviceUsageKey);
			UsagePair pair = createUsagePair(usage_page_id, usage_id);
			if (pair != null)
				usage_pairs_list.add(pair);
		}
		return usage_pairs_list;
	}
*/	
	private final void dumpProperties() {
		log.info(toString());
		dumpMap("", properties);
	}

	private final static void dumpArray(String prefix, Object[] array) {
		log.info(prefix + "{");
		for (int i = 0; i < array.length; i++) {
			dumpObject(prefix + "\t", array[i]);
			log.info(prefix + ",");
		}
		log.info(prefix + "}");
	}
	
	private final static void dumpMap(String prefix, Map map) {
		Iterator keys = map.keySet().iterator();
		while (keys.hasNext()) {
			Object key = keys.next();
			Object value = map.get(key);
			dumpObject(prefix, key);
			dumpObject(prefix + "\t", value);
		}
	}
	
	private final static void dumpObject(String prefix, Object obj) {
		if (obj instanceof Long) {
			Long l = (Long)obj;
			log.info(prefix + "0x" + Long.toHexString(l.longValue()));
		} else if (obj instanceof Map)
			dumpMap(prefix, (Map)obj);
		else if (obj.getClass().isArray())
			dumpArray(prefix, (Object[])obj);
		else
			log.info(prefix + obj);
	}

	private final Map getDeviceProperties() throws IOException {
		return nGetDeviceProperties(device_address);
	}
	private final static native Map nGetDeviceProperties(long device_address) throws IOException;

	public final synchronized void release() throws IOException {
		try {
			close();
		} finally {
			released = true;
			nReleaseDevice(device_address, device_interface_address);
		}
	}
	private final static native void nReleaseDevice(long device_address, long device_interface_address);

	public final synchronized void getElementValue(long element_cookie, OSXEvent event) throws IOException {
		checkReleased();
		nGetElementValue(device_interface_address, element_cookie, event);
	}
	private final static native void nGetElementValue(long device_interface_address, long element_cookie, OSXEvent event) throws IOException;

	public final synchronized OSXHIDQueue createQueue(int queue_depth) throws IOException {
		checkReleased();
		long queue_address = nCreateQueue(device_interface_address);
		return new OSXHIDQueue(queue_address, queue_depth);
	}
	private final static native long nCreateQueue(long device_interface_address) throws IOException;

	private final void open() throws IOException {
		nOpen(device_interface_address);
	}
	private final static native void nOpen(long device_interface_address) throws IOException;

	private final void close() throws IOException {
		nClose(device_interface_address);
	}
	private final static native void nClose(long device_interface_address) throws IOException;

	private final void checkReleased() throws IOException {
		if (released)
			throw new IOException();
	}
}
