/* Controller class for the Device Discovery and Outbound Pairing. 
 * Copyright 2009 Palm, Inc.  All rights reserved.
*/
var InquiryAssistant = Class.create({
    toggleModel: {
        value: true,
		disabled: false,
    },

	// Constructor
	initialize: function(params){
		// Get the Bluetooth state from the app assistant
		this.bt = Mojo.Controller.getAppController().assistant.bt;
		
		// Register this assistant as being interested in BT events
		this.btEventHandler = this.handleBtEvent.bind(this);
		this.bt.registerAssistant(this.btEventHandler);
		
		// Initialize the variables
		this.params = params || {};
		this.inquiryList = [];
		this.inquiryHasbeenCancelled = false;
		this.inquiryInProgress = false;
		this.updateNameCache = true;
		this.pendingInquiry = null;
		this.newlyDiscoveredNamelessDevice = null;
		this.inquiryBlacklist = [];
		this.disablePairing = false;
		this.pairingInProgress = false;
		this.stopInquiryTimer = null;
		this.pageTimeout = 0;
		this.errorMsgTimeout = null;
	},
	
	// Set up the views to display initially. 
	setup: function() {
		Mojo.Log.info("++++++ InquiryAssistant: setup ++++++");
		this.inqMsgArea = this.controller.get('inqmsgarea');
		this.inqMsgSpinner = this.controller.get('inqmsgspinner');
		this.inqMsgDiv = this.controller.get('inq_msg_spinner');
		this.noDevicesFoundMsg = this.controller.get('noDeviceFoundMsg');
		this.btInquiryListWidget = this.controller.get('btinquiry_list');
		this.btOnOffWidget = this.controller.get('btradioonoff');
		
		this.inquiryListModel = {
			listTitle: $L('Found audio devices'),
			items:this.inquiryList,
		};
		
		// If only looking for dcar kits then change the list title and icon
		if (this.params.carkitsOnly) {
			this.inquiryListModel.listTitle = $L('Found car kits');
			this.controller.get('headerIcon').addClassName('contacts');
		}
		    
    	// Set up the attributes & model for this widget:
    	this.controller.setupWidget('btinquiry_list', 
                  {itemTemplate:'inquiry/listitem', listTemplate:'inquiry/listcontainer'},
                  this.inquiryListModel);
		Mojo.Event.listen(this.btInquiryListWidget, Mojo.Event.listTap, this.listClickHandler.bindAsEventListener(this), false);
				  
		// Hide the inquiry results for now
		this.btInquiryListWidget.hide();
		this.controller.hideWidgetContainer(this.btInquiryListWidget);
		
		// Setup the radio on/off widget
		this.toggleModel.value = true;
		this.controller.setupWidget('btradioonoff', {}, this.toggleModel);
		Mojo.Event.listen(this.btOnOffWidget, Mojo.Event.propertyChange, this.turnRadioOff.bindAsEventListener(this), false);
		
		// Setup the menu
		var appMenuModel = {
	        visible: true,
            items: [{label:$L('Help'), command:Mojo.Menu.helpCmd}]
        };
		this.controller.setupWidget(Mojo.Menu.appMenu, {omitDefaultItems:true}, appMenuModel);
		
		// Change the title if necessary
		if (this.params.title)
			this.controller.get('header').update(this.params.title);
			
		// Hide the on/off toggle if necessary
		if (this.params.hideOnOffToggle)
			this.controller.get('radioOnOffToggle').hide();
		
		// Listen for app going to card view
		this.btInqHasFocusHandler = this.btInqHasFocus.bind(this);
		this.btInqHasLostFocusHandler = this.btInqHasLostFocus.bind(this);
		Mojo.Event.listen(this.controller.document, Mojo.Event.activate, this.btInqHasFocusHandler, false);
		Mojo.Event.listen(this.controller.document, Mojo.Event.deactivate, this.btInqHasLostFocusHandler, false);
  	},
	
	// Handle the menu event
	handleCommand: function(event) {
		if (event.type == Mojo.Event.command) {
			if (event.command == Mojo.Menu.helpCmd) {
				 this.bt.btMojoService("palm://com.palm.applicationManager/open",
                                  {'id': 'com.palm.app.help','params': {'target': 'http://help.palm.com/bluetooth/index.html'}});       
			}
		}
	},	

  	// Set the initial behavior of the screen.
  	activate: function(){
		Mojo.Log.info("++++++ activate ++++++");
		this.showInquiryAnimation();
		
		// Show some UI for debugging
		if (this.bt.uiDebugMode) {
			this.inquiryList = [
				{name:"Trusted Device", NAMESTATE:"NameOkay", TRUSTED:'trusted', address:"12:34:56:78:90:12:34", ICON:'headset_a2dp', spinning:false},
				{name:"Device with a very very very long name", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:35", ICON:'a2dp', spinning:false},
				{name:"Spinning", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:36", ICON:'headset_a2dp', spinning:true},
				{name:"Device 1", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:37", ICON:'carkit', spinning:false},
				{name:"Device 2", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:38", ICON:'headset', spinning:false},
				{name:"Device 3", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:39", ICON:'a2dp', spinning:false},
				{name:"Device 4", NAMESTATE:"NameOkay", TRUSTED:'', address:"12:34:56:78:90:12:40", ICON:'headset_a2dp', spinning:true},
			];
			this.btInquiryListWidget.show();
			this.controller.showWidgetContainer('btinquiry_list');
			this.btInquiryListWidget.mojo.noticeAddedItems(0, this.inquiryList);
		}
		else
			this.startInquiry();

	    // Allow pairing requests 
		this.bt.allowPairing(true);
		// This is the active scene for passkey dialogs
		this.bt.setActiveScene(this);
  	},

		
	// Hide the discovery screen
  	cleanup: function() {
		Mojo.Log.info("++++++ cleanup ++++++");

		// No longer interested in inquiry responses		
		if (this.inqServicecall)
			this.inqServicecall.cancel();
		// Cancel inquiry if there is one in progress
		this.cancelInquiry();
		
		// Cancel outbound pairing attempt
		this.bt.cancelOutboundPairing();
		
		this.inquiryList.clear();
		this.btInquiryListWidget.mojo.noticeRemovedItems(0, this.btInquiryListWidget.mojo.getLength());
		
		// Restore the page timeout
		if (!this.relaunchingInquiry) {
			this.lowerPageTimeout(false);
		}

		// If there is a timer to erase the "pairing failed" message then clear it now
		if (this.errorMsgTimeout) {
			clearTimeout(this.errorMsgTimeout);
			this.errorMsgTimeout = null;
		}
	    // Stop the "abort inquiry timer" if it is running 
	    if (this.stopInquiryTimer) { 
	        clearTimeout(this.stopInquiryTimer); 
	        this.stopInquiryTimer = null; 
	    }
		
		// Unregister for BT events
		this.bt.unregisterAssistant(this.btEventHandler);

		// Stop listening for window focus events
		Mojo.Event.stopListening(this.controller.document, Mojo.Event.activate, this.btInqHasFocusHandler, false);
		Mojo.Event.stopListening(this.controller.document, Mojo.Event.deactivate, this.btInqHasLostFocusHandler, false);
  	},
	
	// Inquiry is running full-screen 
	btInqHasFocus: function(event) {
		Mojo.Log.info("++++++ btInqHasFocus ++++++1");
		// Make sure the event is for this scene
		if (event.target != this.controller.document)
			return
		Mojo.Log.info("++++++ btInqHasFocus ++++++");
		this.bt.setFullScreen(true);

	    // Stop the "abort inquiry timer" if it is running 
	    if (this.stopInquiryTimer) { 
	        clearTimeout(this.stopInquiryTimer); 
	        this.stopInquiryTimer = null; 
	    }
	},

	// Inquiry is not running full-screen anymore 
	btInqHasLostFocus: function(event) { 
		Mojo.Log.info("++++++ btInqHasLostFocus ++++++1");
		// Make sure the event is for this scene
		if (event.target != this.controller.document)
			return
		Mojo.Log.info("++++++ btInqHasLostFocus ++++++");
		this.bt.setFullScreen(false);
	
	    // Start a timer that will cancel inquiry if the user doesn't return to the app within 1 minute 
        this.stopInquiryTimer = setTimeout(this.bt.popInquiryScene.bind(this.bt), 60000);
	},
	
	// Handle Bluetooth events
	handleBtEvent: function(event, data) {
		Mojo.Log.info("++++++ handleBtEvent: " + event);
		switch (event) {
			case Bluetooth.serviceDown:
				// This scene should not be running if the service went down
				this.controller.stageController.popScene(); 
				break;
				
			case Bluetooth.popInquiryScene:
				// The  state machine wants this scene to be popped
				// Should it be popped after a short delay?
				if (data && data.delay) 
					setTimeout(this.handleBtEvent.bind(this, Bluetooth.popInquiryScene), data.delay);
				else {
					if (data && data.relaunchingInquiry)
						this.relaunchingInquiry = true;
					this.controller.stageController.popScene();
				}
				break; 
				
			case Bluetooth.restartInquiry:
				// Inquiry should be restarted after unsuccessful pairing
				this.showPairingFailedMessage(data);
				this.pairingInProgress = false;
				this.startInquiry();
				break;
				
			case Bluetooth.disablePairing:
				// Pairing (or taps on devices) must be disabled because this scene is about to exit
				this.disablePairing = true;
				break;
				
			case Bluetooth.inqDeviceFound:
				// Inquiry has found a device
				this.addDiscoveryDevice(data);
				break;
				
			case Bluetooth.inqDeviceName:
				// Ignore this if there is an Inquiry in progress
				if (this.inquiryInProgress)
					break;
				// Was the name retrieval successful?
				var device = data;
				if (device.NAMESTATE === "NameFailed") {
					// Add this device to the blacklist so we don't try to add it again
					// (or at least until a complete inquiry cycle with no new devices found)
					this.inquiryBlacklist.push(device.address);
				}
				else {
					// Show the device list now
					this.btInquiryListWidget.show();
					// Is the device alreay in the list?
					if (device.OFFSET != undefined)
						this.btInquiryListWidget.mojo.noticeUpdatedItems(device.OFFSET, [device]);
					else {
						// Insert CoD icon
						device.ICON = getIconFile(device.cod, device.name);

						// Add the device to the list
						this.inquiryList.push(device);
						this.btInquiryListWidget.mojo.noticeAddedItems(this.btInquiryListWidget.mojo.getLength(), [device]);
					}
					// Hide the No Device Found Message
					this.noDevicesFoundMsg.hide();
				}

				// Retrieve the next name, or restart inquiry
				this.nameRetrieval();
				break;
				
			case Bluetooth.inqComplete:
				// Inquiry has completed
				this.inquiryInProgress = false;
				// Don't do name retrieval if pairing is in progress
				if (!this.pairingInProgress)				
					this.nameRetrieval();
				break;
				
			case Bluetooth.pairingStarted:
				// Start a spinner next to the device
				var device = this.getDeviceFromDiscoveredList(data);
				if (device) {
					device.spinning = true;
					this.updateDeviceDisplay(data);
				}
				break;
				
			case Bluetooth.pairingComplete:
				// Pairing is no longer happening to the device
				var oldDevice = this.getDeviceFromDiscoveredList(data);
				if (oldDevice) {
					oldDevice.spinning = false;
					this.updateDeviceDisplay(data);
				}
				break;
				
		}		
	},
	
	// User turned radio off
	turnRadioOff: function(event) {
		Mojo.Log.info("++++++ turnRadioOff ++++++");
		// Turn the radio off
		this.bt.toggleRadioState();
		
		// Pop this scene
		this.controller.stageController.popScene();
	},
	
	// List Tap Handler - Start the Pairing process
	listClickHandler: function(event) {
		Mojo.Log.info("++++++ listClickHandler ++++++");
		// Ignore the tap if pairing has been disabled by a successful pairing (carkit)
		if (this.disablePairing)
			return;
		if (event.item) {
			this.startPair(event.item.address);
		}
	},	
	
	// Update the UI to show the progress of the pairing process. 
	showPairingAnimation: function() {
		Mojo.Log.info("++++++ showPairingAnimation ++++++");
		// If there is a timer to erase the "pairing failed" message then clear it now
		if (this.errorMsgTimeout) {
			clearTimeout(this.errorMsgTimeout);
			this.errorMsgTimeout = null;
		}

		// Update the progress bar
		this.showStatus($L("Pairing..."), true, false);
	},
	
	// Update the UI to remove the pairing progress. 
  	showInquiryAnimation: function() {
		Mojo.Log.info("++++++ showInquiryAnimation ++++++");
		// Update the progress bar
		if (this.params.carkitsOnly)
			this.showStatus($L("Searching for car kits..."), true, false);
		else
			this.showStatus($L("Searching for audio devices..."), true, false);
				
		// Hide the spinner next to the device names
		for (var i = 0; i < this.inquiryList.length; i++) {
			if (this.inquiryList[i].spinning) {
				this.inquiryList[i].spinning = false;
				this.btInquiryListWidget.mojo.noticeUpdatedItems(i, [this.inquiryList[i]]);
			}
		}
		
		// Clear the error message timer reference
		if (this.errorMsgTimeout) {
			clearTimeout(this.errorMsgTimeout);
			this.errorMsgTimeout = null;
		}
	},
	
	// Display the Pairing Failed message on the status area.
	showPairingFailedMessage: function(showError) {
		Mojo.Log.info("++++++ showPairingFailedMessage ++++++ error=" + showError);
		// Update the progress bar
		if (showError) {
			this.showStatus($L("Pairing failed."), false, true);
			this.errorMsgTimeout = setTimeout(this.showInquiryAnimation.bind(this), 5000);
		}
		else {
			this.showInquiryAnimation();
		}			
		// Hide the spinner next to the device names
		for (var i = 0; i < this.inquiryList.length; i++) {
			if (this.inquiryList[i].spinning) {
				this.inquiryList[i].spinning = false;
				this.btInquiryListWidget.mojo.noticeUpdatedItems(i, [this.inquiryList[i]]);
			}
		}
	},
	
	// Show the message and/or spinner in the status area
	showStatus: function(msg, showSpinner, isError) {
		// Display the message
		this.inqMsgArea.update(msg);
		
		// Add a spinner icon on the progress bar too, if needed
		if (showSpinner) {
			this.inqMsgSpinner.mojo.start();
			this.inqMsgDiv.addClassName('active');
		}
		else {
			this.inqMsgSpinner.mojo.stop();
			this.inqMsgDiv.removeClassName('active');
		}
			
		// Show the text in the correct color
		if (isError)
			this.inqMsgArea.addClassName("error");
		else
			this.inqMsgArea.removeClassName("error");
		
	},
	
	// Start device discovery (inquiry) and name retrieval
	startInquiry: function() {
		Mojo.Log.info("++++++ startInquiry ++++++");
		// Clear the pending inquiry, if any
		this.pendingInquiry = null;
		
		// Lower the page timeout to make name retrieval fast
		this.lowerPageTimeout(true);

		this.inquiryHasbeenCancelled = false;
		this.inquiryInProgress = true;
		this.updateNameCache = true;
		
		// Start the inquiry
		this.inqServicecall = this.bt.findDevices();
	},
	
	// Return the device from the list of discovered devices, if known
	getDeviceFromDiscoveredList: function(addr) {
		if (!addr)
			return undefined;
		for (var i = 0; i< this.inquiryList.length; i++) {
			if (this.inquiryList[i].address == addr) {
				return this.inquiryList[i];
			}									
		}
		return undefined;	
	},
	
	
	// Update the device information being displayed
	updateDeviceDisplay: function(addr) {
		for (var i = 0; i< this.inquiryList.length; i++) {
			if (this.inquiryList[i].address == addr) {
				this.btInquiryListWidget.mojo.noticeUpdatedItems(i, [this.inquiryList[i]]);
				break;
			}									
		}
	},


	// Add the discovered device to the list, if it isn't there already
	addDiscoveryDevice: function(inquiryResult) {
		Mojo.Log.info("++++++ addDiscoveryDevice: "+ Object.toJSON(inquiryResult));
										
		// Check for Duplicate
		if (this.getDeviceFromDiscoveredList(inquiryResult.address))
			return;
		
		// Don't add if the inquiry has been aborted
		if (this.inquiryHasbeenCancelled)
			return;
			
		// Don't add this device if a previous attempt to get the name failed
		if (this.inquiryBlacklist.indexOf(inquiryResult.address) >= 0)
			return;
			
		// Don't add this device if it isn't a car kit, and only car kits are accepted
		if (this.params.carkitsOnly && !isOPPSupported(inquiryResult.cod))
			return;

		if (inquiryResult.NAMESTATE == "GetName") {
			Mojo.Log.info("++++++ Getting the name +++++++");
			// Cancel Inquiry and get the name (don't update names in the cache though)
			this.updateNameCache = false;
			this.cancelInquiry();
			
			// Save details of this device
			this.newlyDiscoveredNamelessDevice = inquiryResult;
		}
		else {
			Mojo.Log.info("++++++ Showing the device +++++++");
			// Insert CoD icon
			inquiryResult.ICON = getIconFile(inquiryResult.cod, inquiryResult.name);
		
			// Add the row to the list of discovered devices
			this.inquiryList.push(inquiryResult);
			this.btInquiryListWidget.mojo.noticeAddedItems(this.btInquiryListWidget.mojo.getLength(), [inquiryResult]);

			// Show the list of devices device
			this.btInquiryListWidget.show();
			this.controller.showWidgetContainer('btinquiry_list');

			// Hide the No Device Found Message
			this.noDevicesFoundMsg.hide();
		}
	},
	
	// Remove discovered devices whose names are unknown
	removeUnknownDiscoveredDevices: function() {
		this.inquiryBlacklist.clear();
	},
	
	// Abort the finding of devices 
	cancelInquiry: function() {
		// If there is a pending inquiry then cancel it
		if (this.pendingInquiry) {
			clearTimeout(this.pendingInquiry);
			this.pendingInquiry = null;
		}
		
		// An inquiry must be in progress, and must not have been cancelled
		if (!this.inquiryInProgress || this.inquiryHasbeenCancelled)
			return;
		
		this.inquiryHasbeenCancelled = true;
			
		// Ask the BT Engine to abort the inquiry
		this.bt.btMojoService("palm://com.palm.bluetooth/gap/finddevicescancel", null, null);		
	},
	
	// Called while finding devices.  Get the next name in the list
	nameRetrieval: function() {
		var i, device;
		// Get names of newly found devices
		if (this.newlyDiscoveredNamelessDevice) {
			this.getRemoteDeviceName(this.newlyDiscoveredNamelessDevice);
			this.newlyDiscoveredNamelessDevice = null;
			return;
		}
		
		// If a 10-second inquiry happened without finding new devices then update the
		// names of found devices, except those that are trusted and have been renamed
		if (this.updateNameCache) {
			for (var i=0; i<this.inquiryList.length; i++) {
				device = this.inquiryList[i];
				if (device.NAMESTATE == "UpdateName") {
					this.getRemoteDeviceName(device, i);
					return;
				}
			}
				
			// Remove devices from the list where name retrieval failed.  This gives them the
			// opportunity to be discovered again, and perhaps the name retrieval will work next time
			this.removeUnknownDiscoveredDevices();
			
			// If no device were found after the first full scan then display
			// a helpful message to the user
			if (this.inquiryList.length == 0) {
				this.noDevicesFoundMsg.show();
				this.showStatus("", false, false);
				this.errorMsgTimeout = setTimeout(this.showInquiryAnimation.bind(this), 2000);			
			}		
		}
		
		// Now that all the names have been updated, start doing inquiries again after a short
		// delay (to allow for WiFi interoperability) - unless a pairing attempt is in progress
		if (!this.pairingInProgress)
			this.pendingInquiry = setTimeout(this.startInquiry.bind(this), 3000);
	},
	
	// Get the remote device name
	getRemoteDeviceName: function(device, offset) {
		// Make the offset part of the device object so that it will be known for the callback
		device.OFFSET = offset;
		
		// Get the name of the device
		this.bt.getInquiryDeviceName(device.address, device);
	},
	
	// Start the Pairing sequence with the selected remote device.  This will result in a connection
	// to the remote device, and prompt the remote device for a passkey.  
  	startPair: function(addr) {
		// Make sure the selected device is valid
        if (!addr)
	        return;

		// Get the device the user tapped on		
		if (this.bt.uiDebugMode) {
			var device = this.getDeviceFromDiscoveredList(addr);
			if (device.spinning)
				device.spinning = false;
			else
				device.spinning = true;
			this.updateDeviceDisplay(addr);
			return;
		}
		
		this.pairingInProgress = true;
		
		// Restore the page timeout (high timeout during pairing)
		this.lowerPageTimeout(false);
		
		// Don't update names of devices found during inquiry
		this.updateNameCache = false;
		
		// Cancel the Inquiry
		this.cancelInquiry();

		// Show that this device is being paired with
        this.showPairingAnimation();
		
        // Send the pair request to Linux
		this.bt.startOutboundPairing(addr);      
    },
	
	// Raise or lower the page timeout.  This determines the timeout for a connection attempt
	lowerPageTimeout: function(lower) {
		// Don't raise the page timeout if it is already high 
		if (this.pageTimeout == 0 && !lower)
			return;
			
		// Don't lower the page timeout if it is already low 
		if (this.pageTimeout != 0 && lower)
			return;
			
		// Invert the page timeout
		this.pageTimeout = 3 - this.pageTimeout; 

		// Lower the page timeout
		this.bt.btMojoService("palm://com.palm.bluetooth/gap/setpagetimeout",
				{seconds:this.pageTimeout}, null);		
	}
});
