/* -*- 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 "nscore.h"
#include "nsString.h"
#include "prio.h"
#include "nsNetUtil.h"
#include "nsISeekableStream.h"
#include "nsMsgUtils.h"
#include "ImportOutFile.h"
#include "ImportCharSet.h"

#include "ImportDebug.h"

/*
#ifdef _MAC
#define  kMacNoCreator    '????'
#define kMacTextFile    'TEXT'
#else
#define  kMacNoCreator    0
#define kMacTextFile    0
#endif
*/

ImportOutFile::ImportOutFile() {
  m_ownsFileAndBuffer = false;
  m_pos = 0;
  m_pBuf = nullptr;
  m_bufSz = 0;
  m_pTrans = nullptr;
  m_pTransOut = nullptr;
  m_pTransBuf = nullptr;
}

ImportOutFile::ImportOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) {
  m_pTransBuf = nullptr;
  m_pTransOut = nullptr;
  m_pTrans = nullptr;
  m_ownsFileAndBuffer = false;
  InitOutFile(pFile, pBuf, sz);
}

ImportOutFile::~ImportOutFile() {
  if (m_ownsFileAndBuffer) {
    Flush();
    delete[] m_pBuf;
  }

  delete m_pTrans;
  delete m_pTransOut;
  delete[] m_pTransBuf;
}

bool ImportOutFile::Set8bitTranslator(nsImportTranslator* pTrans) {
  if (!Flush()) return false;

  m_engaged = false;
  m_pTrans = pTrans;
  m_supports8to7 = pTrans->Supports8bitEncoding();

  return true;
}

bool ImportOutFile::End8bitTranslation(bool* pEngaged, nsCString& useCharset,
                                       nsCString& encoding) {
  if (!m_pTrans) return false;

  bool bResult = Flush();
  if (m_supports8to7 && m_pTransOut) {
    if (bResult) bResult = m_pTrans->FinishConvertToFile(m_pTransOut);
    if (bResult) bResult = Flush();
  }

  if (m_supports8to7) {
    m_pTrans->GetCharset(useCharset);
    m_pTrans->GetEncoding(encoding);
  } else
    useCharset.Truncate();
  *pEngaged = m_engaged;
  delete m_pTrans;
  m_pTrans = nullptr;
  delete m_pTransOut;
  m_pTransOut = nullptr;
  delete[] m_pTransBuf;
  m_pTransBuf = nullptr;

  return bResult;
}

bool ImportOutFile::InitOutFile(nsIFile* pFile, uint32_t bufSz) {
  if (!bufSz) bufSz = 32 * 1024;
  if (!m_pBuf) m_pBuf = new uint8_t[bufSz];

  if (!m_outputStream) {
    nsresult rv;
    rv = MsgNewBufferedFileOutputStream(
        getter_AddRefs(m_outputStream), pFile,
        PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0644);

    if (NS_FAILED(rv)) {
      IMPORT_LOG0("Couldn't create outfile\n");
      delete[] m_pBuf;
      m_pBuf = nullptr;
      return false;
    }
  }
  m_pFile = pFile;
  m_ownsFileAndBuffer = true;
  m_pos = 0;
  m_bufSz = bufSz;
  return true;
}

void ImportOutFile::InitOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) {
  m_ownsFileAndBuffer = false;
  m_pFile = pFile;
  m_pBuf = pBuf;
  m_bufSz = sz;
  m_pos = 0;
}

bool ImportOutFile::Flush(void) {
  if (!m_pos) return true;

  uint32_t transLen;
  bool duddleyDoWrite = false;

  // handle translations if appropriate
  if (m_pTrans) {
    if (m_engaged && m_supports8to7) {
      // Markers can get confused by this crap!!!
      // TLR: FIXME: Need to update the markers based on
      // the difference between the translated len and untranslated len

      if (!m_pTrans->ConvertToFile(m_pBuf, m_pos, m_pTransOut, &transLen))
        return false;
      if (!m_pTransOut->Flush()) return false;
      // now update our buffer...
      if (transLen < m_pos) {
        memcpy(m_pBuf, m_pBuf + transLen, m_pos - transLen);
      }
      m_pos -= transLen;
    } else if (m_engaged) {
      // does not actually support translation!
      duddleyDoWrite = true;
    } else {
      // should we engage?
      uint8_t* pChar = m_pBuf;
      uint32_t len = m_pos;
      while (len) {
        if (!ImportCharSet::IsUSAscii(*pChar)) break;
        pChar++;
        len--;
      }
      if (len) {
        m_engaged = true;
        if (m_supports8to7) {
          // allocate our translation output buffer and file...
          m_pTransBuf = new uint8_t[m_bufSz];
          m_pTransOut = new ImportOutFile(m_pFile, m_pTransBuf, m_bufSz);
          return Flush();
        } else
          duddleyDoWrite = true;
      } else {
        duddleyDoWrite = true;
      }
    }
  } else
    duddleyDoWrite = true;

  if (duddleyDoWrite) {
    uint32_t written = 0;
    nsresult rv =
        m_outputStream->Write((const char*)m_pBuf, (int32_t)m_pos, &written);
    if (NS_FAILED(rv) || ((uint32_t)written != m_pos)) return false;
    m_pos = 0;
  }

  return true;
}

bool ImportOutFile::WriteU8NullTerm(const uint8_t* pSrc, bool includeNull) {
  while (*pSrc) {
    if (m_pos >= m_bufSz) {
      if (!Flush()) return false;
    }
    *(m_pBuf + m_pos) = *pSrc;
    m_pos++;
    pSrc++;
  }
  if (includeNull) {
    if (m_pos >= m_bufSz) {
      if (!Flush()) return false;
    }
    *(m_pBuf + m_pos) = 0;
    m_pos++;
  }

  return true;
}

bool ImportOutFile::SetMarker(int markerID) {
  if (!Flush()) {
    return false;
  }

  if (markerID < kMaxMarkers) {
    int64_t pos = 0;
    if (m_outputStream) {
      // do we need to flush for the seek to give us the right pos?
      m_outputStream->Flush();
      nsresult rv;
      nsCOMPtr<nsISeekableStream> seekStream =
          do_QueryInterface(m_outputStream, &rv);
      NS_ENSURE_SUCCESS(rv, false);
      rv = seekStream->Tell(&pos);
      if (NS_FAILED(rv)) {
        IMPORT_LOG0("*** Error, Tell failed on output stream\n");
        return false;
      }
    }
    m_markers[markerID] = (uint32_t)pos + m_pos;
  }

  return true;
}

void ImportOutFile::ClearMarker(int markerID) {
  if (markerID < kMaxMarkers) m_markers[markerID] = 0;
}

bool ImportOutFile::WriteStrAtMarker(int markerID, const char* pStr) {
  if (markerID >= kMaxMarkers) return false;

  if (!Flush()) return false;
  int64_t pos;
  m_outputStream->Flush();
  nsresult rv;
  nsCOMPtr<nsISeekableStream> seekStream =
      do_QueryInterface(m_outputStream, &rv);
  NS_ENSURE_SUCCESS(rv, false);
  rv = seekStream->Tell(&pos);
  if (NS_FAILED(rv)) return false;
  rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET,
                        (int32_t)m_markers[markerID]);
  if (NS_FAILED(rv)) return false;
  uint32_t written;
  rv = m_outputStream->Write(pStr, strlen(pStr), &written);
  if (NS_FAILED(rv)) return false;

  rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, pos);
  if (NS_FAILED(rv)) return false;

  return true;
}
