/* -*- Mode: java; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is the Grendel mail/news client.
 *
 * The Initial Developer of the Original Code is Netscape Communications
 * Corporation.  Portions created by Netscape are Copyright (C) 1997
 * Netscape Communications Corporation.  All Rights Reserved.
 *
 * Created: Jamie Zawinski <jwz@netscape.com>, 28 Aug 1997.
 */

package grendel.mime.encoder;

import calypso.util.ByteBuf;
import calypso.util.Assert;

/** Implements a plaintext -> Quoted-Printable encoder.
 */
public class MimeQuotedPrintableEncoder extends MimeEncoder {

  boolean had_whitespace = false;
  int output_column = 0;
  byte last_code = NUL;

  static private final byte NUL = 0;   // don't encode
  static private final byte ESC = 1;   // always encode
  static private final byte SPC = 2;   // horizontal whitespace
  static private final byte BOL = 3;   // encode if at beginning of line
  static private final byte CR  = 4;   // carriage return
  static private final byte LF  = 5;   // linefeed

  static private final byte map[] = {
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   000-007
    ESC, SPC,  LF, ESC, ESC,  CR, ESC, ESC,      //   010-017
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   020-027
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   030-037
    SPC, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   040-047   !"#$%&'
    NUL, NUL, NUL, NUL, NUL, NUL, BOL, NUL,      //   050-057  ()*+,-./
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   060-067  01234567
    NUL, NUL, NUL, NUL, NUL, ESC, NUL, NUL,      //   070-077  89:;<=>?

    NUL, NUL, NUL, NUL, NUL, NUL, BOL, NUL,      //   100-107  @ABCDEFG
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   110-117  HIJKLMNO
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   120-127  PQRSTUVW
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   130-137  XYZ[\]^_
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   140-147  `abcdefg
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   150-157  hijklmno
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, NUL,      //   160-167  pqrstuvw
    NUL, NUL, NUL, NUL, NUL, NUL, NUL, ESC,      //   170-177  xyz{|}~

    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   200-207
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   210-217
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   220-227
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   230-237
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   240-247
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   250-257
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   260-267
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   270-277

    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   300-307
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   310-317
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   320-327
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   330-337
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   340-347
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   350-357
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   360-367
    ESC, ESC, ESC, ESC, ESC, ESC, ESC, ESC,      //   370-377
  };

  static private final byte hex[] = {
    '0', '1', '2', '3', '4', '5', '6', '7',
    '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
  };

  static private final byte         crlf[] =      "\r\n".getBytes();
  static private final byte      eq_crlf[] =     "=\r\n".getBytes();
  static private final byte eq_crlf_crlf[] = "=\r\n\r\n".getBytes();


  /** Given a sequence of input bytes, produces a sequence of output bytes
      using the quoted-printable encoding.  If there are bytes in `out'
      already, the new bytes are appended, so the caller should do
      `out.setLength(0)' first if that's desired.
   */
  public final void translate(ByteBuf in, ByteBuf out) {

    byte inb[] = in.toBytes();
    int in_length = in.length();

    for (int i = 0; i < in_length; i++) {
      byte b = inb[i];
      byte c = map[b];

      if (c == NUL) {                           // non-special character
        out.append(b);
        output_column++;
        had_whitespace = false;

      } else if (c == SPC) {                    // whitespace
        out.append(b);
        output_column++;
        had_whitespace = true;

      } else if (c == LF && last_code == CR) {
        // We already processed this linebreak;
        // ignore the LF in the CRLF pair.

      } else if (c == CR || c == LF) {          // start of linebreak

        if (had_whitespace) {
          // Whitespace cannot be allowed to occur at the end of the line.
          // So we encode " \n" as " =\n\n", that is, the whitespace, a
          // soft line break, and then a hard line break.
          //
          out.append(eq_crlf_crlf);
          had_whitespace = false;
        } else {
          out.append(crlf);
        }
        output_column = 0;

      } else if (c == BOL && output_column != 0) {      // beginning-of-line
        out.append(b);                                  // special, while not
        output_column++;                                // at beginning-of-line
        had_whitespace = false;

      } else {                                  // special character
        out.append((byte) '=');
        out.append((byte) hex[0xF & (b >> 4)]);
        out.append((byte) hex[0xF & b]);
        output_column += 3;
        had_whitespace = false;
      }

      last_code = c;

      Assert.Assertion(output_column <= 76);

      if (output_column >= 73) {
        out.append(eq_crlf);
        output_column = 0;
        had_whitespace = false;
      }
    }
  }

  /** Tell the quoted-printable encoder that no more input data will be
      forthcoming.  This may result in output, as a result of flushing the
      internal buffer.  This object must not be used again after calling eof().
      If there are bytes in `out' already, the new bytes are appended,
      so the caller should do `out.setLength(0)' first if that's desired.
   */
  public final void eof(ByteBuf out) {
    if (output_column != 0) {
      out.append(eq_crlf);
      output_column = 0;
      had_whitespace = false;
    }
  }
}
