/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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 mozilla.org code.
 *
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "ImportOutFile.h"
#include "nsImportTranslator.h"

#include "ImportCharSet.h"


PRBool nsImportTranslator::ConvertToFile( const PRUint8 * pIn, PRUint32 inLen, ImportOutFile *pOutFile, PRUint32 *pProcessed)
{
  if (pProcessed)
    *pProcessed = inLen;
  return( pOutFile->WriteData( pIn, inLen));
}

void CMHTranslator::ConvertBuffer( const PRUint8 * pIn, PRUint32 inLen, PRUint8 * 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;
}

PRBool CMHTranslator::ConvertToFile( const PRUint8 * pIn, PRUint32 inLen, ImportOutFile *pOutFile, PRUint32 *pProcessed)
{
  PRUint8    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( PR_FALSE);
      ImportCharSet::ByteToHex( *pIn, hex);
      if (!pOutFile->WriteData( hex, 2))
        return( PR_FALSE);
    }
    else {
      if (!pOutFile->WriteByte( *pIn))
        return( PR_FALSE);
    }
    pIn++; inLen--;
  }

  if (pProcessed)
    *pProcessed = inLen;

  return( PR_TRUE);
}


PRBool C2047Translator::ConvertToFileQ( const PRUint8 * pIn, PRUint32 inLen, ImportOutFile *pOutFile, PRUint32 *pProcessed)
{
  if (!inLen)
    return( PR_TRUE);

  int    maxLineLen = 64;
  int    curLineLen = m_startLen;
  PRBool  startLine = PR_TRUE;

  PRUint8  hex[2];
  while (inLen) {
    if (startLine) {
      if (!pOutFile->WriteStr( " =?"))
        return( PR_FALSE);
      if (!pOutFile->WriteStr( m_charset.get()))
        return( PR_FALSE);
      if (!pOutFile->WriteStr( "?q?"))
        return( PR_FALSE);
      curLineLen += (6 + m_charset.Length());
      startLine = PR_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( PR_FALSE);
      ImportCharSet::ByteToHex( *pIn, hex);
      if (!pOutFile->WriteData( hex, 2))
        return( PR_FALSE);
      curLineLen += 3;
    }
    else {
      if (!pOutFile->WriteByte( *pIn))
        return( PR_FALSE);
      curLineLen++;
    }
    pIn++; inLen--;
    if (curLineLen > maxLineLen) {
      if (!pOutFile->WriteStr( "?="))
        return( PR_FALSE);
      if (inLen) {
        if (!pOutFile->WriteStr( "\x0D\x0A "))
          return( PR_FALSE);
      }

      startLine = PR_TRUE;
      curLineLen = 0;
    }
  }

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

  if (pProcessed)
    *pProcessed = inLen;

  return( PR_TRUE);
}

PRBool C2047Translator::ConvertToFile( const PRUint8 * pIn, PRUint32 inLen, ImportOutFile *pOutFile, PRUint32 *pProcessed)
{
  if (m_useQuotedPrintable)
    return( ConvertToFileQ( pIn, inLen, pOutFile, pProcessed));

  if (!inLen)
    return( PR_TRUE);

  int      maxLineLen = 64;
  int      curLineLen = m_startLen;
  PRBool    startLine = PR_TRUE;
  int      encodeMax;
  PRUint8 *  pEncoded = new PRUint8[maxLineLen * 2];

  while (inLen) {
    if (startLine) {
      if (!pOutFile->WriteStr( " =?")) {
        delete [] pEncoded;
        return( PR_FALSE);
      }
      if (!pOutFile->WriteStr( m_charset.get())) {
        delete [] pEncoded;
        return( PR_FALSE);
      }
      if (!pOutFile->WriteStr( "?b?")) {
        delete [] pEncoded;
        return( PR_FALSE);
      }
      curLineLen += (6 + m_charset.Length());
      startLine = PR_FALSE;
    }
    encodeMax = maxLineLen - curLineLen;
    encodeMax *= 3;
    encodeMax /= 4;
    if ((PRUint32)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( PR_FALSE);
    }

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

  delete [] pEncoded;

  if (pProcessed)
    *pProcessed = inLen;

  return( PR_TRUE);
}


PRUint32  UMimeEncode::GetBufferSize( PRUint32 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 PRUint8 gBase64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

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

  PRUint32  pos = 0;
  PRUint32  len = 0;
  PRUint32  lineLen = 0;
  PRUint32  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);
}
