// Javascript mailto_uri_parser object
//
// Copyright: Michael A. Puls II 2009
// Distributed under the Boost Software License, Version 1.0. <http://boost.org/LICENSE_1_0.txt>
// Documentation: <http://shadow2531.com/js/mailto_uri_parser_doc.txt>
// Contact: <mailto:Michael%20A.%20Puls%20II%20%3Cshadow2531%40gmail.com%3E?subject=Javascript%20mailto_uri_parser%20object>

var mailto_uri_parser = {
    revsion : 1,
    utils : {
        trim : function trim(s) {
            return s.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
        },
        escapeInvalidHH : function(s) {
            return s.replace(/%(?![0-9A-F]{2})/gi, function() {
                return "%25";
            });
        },
        escapeUnsafeHH : function(s) {
            return s.replace(/%(00|01|02|03|04|05|06|07|08|0B|0C|0B|0C|0E|0F|10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F)/gi, function(match, hh) {
                return "%25" + hh;
            });
        },
        escapeUnsafeRaw : function(s) {
            return s.replace(/[\x00-\x08]|[\x0B-\x0C]|[\x0E-\x1F]/g, function(s) {
                try {
                    return encodeURIComponent(s);
                } catch(e) {
                    return "";
                }
            });
        },
        normalizeNewlinesToCRLF : function(s) {
            return s.replace(/\r\n|\r|\n/g, "\r\n");
        },
        normalizeNewlinesToLF : function(s) {
            return s.replace(/\r\n|\r/g, "\n");
        },
        encode : function(s) {
            try {
                return encodeURIComponent(this.escapeUnsafeRaw(this.normalizeNewlinesToCRLF(s)));
            } catch (e) {
                return "percent%20encode%20error";
            }
        },
        encodeAlreadyEncoded : function(s) {
            try {
                return encodeURIComponent(this.escapeUnsafeRaw(s));
            } catch(e) {
                return "percent-encode%2520error";
            }
        },
        decode : function(s) {
            try {
                return this.normalizeNewlinesToCRLF(this.escapeUnsafeRaw(decodeURIComponent(this.escapeUnsafeHH(this.escapeInvalidHH(s)))));
            } catch(e) {
                return "percent-decode error";
            }
        },
        filterNewlines : function(s) {
            return s.replace(/\r|\n/g, "");
        },
        normalizeEncoding : function(s) {
            return this.encode(this.decode(s));
        },
        createDatasetFromMailtoURI : function(uri) {
            uri = this.trim(uri);
            if (uri.search(/mailto:/i) != 0) {
                throw "createDatasetFromURI: uri argument was not a mailto URI";
            }
            uri = "to=" + uri.substr(7);
            var qm = uri.indexOf('?');
            if (qm != -1) {
                var query = uri.substr(qm + 1);
                uri = uri.substring(0, qm).replace(/&/g, "%26");
                if (query != "") {
                    uri += "&" + query;
                }
            } else {
                uri = uri.replace(/&/g, "%26");
            }
            return uri
        },
        getEncodedValueFromKeyMap : function(map, name) {
            return this.encode(this.getValueFromKeyMap(map, name));
        },
        getValueFromKeyMap : function(map, name) {
            map = this.convertKeyMapNamesToLowerCase(map);
            name = name.toLowerCase();
            return map.hasOwnProperty(name) ? map[name] : "";
        },
        cleanKeyMapOfEmptyValues : function(map) {
            var clean_map = {};
            for (var i in map) {
                if (map.hasOwnProperty(i)) {
                    if (i.length > 0 && map[i].length > 0) {
                        clean_map[i] = map[i];
                    }
                }
            }
            return clean_map;
        },
        convertKeyMapNamesToLowerCase : function(map) {
            var lc = {};
            for (var i in map) {
                if (map.hasOwnProperty(i)) {
                    lc[i.toLowerCase()] = map[i];
                }
            }
            return lc;
        },
        createURIFromKeyMap : function(map) {
            map = this.convertKeyMapNamesToLowerCase(map);
            var uri = "mailto:";
            if (map.hasOwnProperty("to")) {
                uri += this.encode(map["to"]);
            }
            var added_qm = false;
            var need_amp = false;
            for (var i in map) {
                if (map.hasOwnProperty(i)) {
                    if (i != "to" && i.length > 0 && map[i].length > 0) {
                        if (!added_qm) {
                            uri += '?';
                            added_qm = true;
                        }
                        if (need_amp) {
                            uri += '&';
                        }
                        uri += this.encode(i);
                        uri += '=';
                        uri += this.encode(map[i]);
                        need_amp = true;
                    }
                }
            }
            return uri;
        },
        normalizeMailtoURI : function(uri) {
            return this.createURIFromKeyMap(mailto_uri_parser.parse(uri));
        },
        normalizeMailtoURISpecial : function(uri, typeList) {
            return this.createURIFromKeyMap(mailto_uri_parser.parseSpecial(uri, typeList));
        },
        formatStringFromKeyMap : function(map, s, encoded_replacements) {
            if (typeof encoded_replacements != "boolean") {
                encoded_replacements = true;
            }
            map = this.convertKeyMapNamesToLowerCase(map);
            var ret = "";
            var uri = this.createURIFromKeyMap(map);
            for (var i = 0; i < s.length; ++i) {
                var c = s.charAt(i);
                var next = i + 1;
                if (c == '[' && next != s.length) {
                    if (s.charAt(next) == '[') {
                        ret += '[';
                        ++i;
                    } else {
                        var bracket_value = "";
                        var z = next;
                        while (z != s.length) {
                            var after = z + 1;
                            if (s.charAt(z) == '\\' && after != s.length) {
                                if (s.charAt(after) == '\\') {
                                    bracket_value += '\\';
                                } else if (s.charAt(after) == '[') {
                                    bracket_value += '[';
                                } else if (s.charAt(after) == ']') {
                                    bracket_value += ']';
                                } else {
                                    bracket_value += '\\';
                                    bracket_value += s.charAt(after);
                                }
                            } else {
                                if (s.charAt(z) == ']') {
                                    break;
                                }
                                bracket_value += s.charAt(z);
                            }
                            ++z;
                        }
                        bracket_value = bracket_value.toLowerCase();
                        if (map.hasOwnProperty(bracket_value) && typeof map[bracket_value] == "string") {
                            if (encoded_replacements) {
                                ret += this.getEncodedValueFromKeyMap(map, bracket_value);
                            } else {
                                ret += this.getValueFromKeyMap(map, bracket_value);
                            }
                        }
                        i = z;
                    }
                } else if (c == '%' && next != s.length) {
                    if (s.charAt(next) == '[') {
                        ret += '%';
                    } else if (s.charAt(next) == 's') {
                        if (encoded_replacements) {
                            ret += this.encodeAlreadyEncoded(uri);
                        } else {
                            ret += uri;
                        }
                        ++i;
                    } else if (s.charAt(next) == '%') {
                        ret += '%';
                        ++i;
                    } else {
                        ret += '%';
                        ret += s.charAt(next);
                        ++i;
                    }
                } else {
                    ret += c;
                }
            }
            return ret;
        }
    },
    parse : function(uri) {
        var no_newlines = ["to", "cc", "bcc", "subject"];
        var dataset = this.utils.createDatasetFromMailtoURI(uri);
        var keymap = {};
        var ref = this;
        dataset.replace(/([^=&]+)=([^&]*)/g, function(match, hname, hvalue) {
            hname = ref.utils.decode(hname).toLowerCase();
            if (hname.length > 0) {
                hvalue = ref.utils.decode(hvalue);
                if (no_newlines.indexOf(hname) != -1) {
                    hvalue = ref.utils.filterNewlines(hvalue);
                }
                keymap[hname] = hvalue;
            }
        });
        return this.utils.cleanKeyMapOfEmptyValues(keymap)
    },
    parseSpecial : function(uri, typeList) {
        if (typeof typeList != "object") {
            typeList = {
                "to" : ["address", true],
                "cc" : ["address", true],
                "bcc" : ["address", true],
                "subject" : ["single-line", true],
                "body" : ["multi-line", false]
            };
        }
        for (var i in typeList) {
            if (typeList.hasOwnProperty(i)) {
                if (typeof i != "string" || typeof typeList[i] != "object" || typeList[i].length != 2 || typeof typeList[i][0] != "string" || typeof typeList[i][1] != "boolean") {
                    throw "parseSpecial: typeList argument format was incorrect.";
                }
            }
        }
        var dataset = this.utils.createDatasetFromMailtoURI(uri);
        var keymap = {};
        var ref = this;
        dataset.replace(/([^=&]+)=([^&]*)/g, function(match, hname, hvalue) {
            hname = ref.utils.decode(hname).toLowerCase();
            if (hname.length > 0) {
                hvalue = ref.utils.decode(hvalue);
                if (typeList.hasOwnProperty(hname)) {
                    if (typeList[hname][1] == true) {
                        hvalue = ref.utils.filterNewlines(hvalue);
                    }
                    var type = typeList[hname][0];
                    if (type == "address") {
                        if (hvalue.length > 0) {
                            if (!keymap.hasOwnProperty(hname)) {
                                keymap[hname] = hvalue;
                            } else {
                                if (keymap[hname].length > 0) {
                                    keymap[hname] += ", ";
                                }
                                keymap[hname] += hvalue;
                            }
                        }
                    } else if (type == "multi-line") {
                        if (!(hvalue.length == 0 && (!keymap.hasOwnProperty(hname) || keymap[hname].length == 0))) {
                            if (!keymap.hasOwnProperty(hname)) {
                                keymap[hname] = hvalue;
                            } else {
                                if (keymap[hname].length > 0) {
                                    keymap[hname] += "\r\n";
                                }
                                keymap[hname] += hvalue;
                            }
                        }
                    } else {
                        keymap[hname] = hvalue;
                    }
                } else {
                    keymap[hname] = hvalue;
                }
            }
        });
        return this.utils.cleanKeyMapOfEmptyValues(keymap);
    }
};
