// trial-request.js
//
// Routines for client-side generation/validation of workspace IDs
// and AJAX lookup/validation of workspace IDs.
//


// Shared HTTP request object and request URI, set within
// init_environment() and used across multiple functions.
//
var Request;
var Request_Query_URI;

// build_requester()
//
// Handles the grunt work of creating the appropriate HTTP
// request object, based on the browser type and, for IE,
// the available version of the MS XMLHTTP ActiveX object.
//
// Normally only called during initialization, to create the
// HTTP request object that's then used throughout the session.
//
function build_requester() {

    var req;

    // For Mozilla-based browsers, use the XMLHttpRequest
    // object of the window. For IE, use one of the ActiveX-
    // based requesters.
    //
    if (window.XMLHttpRequest) {
        req = new XMLHttpRequest();
    }
    else if (window.ActiveXObject) { // looks like IE

        // Try to use the better one.
        //
        req = new ActiveXObject("Msxml2.XMLHTTP");

        // Fall back to the older one if necessary.
        //
        if (! req) {
            req = new ActiveXObject("Microsoft.XMLHTTP");
        }
    }

    // If there's still no request object, complain.
    //
    if (! req) {
        window.alert("Unable to build XMLHTTP request object.");
        return null;
    }

    return req;
}


// send_request_synchronous()
//
function send_request_synchronous(data) {

    // Send the request to the server, and wait for the response.
    //
    Request.open("POST", Request_Query_URI, false);

    Request.setRequestHeader(
        "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

    Request.send(data);
}


// send_request()
//
// Given a data block (formatted as a query string) and a callback
// function, configures the HTTP request object with the specified
// callback, then POSTs the data block to the predefined URI (see
// above).
//
function send_request(data, callback) {

    // Open the connection to the server.
    //
    Request.open("POST", Request_Query_URI, true);

    // Specify the function that will handle the HTTP response.
    // Note that this handler is assigned *after* the open() call,
    // to allow reuse of the Request object on IE.
    //
    // Details here:
    // http://keelypavan.blogspot.com/2006/03/reusing-xmlhttprequest-object-in-ie.html 
    // http://radio.javaranch.com/pascarello/2006/03/31/1143817890773.html
    //
    Request.onreadystatechange = callback;

    Request.setRequestHeader(
        "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");

    Request.send(data);
}


// requested_data()
//
// Evaluates and returns the JSON returned from the most recent
// AJAX request.
//
function requested_data() {

    var func = new Function( "return " + Request.responseText );

    return func();
}


// update_workspace_id_availability()
//
// Sets the ID availability indicator based on the current state
// of the AJAX workspace ID lookup.
//
function update_workspace_id_availability() {

    var ind = document.getElementById("workspaceIdAvailabilityIndicator");
    var el  = document.getElementById("Trial_url__c");

    clear_workspace_id_availability_indicator();

    clear_validation_error_message(el);
    
    // If the request hasn't finished yet, let the user know we're
    // still checking.
    //
    if (! (4 == Request.readyState)) {
        ind.innerHTML = "Checking availability...";
        return;
    }

    // If the Request was processed without error, then
    // display the results.
    //
    if (Request.status == 200) {

        var results = requested_data();

        if (results.available) {
            ind.innerHTML = "<span style='color:green'>This web address is available!</span>"; av=1;
        }
        else {
        
            show_validation_error_message(el,
                'This web address is unavailable. Please enter a different address.'
            ); av=0;
        }
    }
    else {
        // There was some sort of an error, so just be quiet. This isn't
        // perfect, but it's better than tossing up an ugly alert that the user
        // can't do anything about, and which may not matter anyway.
        //
        ind.innerHTML = "";
    }
}


// clear_workspace_id_availability_indicator()
//
// Removes any message in the ID availability indicator.
//
function clear_workspace_id_availability_indicator() {

    var ind = document.getElementById("workspaceIdAvailabilityIndicator");

    ind.innerHTML = "";
}


// check_id_availability()
//
// Issues the AJAX request that checks the availability of the
// current workspace ID, and sets the callback for updating the
// availability indicator.
//
function check_id_availability() {

    var workspace_id = document.getElementById('Trial_url__c').value;

    clear_validation_error_message(document.getElementById('Trial_url__c'));

    if ("" == workspace_id) return;

    send_request("action=workspaceIdAvailable&args=" + escape(workspace_id),
        update_workspace_id_availability);
}


// check_id_availability_synchronous()
//
// Issues a request to check the availability of the current
// workspace ID, then waits for the response. Returns 'yes',
// 'no', or 'error' depending on the response.
//
function check_id_availability_synchronous() {

    var f = document.forms["trialIdAndTitle"];
    var workspace_id = f["workspace_id_c"].value;

    send_request_synchronous("action=workspaceIdAvailable&args=" +
        escape(workspace_id));

    // update_workspace_id_availability();

    if (Request.status == 200) {

        var results = requested_data();

        return (results.available ? "yes" : "no");
    }
    else {
        return "error";
    }
}


// generate_workspace_id()
//
// Given a workspace title, generates and returns a suitable
// workspace ID based on the title.
//
function generate_workspace_id(Workspace_Title__c) {

    return Workspace_Title__c
        .toLowerCase()                  // convert to all lowercase
        .replace(/^\s+/, "")            // remove leading spaces
        .replace(/\s+$/, "")            // remove trailing spaces
        .replace(/\s+/g, "-")           // convert one or more spaces to a single dash
        .replace(/[^-_a-z0-9]/g, "")    // only allow underscores, dashes, letters, and numbers 
        .substring(0, 30)               // limit the length
    ;
}


// update_sample_address()
//
// Updates the sample e-mail address based on the current workspace ID.
//
function update_sample_address() {

    var d = document;

    d.getElementById("sampleEmailAddress").innerHTML =
        d.getElementById('Trial_url__c').value + "@socialtext.net";
}

// Flag that indicates if there's already a highlight timer running.
// Used in both highlight_workspace_id_instructions() and
// unhighlight_workspace_id_instructions().
//
var Already_Highlighted = false;

// Highlight and normal background colors, used across highlight/
// unhighlight routines.
//
// XXX - Use CSS classes instead of directly manipulating the background color.
//
var HIGHLIGHT_COLOR = "#FFFEBD"; // light yellow
var NORMAL_COLOR    = "#FFFFFF"; // white

// highlight_workspace_id_instructions()
//
// Emphasizes the workspace ID instructions. Called when the user
// attempts to enter an illegal ID character.
//
function highlight_workspace_id_instructions() {

    // Only kick this off one at a time, to avoid stacking up multiple
    // sets of timers. Otherwise the flash effect gets ugly and jittery.
    //
    if (Already_Highlighted)
        return;

    // Set a flag to indicate that the instructions are already flashing
    // (see comment above).
    //
    Already_Highlighted = true;

    // XXX: Make cross-browser.
    //
    var el = document.getElementById("workspaceIdInstructions");

    el.style.backgroundColor = HIGHLIGHT_COLOR;

    // Clear the highlight in a few seconds.
    //
    setTimeout("unhighlight_workspace_id_instructions()", 3000);
}

// unhighlight_workspace_id_instructions()
//
// Return to the normal unhighlighted state, called when the user
// leaves the ID field.
//
function unhighlight_workspace_id_instructions() {

    // XXX: Make cross-browser.
    //
    var el = document.getElementById("workspaceIdInstructions");

    el.style.backgroundColor = NORMAL_COLOR;

    Already_Highlighted = false;
}


// clean_id()
//
// Sanitizes the workspace ID, and highlights the workspace ID
// instructions if necessary.
//
function clean_id() {

    var element = document.getElementById('Trial_url__c');

    // Grab the current workspace ID, so it can be compared to the 
    // generated ID.
    //
    var current_id = element.value;
    var new_id = generate_workspace_id( element.value );

    // If they're different, update the ID to the sanitized value,
    // then highlight the instructions to let the user know what
    // they're doing wrong.
    //
    if (current_id != new_id) {

        element.value = new_id;

        highlight_workspace_id_instructions();
    }

    clear_workspace_id_availability_indicator();

    update_sample_address();
}


// update_workspace_id()
//
// Updates the workspace ID based on the workspace title,
// then updates the sample addresses.
//
function update_workspace_id() {

    var f = document.forms["trialIdAndTitle"];

    f["workspace_id_c"].value = generate_workspace_id( f["workspace_title_c"].value );

    update_sample_address();

    clear_workspace_id_availability_indicator();
}


// load_default_trial_form_values()
//
// Sets the initial values for the trial workspace title and ID
// on the trial request step 3 form.
//
function load_default_trial_form_values() {

    var f = document.forms["trialIdAndTitle"];

    // Set the default workspace title based on the organization name.
    //
    if (f["account_name"]) {
        f["workspace_title_c"].value = f["account_name"].value;
    }

    // Set the workspace ID based on the workspace title.
    //
    update_workspace_id();
}


// load_default_personal_form_values()
//
// Sets the initial values for the personal workspace title and ID
// on the personal request step 2 form.
//
function load_default_personal_form_values() {

    var f = document.forms["trialIdAndTitle"];

    // Set the default workspace title based on the person's first
    // and last name.
    //
    f["workspace_title_c"].value =
        f["first_name"].value + " " + f["last_name"].value + " Personal Wiki";

    // Set the workspace ID based on the workspace title.
    //
    update_workspace_id();
}


// error_message_element_for(el)
//
// Finds or creates the error message div for the specified element,
// then returns the element.
//
function error_message_element_for(el) {
    
    var err_msg_id = el.id + "-errMsg";
    var err_msg_el = document.getElementById(err_msg_id);

    if (! err_msg_el) {

        err_msg_el = document.createElement("div");
        
        err_msg_el.setAttribute("id", err_msg_id);

        el.parentNode.insertBefore(err_msg_el, el.nextSibling);
    }

    return err_msg_el;
}


// clear_validation_error_message(el)
//
// Removes any validation error message for the specified element.
//
function clear_validation_error_message(el) {

    var err_msg_el = error_message_element_for(el);

    if (err_msg_el) {
        err_msg_el.innerHTML = "";
    }

    // XXX - This should really restore whatever was there before,
    // but this will work well enough for now.
    //
    el.className = "";
}


// show_validation_error_message()
//
// Displays the specified message for the given element, and
// sets the CSS styles for both the original element and its
// associated error element as necessary.
//
function show_validation_error_message(el, msg) {
    var err_msg_el = error_message_element_for(el);
    var msgNode = document.createTextNode(msg);

    el.className = "errFld";

    err_msg_el.appendChild(msgNode);
    err_msg_el.className = "errMsg";
}


function clear_validation_messages(f, fields) {

    for (var i in fields) {
        clear_validation_error_message(f[ fields[i] ]);
    }
}


var MSG_REQUIRED_TEXT   = "Please enter a value.";
var MSG_REQUIRED_SELECT = "Please select a value.";

function validate_required_fields(f, required_fields, problems) {

    for (var i in required_fields) {
        var el = f[required_fields[i]];
        
        clear_validation_error_message(el);

        switch (el.type) {
        
        case "text":
            if ("" == el.value) {
                show_validation_error_message(el, MSG_REQUIRED_TEXT);
                problems.push(el);
            }
            break;

        case "select-one":

            // For our forms, both -1 and 0 mean nothing's been selected.
            // (item 0 is "SELECT <whatever>")
            //
            if ( ( 0 == el.selectedIndex)
              || (-1 == el.selectedIndex) ) {
                show_validation_error_message(el, MSG_REQUIRED_SELECT);
                problems.push(el);
            }
            break;

        default:
            // XXX - Raise error here.
        
        } // end switch

    } // end for
}


function explain_problems(problems) {

    if (problems.length > 0) {

        window.alert("One or more errors detected."
            + " Your form has not yet been submitted.\n\n"
            + "Please check the information you provided.");

        // Set the input focus to the first problem element.
        //
        problems[0].focus();
    }

}

// Regular expression for validating email addresses. Not perfect,
// but close enough to eliminate the majority of invalid addresses,
// which erring on the side of caution. Adapted from here:
//
// http://fightingforalostcause.net/misc/2006/compare-email-regex.php
//
var EMAIL_ADDRESS_REGEX = new RegExp(
      "^"
    + "([a-zA-Z0-9_'+*$%\\^&!\\.\\-])+"
    + "@"
    + "(([a-zA-Z0-9\\-])+\\.)+"
    + "([a-zA-Z0-9:]{2,4})+"
    + "$"

    , "i"
);

function is_valid_Email(Email) {
    return EMAIL_ADDRESS_REGEX.test(Email);
}


// validate_trial_request_step_1()
//
// Checks the Step 1 form prior to submission.  Displays any validation errors.
// Returns true if the validation is successful, false otherwise.
//
function validate_trial_request(f) {

    var problems = Array();
    var required_fields = Array(
        'FirstName',
        'LastName',
        'Email',
        'Phone',
        'Company',
        'street',
        'city',
        'Country',
        'State',
        'zip',
        'What_best_describes_your_organization__c',
        'Title',
        'industry',
        'Workspace_Title__c',
        'Trial_url__c'
    );

    var all_fields = required_fields.concat(
       'Trial_eMail_1__c',
       'Trial_eMail_2__c',
       'Trial_eMail_3__c',
       'Trial_eMail_4__c',
       'Trial_eMail_5__c'
    );

    // Remove any existing validation error messages.
    //
    clear_validation_messages(f, all_fields);

    // Deal with required fields first.
    //
    validate_required_fields(f, required_fields, problems);

    //
    // Now handle the more advanced validation.
    //

    // Check the e-mail address.
    //
    if ( ("" != f["Email"].value)
      && ! is_valid_Email( f["Email"].value ) ) {
    
        show_validation_error_message(f["Email"],
            "Please enter a valid email address.");

        problems.push(f["Email"]);
    }

    // If any problems were detected, tell the user about them.
    // 
    explain_problems(problems);

    if (problems.length > 0) {
        return false;
    }

    return true;
}


// set_display()
//
// Given an element ID and a string value (usually "none" or "block"),
// sets the element's style display property to the value.
//
function set_display(element_id, value) {
    var elem = document.getElementById(element_id);
    if (elem) {
        elem.style.display = value;
    }
}


function show_and_hide_lists(f) {
    set_display("us-list",      (f.corp_hq_us.checked    ? "block" : "none") );
    set_display("country-list", (f.corp_hq_other.checked ? "block" : "none") );
}


// Instantiates the HTTP request object required to communicate with
// the server application, then sets the initial state of the UI.
//
function init_environment(ajax_query_uri)
{
    Request_Query_URI = ajax_query_uri;

    Request = build_requester();
}

// end trial-request.js

