/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "nsUnicodeToUTF7.h"
#include <string.h>

//----------------------------------------------------------------------
// Global functions and data [declaration]

#define ENC_DIRECT 0
#define ENC_BASE64 1

//----------------------------------------------------------------------
// Class nsBasicUTF7Encoder [implementation]

nsBasicUTF7Encoder::nsBasicUTF7Encoder(char aLastChar, char aEscChar) {
  mLastChar = aLastChar;
  mEscChar = aEscChar;
  Reset();
}

nsresult nsBasicUTF7Encoder::ShiftEncoding(int32_t aEncoding, char* aDest,
                                           int32_t* aDestLength) {
  if (aEncoding == mEncoding) {
    *aDestLength = 0;
    return NS_OK;
  }

  nsresult res = NS_OK;
  char* dest = aDest;
  char* destEnd = aDest + *aDestLength;

  if (mEncStep != 0) {
    if (dest >= destEnd) return NS_OK_UENC_MOREOUTPUT;
    *(dest++) = ValueToChar(mEncBits);
    mEncStep = 0;
    mEncBits = 0;
  }

  if (dest >= destEnd) {
    res = NS_OK_UENC_MOREOUTPUT;
  } else {
    switch (aEncoding) {
      case 0:
        *(dest++) = '-';
        mEncStep = 0;
        mEncBits = 0;
        break;
      case 1:
        *(dest++) = mEscChar;
        break;
    }
    mEncoding = aEncoding;
  }

  *aDestLength = dest - aDest;
  return res;
}

nsresult nsBasicUTF7Encoder::EncodeDirect(const char16_t* aSrc,
                                          int32_t* aSrcLength, char* aDest,
                                          int32_t* aDestLength) {
  nsresult res = NS_OK;
  const char16_t* src = aSrc;
  const char16_t* srcEnd = aSrc + *aSrcLength;
  char* dest = aDest;
  char* destEnd = aDest + *aDestLength;
  char16_t ch;

  while (src < srcEnd) {
    ch = *src;

    // stop when we reach Unicode chars
    if (!DirectEncodable(ch)) break;

    if (ch == mEscChar) {
      // special case for the escape char
      if (destEnd - dest < 1) {
        res = NS_OK_UENC_MOREOUTPUT;
        break;
      } else {
        *dest++ = (char)ch;
        *dest++ = (char)'-';
        src++;
      }
    } else {
      // classic direct encoding
      if (dest >= destEnd) {
        res = NS_OK_UENC_MOREOUTPUT;
        break;
      } else {
        *dest++ = (char)ch;
        src++;
      }
    }
  }

  *aSrcLength = src - aSrc;
  *aDestLength = dest - aDest;
  return res;
}

nsresult nsBasicUTF7Encoder::EncodeBase64(const char16_t* aSrc,
                                          int32_t* aSrcLength, char* aDest,
                                          int32_t* aDestLength) {
  nsresult res = NS_OK;
  const char16_t* src = aSrc;
  const char16_t* srcEnd = aSrc + *aSrcLength;
  char* dest = aDest;
  char* destEnd = aDest + *aDestLength;
  char16_t ch;
  uint32_t value;

  while (src < srcEnd) {
    ch = *src;

    // stop when we reach printable US-ASCII chars
    if (DirectEncodable(ch)) break;

    switch (mEncStep) {
      case 0:
        if (destEnd - dest < 2) {
          res = NS_OK_UENC_MOREOUTPUT;
          break;
        }
        value = ch >> 10;
        *(dest++) = ValueToChar(value);
        value = (ch >> 4) & 0x3f;
        *(dest++) = ValueToChar(value);
        mEncBits = (ch & 0x0f) << 2;
        break;
      case 1:
        if (destEnd - dest < 3) {
          res = NS_OK_UENC_MOREOUTPUT;
          break;
        }
        value = mEncBits + (ch >> 14);
        *(dest++) = ValueToChar(value);
        value = (ch >> 8) & 0x3f;
        *(dest++) = ValueToChar(value);
        value = (ch >> 2) & 0x3f;
        *(dest++) = ValueToChar(value);
        mEncBits = (ch & 0x03) << 4;
        break;
      case 2:
        if (destEnd - dest < 3) {
          res = NS_OK_UENC_MOREOUTPUT;
          break;
        }
        value = mEncBits + (ch >> 12);
        *(dest++) = ValueToChar(value);
        value = (ch >> 6) & 0x3f;
        *(dest++) = ValueToChar(value);
        value = ch & 0x3f;
        *(dest++) = ValueToChar(value);
        mEncBits = 0;
        break;
    }

    if (res != NS_OK) break;

    src++;
    (++mEncStep) %= 3;
  }

  *aSrcLength = src - aSrc;
  *aDestLength = dest - aDest;
  return res;
}

char nsBasicUTF7Encoder::ValueToChar(uint32_t aValue) {
  if (aValue < 26)
    return (char)('A' + aValue);
  else if (aValue < 26 + 26)
    return (char)('a' + aValue - 26);
  else if (aValue < 26 + 26 + 10)
    return (char)('0' + aValue - 26 - 26);
  else if (aValue == 26 + 26 + 10)
    return '+';
  else if (aValue == 26 + 26 + 10 + 1)
    return mLastChar;
  else
    return -1;
}

bool nsBasicUTF7Encoder::DirectEncodable(char16_t aChar) {
  // spec says: printable US-ASCII chars
  if ((aChar >= 0x20) && (aChar <= 0x7e))
    return true;
  else
    return false;
}

//----------------------------------------------------------------------
// Subclassing of nsEncoderSupport class [implementation]

NS_IMETHODIMP nsBasicUTF7Encoder::ConvertNoBuffNoErr(const char16_t* aSrc,
                                                     int32_t* aSrcLength,
                                                     char* aDest,
                                                     int32_t* aDestLength) {
  nsresult res = NS_OK;
  const char16_t* src = aSrc;
  const char16_t* srcEnd = aSrc + *aSrcLength;
  char* dest = aDest;
  char* destEnd = aDest + *aDestLength;
  int32_t bcr, bcw;
  char16_t ch;
  int32_t enc;

  while (src < srcEnd) {
    // find the encoding for the next char
    ch = *src;
    if (DirectEncodable(ch))
      enc = ENC_DIRECT;
    else
      enc = ENC_BASE64;

    // if necessary, shift into the required encoding
    bcw = destEnd - dest;
    res = ShiftEncoding(enc, dest, &bcw);
    dest += bcw;
    if (res != NS_OK) break;

    // now encode (as much as you can)
    bcr = srcEnd - src;
    bcw = destEnd - dest;
    if (enc == ENC_DIRECT)
      res = EncodeDirect(src, &bcr, dest, &bcw);
    else
      res = EncodeBase64(src, &bcr, dest, &bcw);
    src += bcr;
    dest += bcw;

    if (res != NS_OK) break;
  }

  *aSrcLength = src - aSrc;
  *aDestLength = dest - aDest;
  return res;
}

NS_IMETHODIMP nsBasicUTF7Encoder::FinishNoBuff(char* aDest,
                                               int32_t* aDestLength) {
  return ShiftEncoding(ENC_DIRECT, aDest, aDestLength);
}

NS_IMETHODIMP nsBasicUTF7Encoder::Reset() {
  mEncoding = ENC_DIRECT;
  mEncBits = 0;
  mEncStep = 0;
  return NS_OK;
}

//----------------------------------------------------------------------
// Class nsUnicodeToUTF7 [implementation]

nsUnicodeToUTF7::nsUnicodeToUTF7() : nsBasicUTF7Encoder('/', '+') {}

bool nsUnicodeToUTF7::DirectEncodable(char16_t aChar) {
  if ((aChar >= 'A') && (aChar <= 'Z'))
    return true;
  else if ((aChar >= 'a') && (aChar <= 'z'))
    return true;
  else if ((aChar >= '0') && (aChar <= '9'))
    return true;
  else if ((aChar >= 39) && (aChar <= 41))
    return true;
  else if ((aChar >= 44) && (aChar <= 47))
    return true;
  else if (aChar == 58)
    return true;
  else if (aChar == 63)
    return true;
  else if (aChar == ' ')
    return true;
  else if (aChar == 9)
    return true;
  else if (aChar == 13)
    return true;
  else if (aChar == 10)
    return true;
  else if (aChar == 60)
    return true;  // '<'
  else if (aChar == 33)
    return true;  // '!'
  else if (aChar == 34)
    return true;  // '"'
  else if (aChar == 62)
    return true;  // '>'
  else if (aChar == 61)
    return true;  // '='
  else if (aChar == 59)
    return true;  // ';'
  else if (aChar == 91)
    return true;  // '['
  else if (aChar == 93)
    return true;  // ']'
  else
    return false;
}
