/*
 * @(#)Uuencode.java
 *
 * Copyright (c) 2001-2002 JScape
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JScape. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JScape.
 */
// CHECKSTYLE:OFF
package com.jscape.inet.util;


/**
 * Implements methods for encoding and decoding using uuencode algorithm.
 */
public class Uuencode
        implements Codec {

    private static char[] encmap;
    private static byte[] decmap;

    static {
        // uuencode'ing maps (TBD - locale specific encoding)
        encmap = new char[64];
        for (int idx = 0; idx < encmap.length; idx++) {
            encmap[idx] = (char) (idx + 0x20);
        }
        decmap = new byte[128];    //  TBD
        for (int idx = 0; idx < encmap.length; idx++)
            decmap[encmap[idx]] = (byte) idx;
    }

    /**
     * Encodes data to uuencode format.
     *
     * @param contents the data to encode
     *
     * @return encoded data
     */
    public char[] encode(byte[] contents) {
        if (contents == null) return null;
        if (contents.length == 0) return new char[0];

        int line_len = 45;    // line length, in octets

        int sidx, didx;
        char[] nl = System.getProperty("line.separator", "\n").toCharArray();
        char[] dest = new char[(contents.length + 2) / 3 * 4 +
                ((contents.length + line_len - 1) / line_len) * (nl.length + 1)];

        // split into lines, adding line-length and line terminator
        for (sidx = 0, didx = 0; sidx + line_len < contents.length; ) {
            // line length
            dest[didx++] = encmap[line_len];

            // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
            for (int end = sidx + line_len; sidx < end; sidx += 3) {
                dest[didx++] = encmap[(contents[sidx] >>> 2) & 077];
                dest[didx++] = encmap[(contents[sidx + 1] >>> 4) & 017 |
                        (contents[sidx] << 4) & 077];
                dest[didx++] = encmap[(contents[sidx + 2] >>> 6) & 003 |
                        (contents[sidx + 1] << 2) & 077];
                dest[didx++] = encmap[contents[sidx + 2] & 077];
            } // line terminator
            for (int idx = 0; idx < nl.length; idx++) dest[didx++] = nl[idx];
        } // last line
        // line length
        dest[didx++] = encmap[contents.length - sidx];

        // 3-byte to 4-byte conversion + 0-63 to ascii printable conversion
        for (; sidx + 2 < contents.length; sidx += 3) {
            dest[didx++] = encmap[(contents[sidx] >>> 2) & 077];
            dest[didx++] = encmap[(contents[sidx + 1] >>> 4) & 017 |
                    (contents[sidx] << 4) & 077];
            dest[didx++] = encmap[(contents[sidx + 2] >>> 6) & 003 |
                    (contents[sidx + 1] << 2) & 077];
            dest[didx++] = encmap[contents[sidx + 2] & 077];
        }
        if (sidx < contents.length - 1) {
            dest[didx++] = encmap[(contents[sidx] >>> 2) & 077];
            dest[didx++] = encmap[(contents[sidx + 1] >>> 4) & 017 |
                    (contents[sidx] << 4) & 077];
            dest[didx++] = encmap[(contents[sidx + 1] << 2) & 077];
            dest[didx++] = encmap[0];
        } else if (sidx < contents.length) {
            dest[didx++] = encmap[(contents[sidx] >>> 2) & 077];
            dest[didx++] = encmap[(contents[sidx] << 4) & 077];
            dest[didx++] = encmap[0];
            dest[didx++] = encmap[0];
        } // line terminator
        for (int idx = 0; idx < nl.length; idx++) dest[didx++] = nl[idx];

        // check if everything went fine...
        // debug only, remove for release
        if (didx != dest.length)
            throw new Error("Calculated " + dest.length + " chars but wrote " + didx + " chars!");

        return dest;
    }

    /**
     * Decodes data from uuencoded format.
     *
     * @param contents the data to decode
     *
     * @return decoded data
     */
    public byte[] decode(char[] contents) {
        if (contents == null) return null;

        int sidx, didx;
        byte[] dest = new byte[contents.length / 4 * 3];

        for (sidx = 0, didx = 0; sidx < contents.length; ) {
            // get line length (in number of encoded octets)
            int len = decmap[contents[sidx++]];

            // ascii printable to 0-63 and 4-byte to 3-byte conversion
            int end = didx + len;

            for (; didx + 2 < end; sidx += 4) {
                byte A = decmap[contents[sidx]];
                byte B = decmap[contents[sidx + 1]];
                byte C = decmap[contents[sidx + 2]];
                byte D = decmap[contents[sidx + 3]];

                dest[didx++] = (byte) (((A << 2) & 255) | ((B >>> 4) & 003));
                dest[didx++] = (byte) (((B << 4) & 255) | ((C >>> 2) & 017));
                dest[didx++] = (byte) (((C << 6) & 255) | (D & 077));
            }
            if (didx < end) {
                byte A = decmap[contents[sidx]],
                        B = decmap[contents[sidx + 1]];

                dest[didx++] = (byte) (((A << 2) & 255) | ((B >>> 4) & 003));
            }
            if (didx < end) {
                byte B = decmap[contents[sidx + 1]],
                        C = decmap[contents[sidx + 2]];

                dest[didx++] = (byte) (((B << 4) & 255) | ((C >>> 2) & 017));
            } // skip padding
            while (sidx < contents.length && contents[sidx] != '\n' && contents[sidx] != '\r') {
                sidx++;
            }

            // skip end of line
            while (sidx < contents.length && (contents[sidx] == '\n' || contents[sidx] == '\r')) {
                sidx++;
            }
        }
        byte[] tmp = new byte[didx];

        System.arraycopy(dest, 0, tmp, 0, didx);
        return tmp;
    }
}

