/*
 * Copyright (c) 2007 Collactive. All rights reserved.
 */

// The Collactive Wallet account management stuff
if (typeof(Collactive) == 'undefined') {
	Collactive = {};
}

Collactive.AccountManager = function(storage) {
    this._storage = storage;

    // Saves a username and password, but when we're not yet sure if it's been accepted
    // by the website (i.e. before clicking "SIGN UP"). Needs to be confirmed before it
    // is retrievable.
    this.addAccount = function(siteName, username, password) {
        this._storage.saveSession(siteName + ".username", username);
        this._storage.saveSession(siteName + ".password", password);
    };

    this.deleteAccount = function(siteName) {
        this._storage.save(siteName + ".username", null);
        this._storage.save(siteName + ".password", null);
    };

    // Does the user have an account registered for the given site?
    this.hasAccount = function(siteName) {
        var username = this._storage.load(siteName + ".username");
        var password = this._storage.load(siteName + ".password");
        return (username != null && password != null);
    };


    // Gets the user's username for a site
    this.getUsername = function(siteName) {
        return this._storage.load(siteName + ".username");
    };


    // Gets the password for a site
    this.getPassword = function(siteName) {
        return this._storage.load(siteName + ".password");
    };


    // Gets the specific email address for the site, if there is one
    this.getEmailAddress = function(siteName) {
        return this._storage.load(siteName + ".emailAddress");
    };


    // Sets the specific email address for the site
    this.setEmailAddress = function(siteName, emailAddress) {
        this._storage.save(siteName + ".emailAddress", emailAddress);
    };


    // Saves a username and password, but when we're not yet sure if it's been accepted
    // by the website
    this.confirmAccount = function(siteName) {
        if (this._storage.loadSession(siteName + ".username") == null) {
            return null;
        }

        this._storage.save(
            siteName + ".username",
            this._storage.loadSession(siteName + ".username"));

        var password = this._storage.loadSession(siteName + ".password");
        this._storage.save(siteName + ".password", password);
    };


    // Returns all the sites we have accounts for
    this.getAccounts = function() {
        var names = this._storage.keys(/^(\S+).username$/);
        var accounts = [];
        for (var i = 0; i < names.length; i++) {
            accounts[accounts.length] = names[i].replace(".username", "");
        }

        return accounts;
    };

    return this;
};



Collactive.Assistant = function (shortName, requiredFields, storage, ui) {
    this._shortName = shortName;
	this._requiredFields = requiredFields;
    this._storage = storage;
	this._ui = ui;

	this.geo = null;

    this._manager = new Collactive.AccountManager(storage);

	// Specs for fields we support reading from the user
    // Each element contains the following keys:
    //  - prompt: the prompt to display next to the input field
    //  - description (optional): a description, displayed below the input field
    //  - validate (optional): a regexp to compare the input value against
    //                         OR a function to call (returns boolean)
    //  - errorString (optional): an error to display if the validate fails
    //  - input (optional): HTML to use instead of the default text input element
    //  - value (optional): a function that returns the value to be saved, instead of just the input's value
    //  - getInputElements: returns a list of the IDs of the input element(s), if they aren't with the default name
    this.FIELDS = {
		preferredUsername: {
            prompt: "Preferred username",
            description: "This will be used to register on this site and future sites",
            validate: /^[A-Za-z][A-Za-z0-9]{3,11}$/,
            errorString: "Must consist of 4-12 letters or numbers"
        },
        fullName: {
            prompt: "Full name",
            validate: /^[A-Za-z][a-zA-Z0-9\.\-]{1,20} [A-Za-z][a-zA-Z0-9\.\- ]{1,20}$/,
            errorString: "Please enter your full - first and last - name"
        },
        zipCode: {
            prompt: "Zip/postal code",
            errorString: "Please enter a valid postcode for your country",
            initialValue: Collactive.bind(this, function() { return this.geo.zip }),
            validate: Collactive.bind(this, function() {

                var country = this.getField("country");

                if (country == null) {
                    country = Collactive.DomUtils.gEBI("collactive_wizard_form_country").value;
                }

                // TODO: Canada
                if (country == "GB") {
                    // UK: postcode
                    Collactive.DomUtils.gEBI("collactive_wizard_form_zipCode").value = Collactive.DomUtils.gEBI("collactive_wizard_form_zipCode").value.toUpperCase();
                    return Collactive.DomUtils.gEBI("collactive_wizard_form_zipCode").value.match(/^[A-Z]+\d+[A-Z]*\s+\d[A-Z]+$/);
                } else if (country == "US") {
                    // US: 5 digits only, but strip off the "-9999" suffix if the user provided it
                    var result = Collactive.DomUtils.gEBI("collactive_wizard_form_zipCode").value.match(/^(\d{5})(-\d{4})?$/);
                    if (result == null) {
                        return false;
                    }
                    if (result[2] != null) {
                        Collactive.DomUtils.gEBI("collactive_wizard_form_zipCode").value = result[1];
                    }
                    return true;
                } else {
                    return true;
                }
            })
        },
        country: {
            prompt: "Country",
            initialValue: Collactive.bind(this, function() { return this.geo.country }),
            input: '<select id="collactive_wizard_form_country" style="width: 220px">' + Collactive.Effects.getCountryOptions() + "</select>",
            errorString: "Please select a country"
        },
        emailAddress: {
            prompt: "Email address",
            description: "Please enter a valid address as it may be confirmed",
            validate: /^[a-z0-9\-\.\_\+]{1,}@[a-z0-9\-\.]+\.[a-z]{2,4}$/i,
            errorString: "Please enter a valid email address"
        },
        dateOfBirth: {
            prompt: "Date of birth",
            input: '<select id="collactive_wizard_form_dob_month" style="width: 115px"><option value="">--</option>' + Collactive.Effects.getMonthOptions() + '</select>'
                   + '&nbsp;<select id="collactive_wizard_form_dob_day"><option value="">--</option>' + Collactive.Effects.getRangeAsOptions(1,31) + '</select>'
                   + '&nbsp;<select id="collactive_wizard_form_dob_year"><option value="">--</option>' + Collactive.Effects.getRangeAsOptions(new Date().getFullYear()-13, 1910) + '</select>',
            value: function() {
                var d = new Date(0);
                d.setMonth(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_month").value - 1);
                d.setDate(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_day").value);
                d.setFullYear(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_year").value);
                // Save UNIX time (getDateOfBirth() reads this)
                return d.getTime();
            },
            getInputElements: function() {
                return ["collactive_wizard_form_dob_month",
                        "collactive_wizard_form_dob_day",
                        "collactive_wizard_form_dob_year"];
            },
            validate: function() {
                var d = new Date("Jan 01 1980");
                d.setMonth(parseInt(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_month").value) - 1);
                d.setDate(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_day").value);
                d.setFullYear(Collactive.DomUtils.gEBI("collactive_wizard_form_dob_year").value);

                var valid = true;
                valid &= (d.getMonth() == (Collactive.DomUtils.gEBI("collactive_wizard_form_dob_month").value - 1));
                valid &= (d.getDate() == Collactive.DomUtils.gEBI("collactive_wizard_form_dob_day").value);
                valid &= (d.getFullYear() == Collactive.DomUtils.gEBI("collactive_wizard_form_dob_year").value);
                return valid;
            },
            errorString: "Please enter a valid date"
        },
        gender: {
            prompt: "Gender",
            input: '<select id="collactive_wizard_form_gender" style="width: 220px"><option value="">--</option><option value="M">Male</option><option value="F">Female</option></select>',
            validate: /^[MF]$/,
            errorString: "Please select a gender from the list above"
        }
    };


	// UI-related functions
	this.accountSetupPrompt = function(cb) {
		this._ui.displayAccountSetupPrompt(cb);
	};

	this.accountAddPrompt = function(addFromRegister, failedLastTime, username, cb) {
	    this._ui.displayAccountAddPrompt(addFromRegister, failedLastTime, username, cb);
	};

	this.confirmationPrompt = function(fromAddress, confirmURL, cb) {
	    this._ui.displayConfirmationPrompt(fromAddress, confirmURL, cb)
	};

    this.verificationImagePrompt = function(missedLastTime, imageHTML, cb) {
        this._ui.displayVerificationImagePrompt(missedLastTime, imageHTML, cb);
    };
	this.displayThumbChoosePrompt = function(thumbHTMLs, cb){
		this._ui.displayThumbChoosePrompt(thumbHTMLs, cb);
	}

    this.emailErrorPrompt = function(type, emailAddress, cb) {
        this._ui.displayEmailErrorPrompt(type, emailAddress, cb);
    };

	this.reportStep = function(stepName) {
		this._ui.displayStep(stepName);
	};

	this.reportRegisteringStatus = function() {
		this._ui.displayRegisteringMessage();
	};

    this.reportPercentageProgressStatus = function(title, percent_done) {
		this._ui.displayPecentageProgress(title, percent_done);
	}

	this.reportLoggingInStatus = function() {
		this._ui.displayLoggingInMessage();
	};

	this.reportLoadingStatus = function() {
		this._ui.displayLoadingMessage();
	};

	this.reportPerformingActionStatus = function() {
	    this._ui.displayPerformingActionMessage();
	};

    this.reportStep = function(stepName) {
        this._ui.displayStep(stepName);
    };

	this.startAnimationMode = function(controlCallbacks) {
		this._ui.startAnimationMode(controlCallbacks);
	};

	this.stopAnimationMode = function() {
		this._ui.stopAnimationMode();
	};

	///////////////////////

    // Gets a wallet field
    this.getField = function(name) {
        return this._storage.load("fields." + name);
    };


    // Sets a wallet field
    this.setField = function(name, value) {
        this._storage.save("fields." + name, value);
    };


    // Returns the preferred username
    this.getPreferredUsername = function() {
        return this.getField("preferredUsername");
    };
    // Returns the preferred username
    this.resetPreferredUsername = function() {
        return this.setField("preferredUsername", null);
    };


    // Returns the date of birth (if entered), as a Date object
    this.getDateOfBirth = function() {
        var dateString = this.getField("dateOfBirth");
        if (dateString == null) {
            return null;
        }
        return new Date(parseInt(dateString));
    };


    // Returns the first name
    this.getFirstName = function() {
        if (this.getField("fullName") == null) {
            return null;
        }

        var names = this.getField("fullName").split(/\s+/);
        return names[0];
    };


    // Returns the first name
    this.getLastName = function() {
        if (this.getField("fullName") == null) {
            return null;
        }

        var names = this.getField("fullName").split(/\s+/);
        return names[names.length - 1];
    };


    // Generates a username from the preferred username
    this.generateUsername = function(withRandom) {
        if (!withRandom) {
            return this.getPreferredUsername();
        }

        // Time to add a a random suffix
        var name = this.getPreferredUsername().split("");
        var vowels = "a,e,i,o,u,ee,ea,ie,ou".split(",");
        var consonants = "b,c,ch,d,g,h,j,k,l,m,n,p,ph,r,s,t,th,v,w,x,y,z".split(",");
        var suffixLength = Collactive.USERNAME_MIN_SUFFIX
            + Math.floor((Collactive.USERNAME_MAX_SUFFIX - Collactive.USERNAME_MIN_SUFFIX) * Math.random());
        for (var i = 0; i < suffixLength; i++) {
            var list = (i % 2 == 0) ? consonants : vowels;
            var letter = list[Math.floor(list.length * Math.random())];
            name[name.length] = letter;
        }

        if (name.length > Collactive.USERNAME_MAX_LENGTH) {
            name.splice(0, name.length - Collactive.USERNAME_MAX_LENGTH);
        }

        return name.join("");
    };

	this.getEmailAddress = function() {
	    var specific = this._manager.getEmailAddress(this._shortName);
	    if (specific == null) {
	        return this.getField("emailAddress");
	    } else {
    	    return specific;
	    }
	};

	this.setSiteSpecificEmailAddress = function(emailAddress) {
	    this._manager.setEmailAddress(this._shortName, emailAddress);
	};

    this.addAccount = function(username, password) {
        return this._manager.addAccount(this._shortName, username, password)
    };

    this.hasAccount = function() {
        return this._manager.hasAccount(this._shortName);
    };

    this.getUsername = function() {
        return this._manager.getUsername(this._shortName);
    };

    this.getPassword = function() {
        return this._manager.getPassword(this._shortName);
    };

    this.confirmAccount = function() {
        Collactive.CSAUtils.notifyOperation("assist_confirm_account");
        return this._manager.confirmAccount(this._shortName);
    };


    // Generates a password
    this.generatePassword = function(maxLength) {
        if (maxLength == null) {
            maxLength = Collactive.PASSWORD_LENGTH;
        }

        var password = "";

        for (var i = 0; i < maxLength; i++) {
            var type = Math.random();
            if (type < 0.3) {
                password += String.fromCharCode("a".charCodeAt(0) + Math.floor(26*Math.random()));
            } else if (type < 0.6) {
                password += String.fromCharCode("A".charCodeAt(0) + Math.floor(26*Math.random()));
            } else {
                password += String.fromCharCode("0".charCodeAt(0) + Math.floor(10*Math.random()))
            }
        }

        return password;
    };


    this.loadGeoData = function(callback) {
        window.setTimeout(Collactive.bind(this, function() {
            // Strange Ajax.Request behavior does not work outside
            // of voodoo setTimeout...
            new Ajax.Request("/tools/geo", {
                             onComplete: Collactive.bind(this, function(req) {
                                try {
                                     this.geo = eval("(" + req.responseText + ")");
                                 } catch (e) {
                                     // Eval failed, no geo :-( but no big deal...
                                 }

                                 callback();
                             })
                         });
        }), 10);
    };


    // Calls a function, but if we don't have a wallet with our details yet,
    // then get them from the user first and only then call the function.
    this.initWalletAndCall = function(callback) {
        var missingFields = [];
        for (var i = 0; i < this._requiredFields.length; i++) {
            var requiredField = this._requiredFields[i];
            if (this.getField(requiredField) == null) {
                missingFields[missingFields.length] = requiredField;
            }
        }

        var setPreferredFromName = false;
        if (this.getPreferredUsername() == null) {
            // If we're asking for full name anyway, don't bother asking
            // for preferrred username
            for (var i = 0; i < missingFields.length; i++) {
                if (missingFields[i] == "fullName") {
                    setPreferredFromName = true;
                }
            }

            if (!setPreferredFromName) {
                missingFields.splice(0, 0, "preferredUsername");
            }
        }

        if (missingFields.length > 0) {
            this.loadGeoData(Collactive.bind(this, function() {
                this._ui.displayForm(missingFields, this.FIELDS, {
                    onCompletion: Collactive.bind(this, function(values) {
                        for (var i = 0; i < missingFields.length; i++) {
                            this.setField(missingFields[i], values[missingFields[i]]);
                        }
                        if (setPreferredFromName) {
                            var username = this.getFirstName() + this.getLastName().charAt(0);
                            username = username.replace(/[^a-zA-Z]/g, "");
                            this.setField("preferredUsername", username);
                        }
                        callback.onCompletion();
                    }),
                    onCancel: callback.onCancel
                });
            }));
        } else {
            callback.onCompletion();
        }
    };

	this.shortName = function() {
		return this._shortName;
	};

    // Notification from SA that something is wrong
    this.notifyError = function(params, msg, skipDisplayNotify) {
		if (typeof(Automate) != 'undefined' && Automate.error) {Automate.error();}		
        if (isDevelopmentMode() && typeof(console) != "undefined" && typeof(console.error) == "function") {
            console.error("Web Assistant error", params)
        }

        var paramStrings = [];
        for (var i in params) {
            if (i == "extend") {
                continue;
            }
            paramStrings[paramStrings.length] = i + "=" + encodeURIComponent(params[i]);
        }

        var paramString = paramStrings.join("&");

        window.setTimeout(Collactive.bind(this, function() {
            new Ajax.Request("/assistant/error", {
                method: 'post',
                parameters: paramString
            });
			if (!skipDisplayNotify) {
				this._ui.displayErrorMessage(msg);
			}
        }), 100);
    };


	return this;
};
