window.actionkit = { 'utils': {}, 'forms': {} };

// Keep console.log() from being an error
if ( !window.console ) window.console = { log: function() {} };

(function(ak, utils, forms) {

var $ = window.jQuery;
var $id = function(id) { return document.getElementById(id) };
var $sel = function(selector) { return $(document).find(selector) };
var $log = function(item) { if (window.console) window.console.log(item); };

var $text = function(str) { return (forms.text && forms.text[str]) || '' };

// Accepts an error_name like 'card_num:invalid'
forms.errorMessage = function(error_name) { 
    var pieces = error_name.split(':');
    var field_name = pieces[0];
    var error_type = pieces[1];

    if ( ak.form['error_' + error_name] )
        return ak.form['error_' + error_name].value;

    if ( $text('error_' + error_name) ) 
        return $text('error_' + error_name);

    var formatString = $text('error_TEMPLATE:' + error_type);
    return utils.capitalize(
        utils.format(formatString, forms.fieldName(field_name))
    );
};

forms.fieldName = function(field_name) {
    if ( ak.form['field_' + field_name] )
        return ak.form['field_' + field_name].value;

    if ( $text('field_' + field_name) ) 
        return $text('field_' + field_name);

    clean_name = field_name.replace(/^(user|action)_/, '');
    clean_name = clean_name.replace(/_/g, ' ').toLowerCase();
    return clean_name;
}

forms.contextRoot = '/context/';

/* Ask for user info, congress #s, etc. ("context") via script tag */
forms.loadContext = function() {
    var contextArgs = {};
    
    contextArgs.callback = 'actionkit.forms.onContextLoaded';
    contextArgs.page = ak.form.page.value;
    if ( ak.args.action_id ) contextArgs.action_id = ak.args.action_id;
    if ( ak.args.akid ) contextArgs.akid = ak.args.akid;
    if ( ak.args.rd ) contextArgs.rd = ak.args.rd;
    contextArgs.required = forms.required();
    if ( ak.form.want_progress ) contextArgs.want_progress = 1;
    contextArgs.r = Math.random() // bust caching of response
    
    // Long URLs mess up MSIE
    var url = '' + window.location;
    if ( url.length < 500 ) contextArgs.url = url;
    
    var root = forms.contextRoot;

    var contextUrl = (root + '?' + utils.makeQueryString(contextArgs));
    
    forms.createScriptElement(contextUrl);
};

forms.loadPrefiller = function() {
    var prefillerUrl = forms.contextRoot.replace('/context/', '/samples/prefill.js');
    forms.createScriptElement(prefillerUrl);
}

forms.loadProgress = function() {
    var progressUrl = forms.contextRoot.replace(
        '/context/', 
        '/progress/?page=' + ak.form.page.value + '&callback=actionkit.forms.onProgressLoaded'
    );
    forms.createScriptElement(progressUrl);
}

forms.onProgressLoaded = function(progress) {
    ak.context.progress = progress;
    // This may be a jQuery bug: I have to manually filter for htmlFor ==
    // 'progress'
    var templates = $sel("script[type=text/ak-template]");
    for ( var i  = 0; i < templates.length; ++i )
        if ( utils.getAttr(templates[i], 'for') == 'progress' )
            forms.doTemplate(ak.context, templates[i]);
}

forms.onPrefillerLoaded = function() {
    if ( ak.forms.awaitingPrefil ) forms.prefill();
}

forms.prefill = function() {
    $(ak.form).deserialize(ak.args, {overwrite: false});
}

forms.loadText = function() {
    var relative_url = '/text/' + ak.context.lang_id + '?callback=actionkit.forms.onTextLoaded&rand_id=' + Math.random();
    var textUrl = forms.contextRoot.replace('/context/', relative_url);
    forms.createScriptElement(textUrl);
}

forms.onTextLoaded = function(text) {
    forms.text = text;
}

forms.createScriptElement = function(url, attrs) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    if ( attrs )
        for ( name in attrs )
            if ( attrs.hasOwnProperty(name) )
                $(script).attr(name, attrs[name]);
    document.getElementsByTagName('head')[0].appendChild(script);
    script.src = url;
}

// Used by admin.  Give an anonymous callback a name, make a
// call for JSON like the /context and /text calls
var max_callback_id = 0;
forms.loadJSON = function(url, args, fn, err) {
    var callback_id = ++max_callback_id;
    var callback_name = 'actionkitCallback' + callback_id;
    window[callback_name] = fn;
    var err_callback_name = 'actionkitError' + callback_id;
    if(err) window[err_callback_name] = err;
    if (!args) args = {};
    args.callback = 'window.' + callback_name;
    url_with_args = url + '?' + utils.makeQueryString(args);
    forms.createScriptElement(
        url_with_args, 
        err ? {onerror: err_callback_name + '()'} : {}
    );
};

forms.handleQueryStringErrors = function() {
    // Load up errors from the query string into ak.errors and display them
    var errors = {}
    for (key in actionkit.args) {
        match = /^error_(.*)/.exec(key)
        if ( !match ) continue;
        error_name = match[1];
        error_html = ak.args[key];

        // Don't insert HTML tags from ak.args because that'd allow XSS.  
        if ( /</.test(error_html) ) {
            // To not break existing custom error msgs with links, try to
            // get the error message from a hidden field if it's stored
            // there.
            var error_from_page = 
                forms.errorMessage(error_name);
            if ( error_from_page ) {
                error_html = error_from_page;
            }
            // Failing that, strip the HTML out of the error from the args.
            else {
                error_html = error_html.replace(/<.*?>/g, '');
                error_html = error_html.replace(/</g, '');
            }
        }

        errors[error_name] = error_html;
    }
    if ( utils.hasAnyProperties(errors) ) {
        ak.errors = errors;
        ak.forms.onValidationErrors(errors);
    }
}

/* Set akid, source, etc. in form, do "Not Bob?" if needed */
forms.onContextLoaded = function(context) {
    if ( ak.context ) return;
    
    var start = (new Date()).getTime()
    
    ak.context = context;
    
    var recognize = ( context.name
                      && !context.missing_user_fields 
                      && !context.incomplete );
    
    var referring_akid = ak.args.referring_akid;

    if ( recognize ) {
        utils.appendHiddenInput('akid', ak.args.akid);
        $id('known_user_name').appendChild(
            document.createTextNode(context.name)
        );
        $id('unknown_user').style.display = 'none';
        $id('known_user').style.display = 'block';
        if ( ak.args.action_id && $id('ak-logout') ) 
            $id('ak-logout').style.display = 'none';
    }
    else {
        if ( ak.args.akid ) referring_akid = ak.args.akid;
        if ( $id('known_user') )
            $id('known_user').style.display = 'none';
        if ( $id('unknown_user') )
            $id('unknown_user').style.display = 'block';
    }

    ak.form.style.display = 'block';

    if ( referring_akid )
        utils.appendHiddenInput('referring_akid', referring_akid);

    if ( ak.args.source )
        utils.appendHiddenInput('source', ak.args.source);

    utils.appendHiddenInput('url', window.location);
    
    utils.appendHiddenInput('js', 1);
    
    if ( context.required )
        for ( var i = 0; i < context.required.length; ++i )
            utils.appendHiddenInput('required', context.required[i]);
    
    if ( context.incomplete )
        utils.appendHiddenInput('status', 'incomplete');
    
    if ( context.targets )
        forms.onTargets();
    
    context.args = ak.args;
    context.capitalize = ak.utils.capitalize;
    context.add_commas = ak.utils.add_commas;

    if ( ak.form.want_progress && !context.progress )
        forms.loadProgress();

    // For old templates, avoid an error when context.progress is 
    // missing
    if ( !context.progress ) context.progress = {
        'goal': undefined,
        'total': undefined
    };

    var templates = $sel("script[type=text/ak-template]");
    for ( var i = 0; i < templates.length; ++i )
        forms.doTemplate(context, templates[i]);

    if ( typeof($.fn.deserialize) == 'function' )
        actionkit.forms.prefill();
    else
        actionkit.forms.awaitingPrefill = 1;

    forms.handleQueryStringErrors();
        
    if ( window.startTime )
        $log(((new Date()).getTime() - window.startTime) + 'ms');

    forms.loadText();
};

forms.doTemplate = function(context, elem) {
    var forElem = utils.getAttr(elem, 'for');
    try {
        var html = utils.template(elem.innerHTML, context);
        $id(forElem).innerHTML = html;
    }
    catch(e) {
        // Should this complain more loudly?
        $log('Template exception (id: ' + forElem + ')');
        $log(e);
    }
};

forms.onTargets = function() {
    var targets = ak.context.targets;

    targets.pl = function(singular, plural) { 
        return targets.plural ? plural : singular;
    }
    targets.s = targets.pl('', 's');
    targets.es = targets.pl('', 'es');

    var target_form = $id('target_checkboxes');
    $log(targets.checkboxes_html)
    if (target_form && targets.checkboxes_html) 
        target_form.innerHTML = targets.checkboxes_html;
    
    var target_listing = $id('target_listing');
    if (target_listing && targets.listing_html) 
        target_listing.innerHTML = targets.listing_html;
};
    
forms.logOut = function() {
    var args = ak.args;
    args['referring_akid'] = args['akid'];
    delete args['akid'];
    window.location.search = '?' + utils.makeQueryString(args);
    return false;
};

var validators = {};

validators.email = function() {
    if ( !/^\s*\S+@\S+\.\S+\s*$/.test(this.value) )
        return forms.errorMessage('email:invalid');
    return true;
};

validators.taf_emails = function() {
    if ( !/\w\S+@\S+\.\w+/.test(this.value) )
        return forms.errorMessage('taf_emails:missing')
    return true;
};

validators.zip = function() {
    if ( ak.form.country && utils.val(ak.form.country) != 'United States' ) 
        return true;
    if ( !/\d{5}/.test(this.value) )
        return forms.errorMessage('zip:invalid')
    return true;
};

validators.phone = function() {
    if ( ak.form.country && utils.val(ak.form.country) != 'United States' ) 
        return true;
    if ( !/^.*\d{3}.*\d{3}.*\d{4}.*/.test(this.value) ) 
        return forms.errorMessage('phone:invalid');
    return true;
};

validators.mobile_phone = 
    validators.home_phone = 
    validators.work_phone = 
    validators.emergency_phone = 
        validators.phone;

forms.defaultValidators = validators;

forms.required = function() {
    var required = ['email'];
    
    var required_inputs = 
        ak.form.elements.required ? utils.list(ak.form.elements.required) : [];
    for ( var i = 0; i < required_inputs.length; ++i )
        required.push(required_inputs[i].value);
    return required;
};
    
forms.validate = function() {
    var errors = {};
    var required = forms.required();
    
    // If we're missing translation data we can't display errors anyway, so just 
    // let the server check
    if ( !forms.text ) return true;
    
    // Filter out user fields if appropriate
    if ( ak.form.akid ) {
        var userFieldSet =
            utils.makeSet(['email', 'prefix', 'first_name', 'middle_name', 
                   'last_name', 'suffix', 'name', 'address1', 'address2',
                   'city', 'state', 'zip', 'postal', 'country', 'region']);
        for ( var i = 0; i < required.length; ++i )
            if ( /^user_/.test(required[i]) || 
                 userFieldSet[required[i]] )
                delete required[i];
    }
    
    // Check they're nonblank
    for ( var i = 0; i < required.length; ++i ) {
        if ( typeof(required[i]) == 'undefined' ) continue;

        // Special case: first_name/last_name required but there's only a
        // "name" field
        if ( /^(first|last)_name$/.test(required[i]) 
             && !ak.form[required[i]] ) {
            // Error if only one name was given
            if ( !ak.form.name || !/\S\s\S/.test(ak.form.name.value) )
                errors['name:first_and_last'] = 
                    forms.errorMessage('name:first_and_last')
            continue;
        }

        if ( !ak.form[required[i]] || !utils.val(ak.form[required[i]]) )
            errors[required[i] + ':missing'] = 
                forms.errorMessage(required[i] + ':missing')
    }
    
    // Check validity with onvalidate
    elements = ak.form.elements;
    var required_set = utils.makeSet(required);
    for ( var i = 0; i < elements.length; ++i ) {
            var elem = elements[i];
            var val = utils.val(elem);
            if ( !val && !required[elem.name] ) continue;
            // Temporarily patch up expiration dates on the client
            // Try to tolerate 0211, 02/11, 2/11, 2/2011, 2/20, 022001, 22001
            if ( elem.name == 'exp_date' ) {
                val = val.replace(/\D/g, '');
                val = val.replace(/^(\d?\d)20(\d\d)$/, '$1$2');
                if ( val.length == 3 ) val = '0' + val;
                elem.value = val;
            }
            validator = (utils.getAttr(elem, 'onvalidate') 
                         || forms.defaultValidators[elem.name]);
            compiled = validator && utils.compile(validator);
            if ( !compiled ) continue;
            err = compiled.apply(elem);
            // Validator may return false or a string error msg
            if ( typeof(err) == 'string' || !err ) {
                errors[elem.name + ':invalid'] = 
                    typeof(err) == 'string' 
                        ? err 
                        : forms.errorMessage(required[i] + ':invalid');
            }
    }
    
    if (utils.hasAnyProperties(errors)) {
        ak.errors = errors;
        ak.forms.onValidationErrors(errors);
        return false;
    }
    ak.errors = undefined;
    return true;
};

forms.clearErrors = function() {
    ak.errors = undefined;
    ak.form.className = ak.form.className.replace('contains-errors', '');
    var error_list = $id('ak-errors');
    if ( error_list ) error_list.innerHTML = '';
    $sel(':input.ak-error').each(function() {
        $(this).removeClass('ak-error')
    })
};

forms.onValidationErrors = function(errors) {
    if (ak.errors) forms.clearErrors();
    
    // Mark the controls bad
    var error_list = $id('ak-errors');
    for ( error in errors ) {
        if ( !errors.hasOwnProperty(error) ) continue;
        var li = document.createElement('li');
        li.innerHTML = errors[error];
        if ( error_list ) error_list.appendChild(li);
        
        error = error.split(':')[0];
        if (ak.form[error])
            ak.form[error].className += ' ak-error';
    }

    // Mark the labels, too
    var labels = ak.form.getElementsByTagName('label');
    for ( var i = 0; i < labels.length; ++i ) {
        var label = labels[i];
        var label_target = label.htmlFor && $id(label.htmlFor);
        if ( !label_target ) continue;
        if ( /\berror\b/.test( label_target.className ) )
            label.className += ' ak-error';
    }
    
    // Scroll up so errors are visible
    window.scrollTo(0,0);
    
    // Mark the form
    ak.form.className += ' contains-errors';
};

forms.timeout = 3000; // After 3 seconds, assume script tag isn't coming

forms.onTimeout = function() {
    forms.onContextLoaded({});
};

// Note this isn't called for TAF forms
forms.initPage = function() {
    ak.args = utils.getArgs();
    document.body.className += ' js';  // hides form
    // Disable funky back/forward caching
    if ( !window.onload ) window.onload = function() {};
    // Firebug Lite on IE if asked for
    if ( $.browser.msie && ak.args.debug ) {
        window.firebug = { env: {openInPopup: true, debug: true } };
        document.write("<script type='text/javascript' src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></scr" + "ipt>");
    }
};

forms.tryToValidate = function() {
    if ( ak.DEBUG ) {
        try {
            return forms.validate(); 
        }
        catch (e) {
            $log(e);
            return false;
        }
    }
    else {
        return forms.validate();
    }
}

// Set a validation hook, request context
forms.initForm = function(form_name) {
    ak.form = document.getElementsByName(form_name)[0];
    window.setTimeout(forms.onTimeout, 5000);
    if ( !ak.form.onsubmit ) 
        ak.form.onsubmit = forms.tryToValidate;
    if ( ak.args.prefill )
        forms.loadPrefiller();
    forms.loadContext();
};

// Set validation hook, insert akid/action_id, but don't
// request context for TAF forms
forms.initTafForm = function(form_name) {
    ak.args = utils.getArgs();

    // need to do this the hard way since getElementsByName will get
    // fooled in IE into picking up the <div id='taf'>
    var all_forms = document.getElementsByTagName('form');
    for(var i=0; i<all_forms.length; i++) {	 
        if(all_forms[i].getAttribute("name") == form_name) {
           ak.form = all_forms[i];
           break;
        }
    } 
          
    utils.appendHiddenInput('akid', ak.args.akid);
    utils.appendHiddenInput('action_id', ak.args.action_id);
    if ( !ak.form.onsubmit ) 
        ak.form.onsubmit = forms.tryToValidate;
    if ( ak.args.updated && $id('ak-confirmation') )
        $id('ak-confirmation').style.display = 'block';
    forms.loadContext();
}






utils.getById = $id;

utils.escapeForQueryString = function(str) { 
    esc = escape;
    if ( typeof(encodeURIComponent) != 'undefined' )
        esc = encodeURIComponent;
    return esc(str).replace(/\+/g, '%2B'); 
};

utils.makeQueryString = function(args) {
    if (!args) return '';
    var encoded = [];
    for ( key in args ) {
        if ( !args.hasOwnProperty(key) ) continue;
        var item = args[key];
        if ( typeof(item) == 'object' ) {
            for ( var i = 0; i < item.length; ++i )
                encoded.push(key + '=' + 
                    utils.escapeForQueryString(item[i]));
        }
        else { 
            encoded.push(key + '=' + 
                utils.escapeForQueryString(item));
        }
    }
    return encoded.join('&');
};

utils.getArgs = function() {
    var argsStr = window.location.search;
    var pairs = argsStr.replace(/^\?/, '').split('&');
    var args = {};
    unesc = unescape;
    if ( typeof(decodeURIComponent) != 'undefined' )
        unesc = decodeURIComponent;
    for ( var i = 0; i < pairs.length; ++i ) {
        pair = pairs[i].split('=');
        if (pair[0])
            args[unesc(pair[0].replace(/\+/g, ' '))] = 
                unesc(pair[1].replace(/\+/g, ' '));
    }
    return args;
} 

utils.div = document.createElement('div');

utils.makeHiddenInput = function(name, value) {
    // InnerHTML trick is needed to make MSIE work
    utils.div.innerHTML = '<input type="hidden" name="' + name + '" />';
    var input = utils.div.firstChild;
    input.value = value;
    return input;
};
    
utils.appendHiddenInput = function(name, value) {
    if (typeof(ak) != 'undefined' && typeof(ak.form) != 'undefined') {
        ak.form.appendChild(utils.makeHiddenInput(name, value))
    }
};
    
utils.makeSet = function(list) { 
    var s = {}; 
    for ( var i = 0; i < list.length; ++i ) 
        if ( typeof(list[i]) != 'undefined' )
            s[list[i]] = 1;
    return s;
}
    
utils.getAttr = function(element, attribute) {
    return ( element.attributes && element.attributes[attribute] )
        ? element.attributes[attribute].nodeValue
        : element[attribute];  // for Safari
}

utils.hasAnyProperties = function(o) {
    for ( property in o )
        if ( o.hasOwnProperty(property) )
            return true;
    return false;
}

utils.list = function(i) {
    if (typeof(i[0]) == "undefined")
        return [i];
    else return i;
}

utils.val = function(e) {
    if ( e.tagName.toLowerCase() == 'select' ) {
        if ( e.selectedIndex == -1 ) return undefined;
        var option = e.options[e.selectedIndex];
        return option.value || option.text;
    }
    else {
        return e.value;
    }
}

utils.compile = function(code, paramlist) {
    // "false ||" works around MSIE behavior
    if ( typeof(code) == 'function' ) return code;
    if ( !paramlist ) paramlist='';
    else return eval('false || function(' + paramlist + '){' + code + '}');
}

utils.capitalize = function(str) {
    return str.replace(/^(.)/, function(m) {return m.toUpperCase()} )
}

utils.add_commas = function(str, comma) {
    str = '' + str;
    if (!comma) comma = ',';
    while (/^([^\.\,]*\d)(\d{3})/.test(str))
        str = str.replace(/^([^\.\,]*\d)(\d{3})/, 
                          function(all, left, right) { 
                              return left + comma + right 
                          })
    return str;
}

// Simple printf-like '{0} is required', not to be confused with
// templating below
utils.format = function(str) {
    var format_args = arguments;
    var auto_number = 0;
    var arg_replacement = function(all, number) {
        // The lazy can say "{} is {}" instead of "{0} is {1}"
        if (number === '') number = auto_number++;
        return format_args[parseInt(number)+1]
    };
    return str.replace(/\{(\d*)\}/g, arg_replacement);
}

// Tweaked for ActionKit to use a more WYSIWYG-friendly syntax: 
// [%...%]
// And applied fix for single quotes from 
// http://plugins.jquery.com/node/3694
utils.template = 
// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
  
  this.tmpl = function (str, data){
    // Figure out if we're getting a template, or if we need to
    // load the template - and be sure to cache the result.
    var fn = !/\W/.test(str) ?
      cache[str] = cache[str] ||
        tmpl(document.getElementById(str).innerHTML) :
      
      // Generate a reusable function that will serve as a template
      // generator (and which will be cached).
      new Function("obj",
        "var p=[],print=function(){p.push.apply(p,arguments);};" +
        
        // Introduce the data as local variables using with(){}
        "with(obj){p.push('" +
        
        // Convert the template into pure JavaScript
        str
          .replace(/[\r\t\n]/g, " ")
          .split("\[%").join("\t")
          .replace(/(^|%\])[^\t]*/g, function(text){return text.replace(/['\\]/g, "\\$&")})
          .replace(/\t=(.*?)%\]/g, "',$1,'")
          .split("\t").join("');")
          .split("%\]").join("p.push('")
      + "');}return p.join('');");
    
    // Provide some basic currying to the user
    return data ? fn( data ) : fn;
  };
  
  return this.tmpl;
})();

})(window.actionkit, window.actionkit.utils, window.actionkit.forms);
