/******************************************************************************
 *
 * Project:  OpenCPN
 * Purpose:  Support XZ compressed charts
 * Author:   Sean D'Epagnier
 *
 ***************************************************************************
 *   Copyright (C) 2016 by David S. Register                               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,  USA.             *
 ***************************************************************************
 *
 */

// For compilers that support precompilation, includes "wx.h".
#include <wx/wxprec.h>

#ifndef WX_PRECOMP
#include <wx/wx.h>
#endif  // precompiled headers

#include <wx/filename.h>
#include <wx/log.h>
#include <wx/wfstream.h>

#include "config.h"
#include "chartdata_input_stream.h"

#ifdef OCPN_USE_LZMA

wxCompressedFFileInputStream::wxCompressedFFileInputStream(
    const wxString &fileName) {
  init_lzma();
  m_file = new wxFFile(fileName, "rb");
}

wxCompressedFFileInputStream::~wxCompressedFFileInputStream() {
  delete m_file;
  lzma_end(&strm);
}

size_t wxCompressedFFileInputStream::OnSysRead(void *buffer, size_t size) {
  lzma_action action = LZMA_RUN;

  strm.next_out = (uint8_t *)buffer;
  strm.avail_out = size;

  for (;;) {
    if (strm.avail_in == 0) {
      if (!m_file->Eof()) {
        strm.next_in = inbuf;
        strm.avail_in = m_file->Read(inbuf, sizeof inbuf);

        if (m_file->Error()) return 0;

      } else
        action = LZMA_FINISH;
    }

    lzma_ret ret = lzma_code(&strm, action);

    if (strm.avail_out == 0 || ret == LZMA_STREAM_END)
      return size - strm.avail_out;

    if (ret != LZMA_OK) {
      m_lasterror = wxSTREAM_READ_ERROR;
      return 0;
    }
  }
  return 0;
}

wxFileOffset wxCompressedFFileInputStream::OnSysSeek(wxFileOffset pos,
                                                     wxSeekMode mode) {
  // rewind to start is possible
  if (pos == 0 && mode == wxFromStart) {
    lzma_end(&strm);
    init_lzma();
    return m_file->Seek(pos, mode);
  }

  return wxInvalidOffset;
}

wxFileOffset wxCompressedFFileInputStream::OnSysTell() const {
  return strm.total_out;
}

void wxCompressedFFileInputStream::init_lzma() {
  lzma_stream s = LZMA_STREAM_INIT;
  memcpy(&strm, &s, sizeof s);
  lzma_ret ret = lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED);

  if (ret != LZMA_OK) m_lasterror = wxSTREAM_READ_ERROR;
}

ChartDataNonSeekableInputStream::ChartDataNonSeekableInputStream(
    const wxString &fileName) {
  if (fileName.Upper().EndsWith("XZ"))
    m_stream = new wxCompressedFFileInputStream(fileName);
  else
    m_stream = new wxFFileInputStream(fileName);
}

ChartDataNonSeekableInputStream::~ChartDataNonSeekableInputStream() {
  delete m_stream;
}

size_t ChartDataNonSeekableInputStream::OnSysRead(void *buffer, size_t size) {
  m_stream->Read(buffer, size);
  return m_stream->LastRead();
}

wxFileOffset ChartDataNonSeekableInputStream::OnSysSeek(wxFileOffset pos,
                                                        wxSeekMode mode) {
  return m_stream->SeekI(pos, mode);
}

wxFileOffset ChartDataNonSeekableInputStream::OnSysTell() const {
  return m_stream->TellI();
}

ChartDataInputStream::ChartDataInputStream(const wxString &fileName) {
  if (fileName.Upper().EndsWith("XZ")) {
    // decompress to temp file to allow seeking
    m_tempfilename =
        wxFileName::CreateTempFileName(wxFileName(fileName).GetFullName());
    wxCompressedFFileInputStream stream(fileName);
    wxFFileOutputStream tmp(m_tempfilename);

    char buffer[8192];
    int len;
    do {
      stream.Read(buffer, sizeof buffer);
      len = stream.LastRead();
      tmp.Write(buffer, len);
    } while (len == sizeof buffer);

    // do some error checking here?

    tmp.Close();
    m_stream = new wxFFileInputStream(m_tempfilename);
  } else
    m_stream = new wxFFileInputStream(fileName);
}

ChartDataInputStream::~ChartDataInputStream() {
  // close it
  delete m_stream;
  // delete the temp file, how do we remove temp files if the program crashed?
  if (!m_tempfilename.empty()) wxRemoveFile(m_tempfilename);
}

size_t ChartDataInputStream::OnSysRead(void *buffer, size_t size) {
  m_stream->Read(buffer, size);
  return m_stream->LastRead();
}

wxFileOffset ChartDataInputStream::OnSysSeek(wxFileOffset pos,
                                             wxSeekMode mode) {
  return m_stream->SeekI(pos, mode);
}

wxFileOffset ChartDataInputStream::OnSysTell() const {
  return m_stream->TellI();
}

bool DecompressXZFile(const wxString &input_path, const wxString &output_path) {
  if (!wxFileExists(input_path)) {
    return false;
  }
  wxCompressedFFileInputStream in(input_path);
  wxFFileOutputStream out(output_path);

  char buffer[8192];
  int len;
  do {
    in.Read(buffer, sizeof buffer);
    len = in.LastRead();
    out.Write(buffer, len);
  } while (len == sizeof buffer);

  return in.GetLastError() != wxSTREAM_READ_ERROR;
}

#else  // OCPN_USE_LZMA

bool DecompressXZFile(const wxString &input_path, const wxString &output_path) {
  wxLogMessage(_T("Failed to decompress: ") + input_path);
  wxLogMessage(_T("OpenCPN compiled without liblzma support"));

  return false;
}

#endif  // OCPN_USE_LZMA
