/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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 "ImportOutFile.h"
#include "nsImportTranslator.h"

#include "ImportCharSet.h"


bool nsImportTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
{
  if (pProcessed)
    *pProcessed = inLen;
  return (pOutFile->WriteData(pIn, inLen));
}

void CMHTranslator::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut)
{
  while (inLen) {
    if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
      (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') ||
      (*pIn == '%')) {
      // needs to be encode as %hex val
      *pOut = '%'; pOut++;
      ImportCharSet::ByteToHex(*pIn, pOut);
      pOut += 2;
    }
    else {
      *pOut = *pIn;
      pOut++;
    }
    pIn++; inLen--;
  }
  *pOut = 0;
}

bool CMHTranslator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
{
  uint8_t    hex[2];
  while (inLen) {
    if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
      (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') || (*pIn == '\'') ||
      (*pIn == '%')) {
      // needs to be encode as %hex val
      if (!pOutFile->WriteByte('%'))
        return false;
      ImportCharSet::ByteToHex(*pIn, hex);
      if (!pOutFile->WriteData(hex, 2))
        return false;
    }
    else {
      if (!pOutFile->WriteByte(*pIn))
        return false;
    }
    pIn++; inLen--;
  }

  if (pProcessed)
    *pProcessed = inLen;

  return true;
}


bool C2047Translator::ConvertToFileQ(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
{
  if (!inLen)
    return true;

  int    maxLineLen = 64;
  int    curLineLen = m_startLen;
  bool    startLine = true;

  uint8_t  hex[2];
  while (inLen) {
    if (startLine) {
      if (!pOutFile->WriteStr(" =?"))
        return false;
      if (!pOutFile->WriteStr(m_charset.get()))
        return false;
      if (!pOutFile->WriteStr("?q?"))
        return false;
      curLineLen += (6 + m_charset.Length());
      startLine = false;
    }

    if (!ImportCharSet::IsUSAscii(*pIn) || ImportCharSet::Is822SpecialChar(*pIn) || ImportCharSet::Is822CtlChar(*pIn) ||
      (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '?') || (*pIn == '=')) {
      // needs to be encode as =hex val
      if (!pOutFile->WriteByte('='))
        return false;
      ImportCharSet::ByteToHex(*pIn, hex);
      if (!pOutFile->WriteData(hex, 2))
        return false;
      curLineLen += 3;
    }
    else {
      if (!pOutFile->WriteByte(*pIn))
        return false;
      curLineLen++;
    }
    pIn++; inLen--;
    if (curLineLen > maxLineLen) {
      if (!pOutFile->WriteStr("?="))
        return false;
      if (inLen) {
        if (!pOutFile->WriteStr("\x0D\x0A "))
          return false;
      }

      startLine = true;
      curLineLen = 0;
    }
  }

  if (!startLine) {
    // end the encoding!
    if (!pOutFile->WriteStr("?="))
      return false;
  }

  if (pProcessed)
    *pProcessed = inLen;

  return true;
}

bool C2047Translator::ConvertToFile(const uint8_t * pIn, uint32_t inLen, ImportOutFile *pOutFile, uint32_t *pProcessed)
{
  if (m_useQuotedPrintable)
    return ConvertToFileQ(pIn, inLen, pOutFile, pProcessed);

  if (!inLen)
    return true;

  int      maxLineLen = 64;
  int      curLineLen = m_startLen;
  bool      startLine = true;
  int      encodeMax;
  uint8_t *  pEncoded = new uint8_t[maxLineLen * 2];

  while (inLen) {
    if (startLine) {
      if (!pOutFile->WriteStr(" =?")) {
        delete [] pEncoded;
        return false;
      }
      if (!pOutFile->WriteStr(m_charset.get())) {
        delete [] pEncoded;
        return false;
      }
      if (!pOutFile->WriteStr("?b?")) {
        delete [] pEncoded;
        return false;
      }
      curLineLen += (6 + m_charset.Length());
      startLine = false;
    }
    encodeMax = maxLineLen - curLineLen;
    encodeMax *= 3;
    encodeMax /= 4;
    if ((uint32_t)encodeMax > inLen)
      encodeMax = (int)inLen;

    // encode the line, end the line
    // then continue. Update curLineLen, pIn, startLine, and inLen
    UMimeEncode::ConvertBuffer(pIn, encodeMax, pEncoded, maxLineLen, maxLineLen, "\x0D\x0A");

    if (!pOutFile->WriteStr((const char *)pEncoded)) {
      delete [] pEncoded;
      return false;
    }

    pIn += encodeMax;
    inLen -= encodeMax;
    startLine = true;
    curLineLen = 0;
    if (!pOutFile->WriteStr("?=")) {
      delete [] pEncoded;
      return false;
    }
    if (inLen) {
      if (!pOutFile->WriteStr("\x0D\x0A ")) {
        delete [] pEncoded;
        return false;
      }
    }
  }

  delete [] pEncoded;

  if (pProcessed)
    *pProcessed = inLen;

  return true;
}


uint32_t  UMimeEncode::GetBufferSize(uint32_t inBytes)
{
  // it takes 4 base64 bytes to represent 3 regular bytes
  inBytes += 3;
  inBytes /= 3;
  inBytes *= 4;
  // This should be plenty, but just to be safe
  inBytes += 4;

  // now allow for end of line characters
  inBytes += ((inBytes + 39) / 40) * 4;

  return inBytes;
}

static uint8_t gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

uint32_t UMimeEncode::ConvertBuffer(const uint8_t * pIn, uint32_t inLen, uint8_t * pOut, uint32_t maxLen, uint32_t firstLineLen, const char * pEolStr)
{

  uint32_t  pos = 0;
  uint32_t  len = 0;
  uint32_t  lineLen = 0;
  uint32_t  maxLine = firstLineLen;
  int  eolLen = 0;
  if (pEolStr)
    eolLen = strlen(pEolStr);

  while ((pos + 2) < inLen) {
    // Encode 3 bytes
    *pOut = gBase64[*pIn >> 2];
    pOut++; len++; lineLen++;
    *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
    pIn++; pOut++; len++; lineLen++;
    *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)];
    pIn++; pOut++; len++; lineLen++;
    *pOut = gBase64[(*pIn) & 0x3F];
    pIn++; pOut++; len++; lineLen++;
    pos += 3;
    if (lineLen >= maxLine) {
      lineLen = 0;
      maxLine = maxLen;
      if (pEolStr) {
        memcpy(pOut, pEolStr, eolLen);
        pOut += eolLen;
        len += eolLen;
      }
    }
  }

  if ((pos < inLen) && ((lineLen + 3) > maxLine)) {
    lineLen = 0;
    maxLine = maxLen;
    if (pEolStr) {
      memcpy(pOut, pEolStr, eolLen);
      pOut += eolLen;
      len += eolLen;
    }
  }

  if (pos < inLen) {
    // Get the last few bytes!
    *pOut = gBase64[*pIn >> 2];
    pOut++; len++;
    pos++;
    if (pos < inLen) {
      *pOut = gBase64[(((*pIn) & 0x3)<< 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
      pIn++; pOut++; pos++; len++;
      if (pos < inLen) {
        // Should be dead code!! (Then why is it here doofus?)
        *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >>6)];
        pIn++; pOut++; len++;
        *pOut = gBase64[(*pIn) & 0x3F];
        pos++; pOut++; len++;
      }
      else {
        *pOut = gBase64[(((*pIn) & 0xF) << 2)];
        pOut++; len++;
        *pOut = '=';
        pOut++; len++;
      }
    }
    else {
      *pOut = gBase64[(((*pIn) & 0x3)<< 4)];
      pOut++; len++;
      *pOut = '=';
      pOut++; len++;
      *pOut = '=';
      pOut++; len++;
    }
  }

  *pOut = 0;

  return len;
}