/* Copyright 2009 Palm, Inc.  All rights reserved. */

var IncomingcallAssistant = Class.create({
	/* persistent stage that is created on first incoming call and sticks around.
	 * exitStatus is set with action to be taken by blur handler
	 * blur (caused by hitting corenav button) causes call to be rejected
	 */
    initialize: function(callId, number, contact, fromContacts, soundclass, soundfile, dialogSceneController) {
    	QDLogger.log( "IncomingcallAssistant#initialize", number  , fromContacts);
		this.appControl = Mojo.Controller.getAppController();
		this.appAssistant = this.appControl.assistant;
		this.contacts = this.appAssistant.contacts; 
		this.announcer = this.appAssistant.announcer;
		this.telListener = this.appAssistant.telephonyEventListener;

		// get initial puck status so we can detect changes
		this.puckConnected = this.telListener.puckConnected;
		this.callId = callId;
		this.soundclass = soundclass;
		this.soundfile = soundfile;
		// NOTE: this is a potentially dangerous assumption
		// as long as the subscription doesn't provide initial status, this will be fine.
		this.sliderOpen = false;
		
		this.updateFields(number, contact, fromContacts);
		this.now = new Date();
		this.windowName = "incoming";
		this.screenLocked = false;
		this.muted = false;
		this.exitStatus = "";
		this.dialogSceneController = dialogSceneController;
		if (this.dialogSceneController) {
			QDLogger.log( "IncomingcallAssistant#initialize", "i has a dialog");
			this.dialog = true;
		}
		
		// dialog is undefined if this assistant is running inside a dialog box
		// incoming call in a dialog is used in emergency mode and first use
		// while in a dialog, the incoming call popup is open, but is hidden
		// thus, we don't want to subscribe to anything
		if (!(this.dialog)) {
			this.wiredSubscription = TelephonyCommands.wiredButtonSubscribe(this.onWiredButtonEvent.bind(this));
			this.lockSubscription = TelephonyCommands.lockStatusSubscribe(this.onLockStatusEvent.bind(this));
			this.lockButtonSubscription = TelephonyCommands.lockScreenButtonSubscribe(this.onLockButtonEvent.bind(this));

    		this.puckSubscription = TelephonyCommands.puckStatusSubscribe(this.onPuckEvent.bind(this));	
			this.sliderSubscription = TelephonyCommands.sliderSubscribe(this.onSliderEvent.bind(this));
		}	
		
		this.unblockIgnore = this.unblockIgnore.bind(this); 
		
		this.blockIgnore = true;
		
		this.onContactTap = this.onContactTap.bindAsEventListener(this);
		
	},
    
    setup: function(){
		this.sceneCtrl = this.dialogSceneController || this.controller;
		this.unblockIgnore.delay(IncomingcallAssistant.kBlockIgnoreDelay);
		
		if (this.dialog) {
			//this.sceneCtrl.get('content').addClassName('palm-dialog-content');
		} else {
		    // handle rotation
			var devInfo = Mojo.Environment.DeviceInfo;
			if (devInfo && devInfo.screenHeight > 0) {
				Mojo.Event.listen(this.controller.window, 'resize', this.onResize.bindAsEventListener(this), false);
				this.screenHeight = devInfo.screenHeight;
			}  else {
				this.screenHeight = 0;
			}
		    
			this.powerButtonListen();
			this.controller.listen(this.controller.document, Mojo.Event.deactivate, this.onBlur.bind(this));
		}
		
		try {
			this.sceneCtrl.get('answer_button').addEventListener(Mojo.Event.tap, this.answerCall.bindAsEventListener(this));
			this.sceneCtrl.get('reject_button').addEventListener(Mojo.Event.tap, this.rejectCall.bindAsEventListener(this));
			
	        this.exposed = true;
			// turn display on and lock it there
			this.displayLock();
			this.renderScene();
			this.setupContactOpen();
		} catch (e) {
			Mojo.Log.error( "PhoneApp: IncomingcallAssistant#setup CAUGHT EXCEPTION %j" , e);
			this.powerButtonStopListening();
		}
		
    },
	
	unblockIgnore: function() {
		this.blockIgnore = false;
	},
	
	// blocks power button from turning screen off; starts listening for button events
	powerButtonListen: function() {
		this.powerButtonSubscription = TelephonyCommands.powerButtonBlockWithTimeout(
			this.windowName, this.onPowerButtonEvent.bind(this));
	},
	
	// reenables power button screen off; stops listening for button events
	powerButtonStopListening: function() {
		if (this.powerButtonSubscription) {
			this.powerButtonSubscription.cancel();
			delete this.powerButtonSubscription;
		}
	},
    
	// lock display status
	displayLock: function() {
		if (!this.dnastSub) {
			this.dnastSub = TelephonyCommands.displayChangeBlockWithTimeout(undefined);
		} 
	},
	
	// unlock display
	displayOff: function() {
		if(this.dnastSub) {
			this.dnastSub.cancel();
			delete this.dnastSub;	
		}
	},
    
	// re-raises incoming call alert
	showAlert: function(id, number, contact, fromContacts, soundclass, soundfile) {
		QDLogger.log( "IncomingcallAssistant#showAlert", number  , fromContacts);
		this.blockIgnore = true;
		this.unblockIgnore.delay(IncomingcallAssistant.kBlockIgnoreDelay);
		this.updateSound(soundclass, soundfile);
		
		try {
		
			this.exposed = true;
			
			// turn display on and lock it there
			this.callId = id;
			this.now = new Date();
			
			this.updateFields(number, contact, fromContacts);
			
			this.renderScene();
			this.exitStatus = "";
			this.muted = false;
			this.setupContactOpen();
			this.controller.stageController.activate();
			this.powerButtonListen();
			this.displayLock();
		} catch (e) {
			QDLogger.error( "IncomingcallAssistant#showAlert", "failure");
			this.powerButtonStopListening();
		}
	},
	
	// determines if we need to switch ringtones associated with alert and
	// does it if necessary
	updateSound: function(soundclass, soundfile) {
		if (soundclass !== this.soundclass || soundfile !== this.soundfile) {
			this.soundclass = soundclass;
			this.soundfile = soundfile;
			
			if (soundfile) {
				this.controller.stageController.window.PalmSystem.setAlertSound(soundclass, soundfile);
			} else {
				this.controller.stageController.window.PalmSystem.setAlertSound(soundclass);
			}
		}
	},
	
	// converts values into formatted versions for display
	updateFields: function(number, contact, fromContacts) {
		var name = ""
		if (contact.name) {
			name = contact.name;
		} else if (contact.cnapName && contact.cnapName !== "unknown") {
			name = contact.cnapName;
		} else {
			name = Messages.unknownCaller;
		}

		this.fromContacts = fromContacts;
		this.displayName = name;
	
		this.number = number;
		this.displayNumber = this.formatNumber(number);
	
		this.picture = (contact && contact.pictureLoc ? contact.pictureLoc : null);
		this.contact = contact;
		
		// ensure empty label is displayed as empty
		this.displayLabel = (this.contact.labelFormatted ? this.contact.labelFormatted  : "");
	
	},
	
	// updates contact info or number; called while alert is showing
	// intended to provide CNAP updates or late-arriving contact info
	updateDisplay: function(number, contact, fromContacts) {
		QDLogger.log( "IncomingcallAssistant#updateDisplay", name  , fromContacts);
		// lookup name overrides CNAP name, so:
		// if we already have a displayName from contacts, ignore this update 
		if (this.displayName && this.fromContacts)
			return;
		this.updateFields(number, contact, fromContacts);
		this.renderScene();	
	},
	
	updateContactLate: function(contact) {
		QDLogger.log( "IncomingcallAssistant#updateContactLate");
		if (contact && contact.name) {
			this.contact = contact;
			this.displayName = contact.name
			if (this.exposed) {
				this.renderScene()
			}
		}
		
	},
	
	onResize: function(event) {
		if (this.controller.window.innerWidth === this.screenHeight) {
			this.controller.get('content').addClassName('landscape');
			this.landscape = true;
		} else {
			this.controller.get('content').removeClassName('landscape');
			this.landscape = false;
		}
	},
	
	// shoves formatted data into divs
	renderScene: function(){
        QDLogger.log( "IncomingcallAssistant#renderScene");
		
		var templatefile = "";
		
		// pick template: dialog if picture
		if (this.dialog) {
			templatefile = "incomingcall/details-dialog";
		} else if (!(this.fromContacts) || this.smallWindow) {
			templatefile = "incomingcall/details-unknown";
			// if this gets updated later, make sure we don't use the big template later
			// after we've committed to a small window size
			this.smallWindow = true;
		} else if (!(this.picture)) {
			templatefile = "incomingcall/details-stamp";
		} else {
			// if there's a picture, and its appropriate edge is big enough, use full screen
			if (this.contact.pictureFormat == "landscape" && this.contact.pictureObj.height >= 140
				|| this.contact.pictureFormat !== "landscape" && this.contact.pictureObj.width >= 170)
				templatefile = "incomingcall/details-fullscreen";
			else
				templatefile = "incomingcall/details-stamp";
				
		}
		
		// if the phone number is the same as the contact name, blank out the number
		if (this.displayNumber === this.displayName) {
			this.displayNumber = "";
		}
		
		var content = Mojo.View.render({
            object: this,
            template: templatefile
		});
        
		this.sceneCtrl.get('incoming_message').update(content);
		
		// if portrait or landscape, apply class to crop
		if (!(this.smallWindow) && !(this.dialog) && this.picture) {
			var pictureElement = this.sceneCtrl.get('picture');
			if (pictureElement.className == "full-screen-picture") {
				var offset = 0;
				switch (this.contact.pictureFormat) {
					case "portrait":
						var offset = this.contact.pictureObj.height * -0.12
						break
					case "square":
						var offset = this.contact.pictureObj.height * -0.1
						break;
				}
				if (offset !== 0) {									
					pictureElement.style.marginTop = Math.round(offset) + "px"		
				}						

						
			} else {
				
				switch (this.contact.pictureFormat) {
					case "portrait":
						pictureElement.className = "incoming-picture-portrait";
						break;
					case "landscape":
						pictureElement.className = "incoming-picture-landscape";
						break;
					default:
						break;
				}
			} 
			pictureElement.src = this.picture;
		} else if (!(this.contacts.isNumberValid(this.number))
			&& !(this.contact.cnapName) && !(this.dialog)){
			this.sceneCtrl.get('unknownlabel').hide();
			this.sceneCtrl.get('contact_number').addClassName('blocked');
		}
		
		// TODO until refactoring: hide this if it's already locked 
		if (!(this.dialog)) {
			if (this.appAssistant.screenLocked) {
				this.buttonsSetVisible(false);
			} else {
				this.buttonsSetVisible(true);
			}
		}
		
		QDLogger.log( "IncomingcallAssistant#renderScene", "end");

    },
	
	buttonsSetVisible: function(visible) {
		if (visible) {
			this.sceneCtrl.get('answer_button').show();
			this.sceneCtrl.get('reject_button').show();
		} else {
			this.sceneCtrl.get('answer_button').hide();
			this.sceneCtrl.get('reject_button').hide();
		}
	},
	
	// called externally when incoming call is disconnected before answering
    onMissed: function(id){
		this.exitStatus = "missed"
		this.closeWindow();
    },
	
	// called AFTER call is answered (i.e. radio has or will answer call)
	// could be called externally if radio auto-answers call (i.e. loopback call)
	onAnswered: function() {
		this.exitStatus = "answered";
		this.appAssistant.focusStage();
		this.closeWindow();
	},
    
	// handles slides
	// NOTE: assumes that service does not initially provide status.
	// if it starts doing that, it could inadvertently answer the first call.
	onSliderEvent: function(response) {
		if (response && response.key && response.state) {
			QDLogger.log( "IncomingcallAssistant#onSliderEvent", response.key  , response.state);
		} else {
			return;
		}
		
		if (response.key === "slider") {
			var newSliderOpenState = (response.state === "up");
			if (this.exposed
				&& this.sliderOpen === false 
				&& newSliderOpenState === true) {
					this.answerCall();
			}
			this.sliderOpen = newSliderOpenState
		} 
	},
	
	// handles presses on wired button
	onWiredButtonEvent: function(response) {
		if (response) {
			QDLogger.log( "IncomingcallAssistant#onWiredButtonEvent", response.key  , response.state);
		}
		
		if (this.exposed) {
			if (response && response.key == "headset_button") {
				if (response.state == "single_click") {
					this.answerCall();
				} else if (response.state == "double_click") {
					this.ignoreCall();
				} else if (response.state == "hold") {
					this.rejectCall();
				}
			}
		}
	},
	
	// detect connection of inductive charger to determine if we should answer a call
	onPuckEvent: function(response) {
		if (response) {
			QDLogger.log( "IncomingcallAssistant#onPuckEvent",
				response.type, response.connected);	
		}
	
		// answer the call after a delay if we're exposed, set to do so, 
		// and we were previously on the puck
		if (response && response.type == "inductive") {
			if (this.appAssistant.puckMode
				&& this.exposed
				&& this.puckConnected === true
				&& response.connected === false) {
					this.answerIfStillOffPuck.bind(this).delay(0.750);
			}
			this.puckConnected = response.connected;
		}
		
	},
	
	// checks if phone is still off the puck, and answers call if it is
	// corrects for jitter in puck messages
	answerIfStillOffPuck: function() {
		if (this.puckConnected === false) {
			this.answerCall();
		} else {
			QDLogger.log( "IncomingcallAssistant#answerIfStillOffPuck", "back on puck");
		}
	},
	
	// detect lock status to hide/show buttons
	onLockStatusEvent: function(response) {
		QDLogger.log( "IncomingcallAssistant#onLockStatusEvent");
		if (response) {
			if (response.locked === true) {
				this.buttonsSetVisible(false);
			} else if (response.locked === false) {
				this.buttonsSetVisible(true);
			} else {
				QDLogger.log( "IncomingcallAssistant#onLockStatusEvent", "unknown");
			}
		}
	},
	
	onLockButtonEvent: function(response) {
		if (this.exposed === true && response && response.triggered === true) {
			this.answerCall();
		}
	},
	
    // tell radio to answer call
    answerCall: function(){
        QDLogger.log( "IncomingcallAssistant#answerCall", this.callId);
        this.mute();
		TelephonyCommands.answer(this.callId);
        this.onAnswered();
    },
    
	// hide alert and instruct blur handler to disconnect call & show ignored UI
	rejectCall: function(event){
        if (this.blockIgnore) {
			QDLogger.error( "IncomingcallAssistant#rejectCall tapped too soon");
			this.unblockIgnore();
			return;	
		}
		QDLogger.log( "IncomingcallAssistant#rejectCall", this.callId);
        this.mute();
		this.exitStatus = "rejected";
		
      	this.closeWindow();
    },
	
	// hide alert and instruct blur handler to show ignore UI
	ignoreCall: function(event) {
		QDLogger.log( "IncomingcallAssistant#ignoreCall", this.callId);
        
		this.exitStatus = "ignored";
		
      	this.closeWindow();
	},
	
	mute: function() {
		if (!this.muted) {
			this.muted = true;
		}
		TelephonyCommands.muteRingtone();
		if ( !this.dialog 
			&& this.controller 
			&& this.controller.window
			&& this.controller.window.PalmSystem 
			&& Object.isFunction(this.controller.window.PalmSystem.cancelVibrations) ) {
			this.controller.window.PalmSystem.cancelVibrations();
		}
	},	
	// if a valid power button event, registers it as a hardkey press
	onPowerButtonEvent: function(event) {
		QDLogger.log( "IncomingcallAssistant#onPowerButtonEvent");
		if (event && event.powerKey == "released") {
			this.handleHardKeyPress();
		} 
	}, 
	
	// both power button and corenav button are interchangeable.
	// first tap mutes ringtone.
	// second closes window and rejects call
	handleHardKeyPress: function() {
		QDLogger.log( "IncomingcallAssistant#handleHardKeyPress");
		if (this.muted) {
			QDLogger.log( "IncomingcallAssistant#handleHardKeyPress", "reject");
			this.rejectCall();
		} else {
			QDLogger.log( "IncomingcallAssistant#handleHardKeyPress", "mute");
			this.mute();
		}
	},
    
    closeWindow: function(){
		if (this.dialog) {
			// use announcer's dialog close			
			this.onBlur();
			this.announcer.closeIncomingCallDialog(true /* also deactivate stage */);
		} else{
			// incoming call stage is persistent, so leave window around but hide it
			this.controller.stageController.deactivate();	
		}
    },
	
	closeWindowNoAction: function(){
		this.exitStatus = "close";
		this.closeWindow();
    },
	
	// process exitStatus by disconnecting rejected calls; showing ignored UI
	// for ignored and rejected.  could be called due to action in assistant
	// or by corenav press
	onBlur: function() {
		QDLogger.log( "IncomingcallAssistant#onBlur", this.exitStatus);
		// TODO workaround: vibrator needs this to calm itself down.
		if (this.exposed) {
			this.exposed = false;
			this.mute();
			
			// won't be set if we blurred due to corenav button press
			// so use this to raise incoming call dashboard
			if (!this.exitStatus) {
				this.exitStatus = "hidden";
			}
		}
		
		switch (this.exitStatus) {
			case "hidden":
				var title = this.displayName;
				if (this.displayLabel) {
					var message = Messages.contactWithLabel;
					title = new Template(message).evaluate({
						"contact": this.displayName,
						"label": this.displayLabel
					});
				}
				this.announcer.announceIncomingDashboard(title, this.displayNumber, this.answerCall.bind(this));
				break;
			case "rejected":
				TelephonyCommands.disconnect(this.callId);
				// NO BREAK: falls through
			case "ignored":
				this.telListener.flagCallRejected(this.callId);
				this.announcer.announceIgnored(this.displayName, this.number, this.now, this.picture, this.contact, this.fromContacts);
				break; // STOP falling through
			case "missed":
			case "answered":
			case "close":
	        default:
				break;
		}
		
		this.exitStatus = "";
		
		// stop blocking power key
		this.powerButtonStopListening();
		
		// unblock display state changes
		this.displayOff();
		
		// unbind contact listener:
		this.clearContactOpen();
		
	},
	
	// cancel subscriptions when we're being replaced
	cleanup: function() {
		QDLogger.log( "IncomingcallAssistant#cleanup");
		if (!(this.dialog)) {
			this.wiredSubscription.cancel();
			this.lockSubscription.cancel();
			this.puckSubscription.cancel();
			this.sliderSubscription.cancel();
			
			delete this.wiredSubscription;
			delete this.lockSubscription;
			delete this.puckSubscription;
			delete this.sliderSubscription;
		}
		
		// just in case we never opened, turn off power and display blocks
		this.powerButtonStopListening();
		this.displayOff();
	},
	
	// format number or provide default unknown number string
	formatNumber: function(number){
		if (number) {
			return FormatPhoneNumber(number);
		} else {
			return Messages.unknownNumber;
		}
	},
	
	// launches the contact app with contact card or pseudocard depending
	// on if the passed line has a contact book entry or not
	onContactTap: function(event){
		event.stop();
		
		if (this.landscape === true) {
			return;
		}
		
		if (this.contact && this.contact.id) {
			TelephonyCommands.contactsLaunchWithId(this.contact.id);
		} else if (this.contacts.isNumberValid(this.number)) {
			TelephonyCommands.contactsLaunchWithPseudocard(this.number);
		}
		
		this.controller.stageController.deactivate();
	},
	
	// binds contact info text to contact text.  doesn't if it's a dialog.
	setupContactOpen: function(){
		if (!this.dialog) {
			this.sceneCtrl.get('contact_info').addEventListener(Mojo.Event.tap, this.onContactTap);
			var framedPicture = this.sceneCtrl.get('contact_picture_frame');
			if (framedPicture) {
				framedPicture.addEventListener(Mojo.Event.tap, this.onContactTap);
			}
		}
	},
	
	// unbinds contact info text to contact text
	clearContactOpen: function() {
		if (!this.dialog) {
			this.sceneCtrl.get('contact_info').removeEventListener(Mojo.Event.tap, this.onContactTap);
			var framedPicture = this.sceneCtrl.get('contact_picture_frame');
			if (framedPicture) {
				framedPicture.removeEventListener(Mojo.Event.tap, this.onContactTap);
			}
		}
	}
	
});

IncomingcallAssistant.kBlockIgnoreDelay = 0.5;