//
// This file is part of the aMule Project.
//
// Copyright (c) 2003-2011 aMule Team ( admin@amule.org / http://www.amule.org )
// Copyright (c) 2002-2011 Merkur ( devs@emule-project.net / http://www.emule-project.net )
//
// Any parts of this program derived from the xMule, lMule or eMule project,
// or contributed by third-party developers are copyrighted by their
// respective authors.
//
// 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 St, Fifth Floor, Boston, MA  02110-1301, USA
//


#include <wx/dir.h>		// Needed for wxDir
#include <wx/fs_zip.h>		// Needed for wxZipFSHandler
#include <wx/wfstream.h>	// wxFileInputStream
#include <wx/zipstrm.h>		// Needed for wxZipInputStream
#include <wx/zstream.h>		// Needed for wxZlibInputStream
#include <wx/log.h>		// Needed for wxSysErrorMsg

#ifdef __WXMAC__
#include <zlib.h> // Do_not_auto_remove
#endif
#include <algorithm>		// Needed for std::min

#include "FileFunctions.h"
#include "StringFunctions.h"
#include "SmartPtr.h"		// Needed for CSmartPtr

//
// This class assumes that the following line has been executed:
//
//	wxConvFileName = &aMuleConvBrokenFileNames;
//
// This line is necessary so that wxWidgets handles unix file names correctly.
//
CDirIterator::CDirIterator(const CPath& dir)
	: wxDir(dir.GetRaw())
{
}


CDirIterator::~CDirIterator()
{
}


CPath CDirIterator::GetFirstFile(FileType type, const wxString& mask)
{
	if (!IsOpened()) {
		return CPath();
	}

	wxString fileName;
	if (!GetFirst(&fileName, mask, type)) {
		return CPath();
	}

	return CPath(fileName);
}


CPath CDirIterator::GetNextFile()
{
	wxString fileName;
	if (!GetNext(&fileName)) {
		return CPath();
	}

	return CPath(fileName);
}


bool CDirIterator::HasSubDirs(const wxString& spec)
{
	// Checking IsOpened() in case we don't have permissions to read that dir.
	return IsOpened() && wxDir::HasSubDirs(spec);
}


static EFileType GuessFiletype(const wxString& file)
{
	wxFile archive(file, wxFile::read);
	if (!archive.IsOpened()) {
		return EFT_Error;
	}
	static const uint8 UTF8bom[3] = {0xEF, 0xBB, 0xBF};
	uint8 head[10] = {0, 0};
	int read = archive.Read(head, std::min<off_t>(10, archive.Length()));

	if (read == wxInvalidOffset || read == 0) {
		return EFT_Unknown;
	} else if ((head[0] == 'P') && (head[1] == 'K')) {
		// Zip-archives have a header of "PK".
		return EFT_Zip;
	} else if (head[0] == 0x1F && head[1] == 0x8B) {
		// Gzip-archives have a header of 0x1F8B
		return EFT_GZip;
	} else if (head[0] == 0xE0 || head[0] == 0x0E) {
		// MET files have either of these headers
		return EFT_Met;
	}

	// Check at most the first ten chars, if all are printable,
	// then we can probably assume it is ascii text-file.
	for (int i = 0; i < read; ++i) {
		if (!(		isprint(head[i])
				||	isspace(head[i])
				||	(i < 3 && head[i] == UTF8bom[i]))) {
			return EFT_Unknown;
		}
	}

	return EFT_Text;
}


/**
 * Replaces the zip-archive with "guarding.p2p" or "ipfilter.dat",
 * if either of those files are found in the archive.
 */
static bool UnpackZipFile(const wxString& file, const wxChar* files[])
{
	wxTempFile target(file);
	CSmartPtr<wxZipEntry> entry;
	wxFFileInputStream fileInputStream(file);
	wxZipInputStream zip(fileInputStream);
	bool run = true;
	while (run) {
		entry.reset(zip.GetNextEntry());
		if (entry.get() == NULL) {
			break;
		}
		// access meta-data
		wxString name = entry->GetName();
		// We only care about the files specified in the array
		// probably needed to weed out included nfos
		for (int i = 0; run && files[i]; i++) {
			if (name.CmpNoCase(files[i]) == 0) {
				// we found the entry we want
				// read 'zip' to access the entry's data
				char buffer[10240];
				while (!zip.Eof()) {
					zip.Read(buffer, sizeof(buffer));
					target.Write(buffer, zip.LastRead());
				}
				run = false;
			}
		}
	}

	if (target.Length()) {
		target.Commit();
		return true;
	}

	return false;
}


/**
 * Unpacks a GZip file and replaces the archive.
 */
static bool UnpackGZipFile(const wxString& file)
{
	wxTempFile target(file);

	bool write = false;

#ifdef __WXMAC__
	// AddDebugLogLineN( logFileIO, wxT("Reading gzip stream") );

	gzFile inputFile = gzopen(filename2char(file), "rb");
	if (inputFile != NULL) {
		char buffer[10240];
		write = true;

		while (int bytesRead = gzread(inputFile, buffer, sizeof(buffer))) {
			if (bytesRead > 0) {
				// AddDebugLogLineN(logFileIO, CFormat(wxT("Read %u bytes")) % bytesRead);
				target.Write(buffer, bytesRead);
			} else if (bytesRead < 0) {
				wxString errString;
				int gzerrnum;
				const char* gzerrstr = gzerror(inputFile, &gzerrnum);
				if (gzerrnum == Z_ERRNO) {
					errString = wxSysErrorMsg();
				} else {
					errString = wxString::FromAscii(gzerrstr);
				}

				// AddDebugLogLineN( logFileIO, wxT("Error reading gzip stream (") + errString + wxT(")") );
				write = false;
				break;
			}
		}

		// AddDebugLogLineN( logFileIO, wxT("End reading gzip stream") );
		gzclose(inputFile);
	} else {
		// AddDebugLogLineN( logFileIO, wxT("Error opening gzip file (") + wxString(wxSysErrorMsg()) + wxT(")") );
	}
#else
	{
		// AddDebugLogLineN( logFileIO, wxT("Reading gzip stream") );

		wxFileInputStream source(file);
		wxZlibInputStream inputStream(source);

		while (!inputStream.Eof()) {
			char buffer[10240];
			inputStream.Read(buffer, sizeof(buffer));

			// AddDebugLogLineN(logFileIO, CFormat(wxT("Read %u bytes")) % inputStream.LastRead());
			if (inputStream.LastRead()) {
				target.Write(buffer, inputStream.LastRead());
			} else {
				break;
			}
		};

		// AddDebugLogLineN( logFileIO, wxT("End reading gzip stream") );

		write = inputStream.IsOk() || inputStream.Eof();
	}
#endif

	if (write) {
		target.Commit();
		// AddDebugLogLineN( logFileIO, wxT("Commited gzip stream") );
	}

	return write;
}


UnpackResult UnpackArchive(const CPath& path, const wxChar* files[])
{
	const wxString file = path.GetRaw();

	// Attempt to discover the filetype and uncompress
	EFileType type = GuessFiletype(file);
	switch (type) {
		case EFT_Zip:
			if (UnpackZipFile(file, files)) {
				// Unpack nested archives if needed.
				return UnpackResult(true, UnpackArchive(path, files).second);
			} else {
				return UnpackResult(false, EFT_Error);
			}

		case EFT_GZip:
			if (UnpackGZipFile(file)) {
				// Unpack nested archives if needed.
				return UnpackResult(true, UnpackArchive(path, files).second);
			} else {
				return UnpackResult(false, EFT_Error);
			}

		default:
			return UnpackResult(false, type);
	}
}
// File_checked_for_headers
