#include "Archive.h"

#ifdef __cplusplus
extern "C" {
#endif
#include "libarchive/config.h"
#include "libarchive/archive.h"
#include "libarchive/archive_entry.h"
#ifdef __cplusplus
}
#endif

// constructor
Archive::Archive(String const &path)
{
	_path = path;
	_error = false;
	_errorMsg = "";
}

// destructor
Archive::~Archive()
{
}

// copy data from one archive to another
bool Archive::copyData(struct archive *ar, struct archive *aw)
{
	int res;
	const void *buff;
	size_t size;
#if ARCHIVE_VERSION_NUMBER >= 3000000
	int64_t offset;
#else
	off_t offset;
#endif

	for (;;)
	{
		Cerr() << "Reading block\n";
		res = archive_read_data_block(ar, &buff, &size, &offset);
		
		if (res == ARCHIVE_EOF)
		{
			Cerr() << "Finished block\n";
			return true;
		}
		
		if (res != ARCHIVE_OK)
		{
			Cerr() << "Error reading block\n";
			_error = true;
			_errorMsg = archive_error_string(ar);
			return false;
		}

		Cerr() << "Writing block\n";
		res = archive_write_data_block(aw, buff, size, offset);
		if (res != ARCHIVE_OK)
		{
			Cerr() << "Error writing block\n";
			_error = true;
			_errorMsg = archive_error_string(aw);
			return false;
		}
	}
}

static String pathStrip(String path, int strip)
{
	// leading /, if any... to be sure
	if(path.StartsWith("/"))
		path = path.Mid(1);
	int start = 0;
	for(int i = 0; i < strip; i++)
	{
		start = path.Find("/", start);
		if(start < 0)
			return "";
	}
	return path.Mid(start + 1);
}

// decompress archive into a given folder
bool Archive::Decompress(String const &destPath, int strip)
{
	// extract files
	struct archive *a;
	struct archive *ext;
	struct archive_entry *entry;
	int res;
	int flags =
		ARCHIVE_EXTRACT_TIME	|
		ARCHIVE_EXTRACT_PERM
/*
 	|
		ARCHIVE_EXTRACT_ACL		|
		ARCHIVE_EXTRACT_FFLAGS
*/
	;

	a = archive_read_new();
	
	ext = archive_write_disk_new();
	archive_write_disk_set_options(ext, flags);

	// supporto all formats and filters
	archive_read_support_format_all(a);
	archive_read_support_filter_all(a);

	// open the archive
	res = archive_read_open_filename(a, _path, 10240);
	if(res)
	{
		_error = true;
		_errorMsg = archive_error_string(a);
		archive_read_close(a);
		archive_read_free(a);
		return false;
	}
	
	int64 archiveSize = GetFileLength(_path);
	Progress(0, 1);
	
	// create destination folder, if needed
	RealizeDirectory(destPath);

	// go to destination path
	// this dumb library needs it for hard links.. no other way
	String oldDir = GetCurrentDirectory();
	SetCurrentDirectory(destPath);
	for (;;)
	{
		res = archive_read_next_header(a, &entry);

		int64 pos = archive_read_header_position(a);
		int prog = (int)(100L * pos / archiveSize);
		Progress(prog, 100);

		// if archive end, terminate
		if (res == ARCHIVE_EOF)
			break;
		
		// on error, store error message and terminate
		if (res != ARCHIVE_OK)
		{
			_error = true;
			_errorMsg = archive_error_string(a);
			break;
		}
		
		// set dest path name
		String path = archive_entry_pathname(entry);
		
		if(strip)
		{
			String stripped = pathStrip(path, strip);
			
			// silently skip files with no path after stripping
			if(stripped.IsEmpty())
				continue;
			
			archive_entry_set_pathname(entry, stripped);
			
			const char *hLink = archive_entry_hardlink(entry);
			if(hLink)
			{
				String hStripped = pathStrip(hLink, strip);
				archive_entry_set_hardlink(entry, hStripped);
			}
		}
				
		
		// write entry to destination
		res = archive_write_header(ext, entry);
		
		if(res != ARCHIVE_OK)
		{
			_error = true;
			_errorMsg = archive_error_string(ext);
			break;
		}
		
		// copy data
		if(!copyData(a, ext))
		{
			break;
		}
		
		// terminate writing
		res = archive_write_finish_entry(ext);
		
		if(res != ARCHIVE_OK)
		{
			_error = true;
			_errorMsg = archive_error_string(ext);
			break;
		}
	}
	
	// back to previous directory
	SetCurrentDirectory(oldDir);
	
	archive_read_close(a);
	archive_read_free(a);

	archive_write_close(ext);
	archive_write_free(ext);
	
	return !_error;
}
