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

var Contact = Class.create({
    initialize: function(){
        QDLogger.log( "Contact::initialize");
		this.callerDetailsTable = new CallerDetailsTable();
		this.carrierBook = {};
		this.carrierBookLoad();
		this.emergencyNumbers = $H({});
		this.sentinel = "17102622453032400";
		this.lastDialedNumber = null;
    },
	
	// stores carrierbook in Mojo depot
	carrierBookSave: function(entries) {
		
		var addEntries = function(entries) {
			QDLogger.log( "Contact::carrierBookSave","adding entries");
			this.db.simpleAdd("entries", this.carrierBook,//.toObject(), 
				function() {
					QDLogger.log( "Contact::carrierBookSave","put successful");
				},
				function() {
					QDLogger.log( "Contact::carrierBookSave","put failed");
				});
		}.bind(this);
		
		var createFail = function() {
			QDLogger.log( "Contact::carrierBookSave","failed to create db");
		}.bind(this);
		

		QDLogger.log( "Contact::carrierBookSave");
		for (var i = 0; i < entries.length; i++) {
			QDLogger.log( "Contact::carrierBookSave", "adding:: " , entries[i].number , " - " , entries[i].label);
			//this.carrierBook.set(entries[i].number, entries[i].label);
			this.carrierBook[entries[i].number] = entries[i].label;
		}
		
		this.carrierBook[this.sentinel] = "Good frequencies"
		
		QDLogger.log( "Contact::carrierBookSave","creating");
		this.db = new Mojo.Depot({
			name: "carrierbook",
			version: 1,
			replace: true
		}, addEntries, createFail);
	},
	
	// loads carrierbook from Mojo depot
	carrierBookLoad: function() {
		var loadEntries = function(db, entries) {
			QDLogger.log( "Contact::carrierBookLoad","connect successful");
			this.db.simpleGet("entries", 
				function(result) {
					if (result !== undefined) {
						QDLogger.log( "Contact::carrierBookLoad", "get successful");
						if (result == null) {
							QDLogger.log( "Contact::carrierBookLoad","null book");
						} else if (!(result[this.sentinel])) {
							QDLogger.log( "Contact::carrierBookLoad","empty book");
						} else {
							// copy entries into book
							// we're doing this because the load happens after we've retrieved voicemail number et al
							for (number in result) {
								if (result.hasOwnProperty(number)) {
									this.carrierBook[number] = result[number];
								}
							}
						}
					} else {
						QDLogger.log( "Contact::carrierBookLoad","undefined book");
					}
				}.bind(this),
				function() {
					QDLogger.log( "Contact::carrierBookLoad","get failed");
				});
		}.bind(this);
		
		var connectFail = function() {
			QDLogger.log( "Contact::carrierBookLoad","connect failed");
		}.bind(this);
		
		QDLogger.log( "Contact::carrierBookLoad");
		
		this.db = new Mojo.Depot({
			name: "carrierbook",
			version: 1,
			replace: false
		}, loadEntries, connectFail);
		
		QDLogger.log( "Contact::carrierBookLoad","end");
	},
  
  	// look in carrier-provided address book for number; add to contact passed in
	carrierBookLookup: function(number, contact) {
		QDLogger.log( "Contact::carrierBookLookup");
		var name = this.carrierBook[number];
		if (name !== undefined) {
			QDLogger.log( "Contact::carrierBookLookup", "carrierbook matched");
			contact.name = $L(name);
		}
	},
	
	// marks entry as stale
	removeCaller: function(number) {
		var entry = this.callerDetailsTable.get(number);
		
		if (entry && (entry.id || entry.manuallySet)) {
			entry.lookupComplete = false;
			entry.manuallySet = false;
		} else {
			this.callerDetailsTable.unset(number);
		}
		
	},
	
	isNumberValid: function(number) {
		return !(number === undefined
			|| number == null
			|| number == "" 
			|| number == "unknown" 
			|| number == "unknown caller"
			|| number == "blocked caller") 
	},
	
	// returns existing contact or creates a new one if it doesn't exist yet
	// if a prior contact entry is passed in, adds that
	// TODO FIXME: this really needs to be refactored.  each call should have an
	// individual Contact instance associated with it.  it should have a reverselookup method, 
	// and should exist for the entire lifetime of a call.
	getContact: function(number, details, forceRefresh) {
		var entry = this.callerDetailsTable.get(number);
	
		if (!(entry)) {
			entry = {};
			this.callerDetailsTable.set(number, entry)
		}
		
			if (details) {
				if (details.name) {
					entry.name = details.name;
					entry.lookupComplete = true;
					entry.manuallySet = true;
				} else if (details.id) {
					entry.initialId = details.id;
					if (details.label && details.label.length > 0) {
						entry.label = details.label.charAt(0).toUpperCase() + details.label.substring(1);
					} 
					entry.manuallySet = true;
				entry.lookupComplete = false;
				}
			}
			
		if (forceRefresh) {
			entry.lookupComplete = false;
		}
		
		return entry;
	},
	
	// use contacts service and carrier book to perform reverse lookup on number.  stores results in passed contact object.
	// runs callback when done.
	// if the number isn't valid, marks contact lookup complete 
	// if there's already one happening, or one already completed, just fires callback
	// if there's a lateCallback provided, calls that if the result comes much later
	reverseLookup: function(number, contact, callback, lateCallback){
    	QDLogger.log( "Contacts ReverseLookup",  "phone=" , number);
		
		// bail if the number is invalid
		if (!(this.isNumberValid(number))) {
				this.finishLookup(contact, callback);
				return;
		}
		
		if (contact.lookupComplete) {
	        QDLogger.log( "Contact#reverseLookup","contacts match already exists for " , number);	
			callback(contact);	
			return;
		}
		
		if (contact.lookupPending) {
			QDLogger.log( "Contact#reverseLookup","waiting for lookup for " , number);	
			callback(contact);	
			return;
		}
		
		// if it's an emergency number, go right to the carrierbook
		if (PlatformType.isEmergencyNumber(number)) {
			this.carrierBookLookup(number,contact);
			this.finishLookup(contact, callback);	
		}
		
		// use contacts service to perform lookup.
		// if contact already has an id in it, use person lookup
		// instead of doing reverse lookup on number
		QDLogger.log( "Contact#reverseLookup", "do lookup");
		contact.lookupPending = true;
		var method, params;
		if (contact.initialId) {
			method = 'basicDetails'
			params =  {'id' : contact.initialId}
			delete contact.initialId;
		} else {
			method = 'reverseLookup'
			params = {
                'value': number,
                'type': "phone"
            }
		}
		this.lastRequest = new Mojo.Service.Request('palm://com.palm.contacts', {
			'method': method,
            parameters: params,
            onSuccess: this.onLookup.bind(this, number, contact, callback, lateCallback),
			onFailure: function() {
				QDLogger.log( "Contact#reverseLookup", "lookup failed. off to carrierbook");
				// cancel previous lookup, so this doesn't fire on a service crash
				if (this.lastRequest) {
					this.lastRequest.cancel();
					this.lastRequest = undefined;
				}
				
				this.carrierBookLookup(number,contact);
				this.finishLookup(contact, callback);
			}.bind(this)
        });
		
		// timeout if lookup hasn't completed in 4 seconds
		this.lookupTimeout = setTimeout(this.onLookupTimeout.bind(this, contact, callback, lateCallback), 4000);
    },
	
	// when contact lookup returns, check for valid result
	// if valid result, grab name, ringtone; if there's a picture, start loading and set callback to measure it
	// if no valid result, look in carrier book
	// fire callback when done
    onLookup: function(number, contact, callback, lateCallback, result){
        Mojo.Log.info( "PhoneApp: Contact::onLookup CALLER ID LOOKUP %s RETURNED %j" , number , result);
		var statusChange = "";
		
		// cancel previous lookup, so this doesn't fire on a service crash
		if (this.lastRequest) {
			this.lastRequest.cancel();
			this.lastRequest = undefined;
		}
		
		if (result.record) {
			/*		
			// don't match if the number we get back is different than the number
			// we passed in (provided the lengths are the same)
			if (!(result.record.number)
				|| ( (result.record.number) && ( (result.record.number.length !== number.length) 
					|| (result.record.number.length === number.length 
						 && result.record.number === number)))) {
			*/
			contact.id = result.record.id;
			
			// parse name fields into unified name field
			if (result.record.firstName && result.record.lastName && result.record.middleName) {
				contact.name = result.record.firstName + " " + result.record.middleName+ " "+ result.record.lastName;
			} else	if (result.record.firstName && result.record.lastName) {
				contact.name = result.record.firstName + " " + result.record.lastName;
			} else {
				// default to firstname, lastname, companyname, and finally a formatted version of the number
				contact.name = result.record.firstName || result.record.lastName 
					|| result.record.companyName || result.record.displayText || FormatPhoneNumber(number);
			}
		
			// handle number label. use custom label as is.  otherwise lookup label from table
			if (result.match && result.match.label!==undefined) {
				if (result.match.label == 2 && result.match.customLabel) {
					contact.label = result.match.customLabel;
					contact.labelFormatted = result.match.customLabel
				} else {
					contact.label = result.match.label;
					contact.labelFormatted = Messages.phoneLabel[contact.label] || contact.label;
				}
			} else if (contact.label) {
				contact.labelFormatted = Messages.phoneLabel[contact.label] || contact.label;
			}
			
			contact.ringtoneLoc = result.record.ringtoneLoc;
			
			// contact picture handling:
			
			// use square pic for small pics
			contact.pictureLocSmall = result.record.pictureLocSquare;
		
			// prefer pictures in this order: pictureLocBig, pictureLoc, pictureLocSquare
			if(result.record.pictureLocBig)
				contact.pictureLoc = result.record.pictureLocBig;
			else if (result.record.pictureLoc)
				contact.pictureLoc = result.record.pictureLoc;
			else if (contact.pictureLocSmall)
				contact.pictureLoc = contact.pictureLocSmall;
		
			// if we have a picture, determine size and format
			if (contact.pictureLoc) {
				contact.pictureObj = new Image();
				contact.pictureObj.onabort = function() {
					this.onContactPictureLoadError(contact);
					this.finishLookup(contact, callback, lateCallback);
				}.bind(this);
				contact.pictureObj.onerror = function() {
					this.onContactPictureLoadError(contact);
					this.finishLookup(contact, callback, lateCallback);
				}.bind(this);
				contact.pictureObj.onload = function() {
					this.contactPictureGetProperties(contact);
					this.finishLookup(contact, callback, lateCallback);
				}.bind(this);
				contact.pictureObj.src = contact.pictureLoc;
				return;
			} 
		} else {
			this.carrierBookLookup(number,contact);
		}
		
		this.finishLookup(contact, callback, lateCallback);
    },
	
	// given a picture object attached to a contact object, measure sides
	// to determine validity and orientation. save these properties to contact object
	contactPictureGetProperties: function(contact) {
		var h = contact.pictureObj.height;
		var w = contact.pictureObj.width;
		
		// treat 0-width/height pictures as errors
		if (h < 1 || w < 1) {
			this.onContactPictureLoadError(contact);
			return;
		}
		
		// determine format
		// we consider a picture to be square if the sides' length are 
		// off by <= 10%	
		if (Math.min(h,w)/Math.max(h,w) >= .9) {
			contact.pictureFormat = "square";
			contact.pictureShortSideLength = Math.min(h,w);
		} else if (h > w) {
			contact.pictureFormat = "portrait";
			contact.pictureShortSideLength = w;
		} else if (w > h) {
			contact.pictureFormat = "landscape";
			contact.pictureShortSideLength = h;
		}
		
	},
	
	// on error, clear picture reference
	onContactPictureLoadError: function(contact) {
		QDLogger.log( "Contact#onContactPictureLoadError");
		contact.pictureLoc = undefined;
	},
	
	// mark complete; cancel timeout; fire callback if it hasn't fired yet
	finishLookup: function(contact, callback, lateCallback) {
		contact.lookupPending = false;
		
		// cancel lookup timeout
		clearTimeout(this.lookupTimeout); 
		this.lookupTimeout = undefined;
		
		if (!(contact.lookupComplete)) {
			contact.lookupComplete = true;
			callback(contact);
		// if we have a late return, and we got a contact result, call
		// the late return update 
		} else if (lateCallback && contact.doLateCallback && contact.id) {
			QDLogger.log( "Contact#finishLookup", "doing late callback");
			contact.doLateCallback = false;
			lateCallback.delay(5, contact);
		} else {
			QDLogger.log("Contacts#finishLookup: already finished this one");
		}
		
	},
	
	// flag lookup as done; proceed with callback
	onLookupTimeout: function(contact, callback, lateCallback) {
		QDLogger.log( "Contact#onLookupTimeout");
		
		if (lateCallback) {
			contact.doLateCallback = true;
		} else if (this.lastRequest && !lateCallback) {
			// cancel previous lookup, so this doesn't fire on a service crash
			this.lastRequest.cancel();
			this.lastRequest = undefined;
		}
		
		// clear timeout
		this.lookupTimeout = undefined;
		
		// flag done; fire callback if it hasn't yet
		contact.lookupComplete = true;
		var lookupWasPending = contact.lookupPending;
		contact.lookupPending = false;
		if (lookupWasPending) 
			callback(contact);
	},
	
	// performs another reverse lookup on a contact.  
	refreshReverseLookup: function(number, contact, callback) {
		// don't continue if we're still attempting a lookup
		if (!contact || contact.manuallySet || contact.lookupPending || !(contact.lookupComplete)) {
			callback(contact);
			return;
		}
			
		contact.lookupComplete = false;
		
		this.reverseLookup(number, contact, callback);
	},
	
	saveLastDialedNumber: function(number, contactId, displayName, label) {
		this.lastDialedNumber = {"number": number, "contactId": contactId, "displayName": displayName, "label": label};
	},
	
	clearLastDialedNumber: function() {
		this.lastDialedNumber = null;
	}
	
});
