/*  
 *  (c) 2006 Michael Porter
 */

var toUtf8Codes = function(s) {
    var result = [];
    for (var i = 0; i < s.length;) {
        var c = decodeUtf16(s, i);
        var code = c[0];
        i += c[1];
        if (code < 0x80) {
            result.push(code);
        } else if (code < 0x800) {
            result.push((code >>> 6) | 0xC0);
            result.push((code & 0x3F) | 0x80);
        } else if (code <= 0xFFFF) {
            result.push((code >>> 12) | 0xE0);
            result.push(((code >>> 6) & 0x3F) | 0x80);
            result.push((code & 0x3F) | 0x80);
        } else if (code <= 0x1FFFFF) {
            result.push((code >>> 18) | 0xF0);
            result.push(((code >>> 12) & 0x3F) | 0x80);
            result.push(((code >>> 6) & 0x3F) | 0x80);
            result.push((code & 0x3F) | 0x80);
        } else {
            throw "Not supported";
        }
   }
   return result;
}

var InvalidUtf8Exception = function () {};

var fromUtf8Codes = function (codes) {
    var codes = [].concat(codes);
    codes.reverse();
    var getCode = function () {
        if (codes.length == 0) {
            throw new InvalidUtf8Exception;
        }
        return codes.pop();
    }
    var result = [];
    try {
        while (codes.length > 0) {
            var code0 = getCode();
            if (code0 < 0x80) {
                result.push(code0);
            } else {
                var code1 = getCode();
                if (code0 < 0xE0) {
                    result.push(((code0 & 0x1F) << 6) | (code1 & 0x3F));
                } else {
                    var code2 = getCode();
                    if (code0 < 0xF0) {
                        result.push(((code0 & 0xF) << 12) | ((code1 & 0x3F) << 6) | (code2 & 0x3F));
                    } else {
                        var code3 = getCode();
                        if (code0 < 0xF8) {
                            result.push(((code0 & 0x7) << 18) | ((code1 & 0x3F) << 12) | ((code2 & 0x3F) << 6) | (code3 & 0x3F));
                        } else {
                            throw new InvalidUtf8Exception();
                        }
                    }
                }
            }
        }
    } catch (e) {
        if (e instanceof InvalidUtf8Exception) {
            result = [];
        } else {
            throw e;
        }
    }
    return encodeUtf16(result);
}

var decodeUtf16 = function(s, idx) {
    var firstChar = s.charCodeAt(idx)
    if ((firstChar < 0xD800) || (firstChar > 0xDFFF)) {
        return [firstChar, 1];
    }
    var secondChar = s.charCodeAt(idx+1);
    return [
        ((((firstChar >>> 6) & 0xF) + 1) << 16) 
        | (firstChar & 0x3F) << 10
        | (secondChar & 0x3FF),
        2
    ]
}

var encodeUtf16 = function(codes) {
    var result = [];
    for (var i = 0; i < codes.length; i += 1) {
        var code = codes[i];
        if (code <= 0xFFFF) {
            result.push(String.fromCharCode(code));
        } else if (code <= 0x10FFFF) {
            result.push(String.fromCharCode(
                0xD800 | (((code >>> 16) & 0x1F) - 1) | ((code >>> 10) & 0x3F),
                0xDC00 | (code & 0x3FF)
            ));
        } else {
            return [];
        }
    }
    return result.join("");
}

var hexEncode = function(numCols, s) {
    var s = toUtf8Codes(s);
    var rows = [];
    for (var i = 0; i < s.length; i+= numCols) {
        rowData = s.slice(i, i+numCols);
        while (rowData.length < numCols) {
            rowData.push(0);
        }
        rows.push(rowData.map(toHexStr));
    }
    return rows;
}

var toHexStr = function(c) {
    var s = c.toString(16);
    if (s.length == 1) {
        s = "0" + s;
    }
    return s.toUpperCase();
}

var hexDecode = function (s) {
    var result = [];
    if ((s.length % 2) == 0) {
        for (var i = 0; i < s.length; i+= 2) {
            hexByte = s.slice(i, i+2);
            code = parseInt(hexByte, 16);
            if (isNaN(code)) {
                result = [];
                break;
            }
            result.push(code);
        }
    }
    return fromUtf8Codes(result).replace(/\0+$/,"")
}

var showDecoded = function (srcEl, destSetter) {
    var result = "";
    var s = $F(srcEl).replace(/[:\s]+/g, "");
    if (s.length > 0) {
        var decoded = hexDecode(s);
        result = decoded ? decoded : "<Not a valid message>";
    }
    destSetter(result)
}

var showPreview = function (destEl, data, numRows) {
    var rows = [];
    var yOffset = Math.max(0, Math.floor((numRows - data.length) / 2));
    while (yOffset-- > 0) {
        rows.push("");
    }
    rows = rows.concat(data).slice(0,numRows);
    Element.update(destEl, rows.join("<br/>"));
}

var createPreviewHandler = function (inputEl, previewEl, warnEl, options) {
    var numRows = options.numRows;
    var numCols = options.numCols;
    return function () {
        var text = $F(inputEl);
        var coded = hexEncode(numCols, text).invoke("join",":");
        showPreview(previewEl, coded, numRows);
        if (coded.length > numRows) {
            Element.removeClassName(warnEl, "hidden");
        } else {
            Element.addClassName(warnEl, "hidden");
        }
    }
} 

var installDecodeHandler = function (inputEl, outputEl) {
    var handler = function () {
        showDecoded(inputEl, function (v) {$(outputEl).value=v});
    }
    Event.observe(inputEl, "keyup", handler, false);
    return handler;
}

var installPreviewHandler = function (inputEl, previewEl, warnEl, options) {
    var handler = createPreviewHandler(inputEl, previewEl, warnEl, options);
    Event.observe(inputEl, "keyup", handler, false);
    return handler;
} 
