/* ArchiveReader::extractCurrentEntry {{{
 *
*/
ZEND_METHOD(ArchiveReader, extractCurrentEntry) 
{
	zval *this = getThis();
	archive_file_t *arch;
	int result, error_num, flags = 0;
	const char *error_string;
    zend_error_handling error_handling;
	
    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags) == FAILURE) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (arch->current_entry == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Current archive entry is not available");
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (arch->current_entry->data) {
		/* again, rather annoying libarchive limitation: you can't extract or 
		 * read entry anymore if it had been extracted/read before.
		 * */
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_FALSE;
	}

	result = archive_read_extract(arch->arch, arch->current_entry->entry, flags);
	
	if (result && result != ARCHIVE_EOF) {
		error_num = archive_errno(arch->arch);
		error_string = archive_error_string(arch->arch);
		
		if (error_num && error_string) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to extract entry: error #%d, %s", error_num, error_string);
		}
		else {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to extract entry: unknown error %d", result);
		}

        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
    zend_restore_error_handling(&error_handling TSRMLS_CC);
	RETURN_TRUE;
}
/* ArchiveReader::getCurrentEntryData {{{
 *
*/
ZEND_METHOD(ArchiveReader, getCurrentEntryData) 
{
	zval *this = getThis();
	archive_file_t *arch;
	int result, error_num;
	size_t len;
	off_t offset;
	const char *error_string;
	char *buf;
    zend_error_handling error_handling;
	
    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (arch->current_entry == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Current archive entry is not available");
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (arch->current_entry->data) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_STRINGL(arch->current_entry->data, arch->current_entry->data_len, 1);
	}
	
	while ((result = archive_read_data_block(arch->arch, (const void **)&buf, &len, &offset)) == ARCHIVE_OK) {
		arch->current_entry->data = erealloc(arch->current_entry->data, arch->current_entry->data_len + len + 1);
		memcpy(arch->current_entry->data + arch->current_entry->data_len, buf, len);
		arch->current_entry->data_len += len;
	}
	
	if (result && result != ARCHIVE_EOF) {
		error_num = archive_errno(arch->arch);
		error_string = archive_error_string(arch->arch);
		
		if (error_num && error_string) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read entry data: error #%d, %s", error_num, error_string);
		}
		else {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read entry data: unknown error %d", result);
		}
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
    zend_restore_error_handling(&error_handling TSRMLS_CC);
	RETURN_STRINGL(arch->current_entry->data, arch->current_entry->data_len, 1);
}
/* ArchiveReader::readCurrentEntryData(count) {{{
 *
*/
ZEND_METHOD(ArchiveReader, readCurrentEntryData){
	zval *this = getThis();
	archive_file_t *arch;
	char *buf;
	const char *error_string;
	size_t len;
	int r, error_num;
	long count;
    zend_error_handling error_handling;

    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &count) == FAILURE) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	if (arch->current_entry == NULL) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Current archive entry is not available");
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	Z_STRVAL_P(return_value) = emalloc(count+1);
	len = 0;
	while(count > 0){
		r = archive_read_data(arch->arch, Z_STRVAL_P(return_value)+len, count);
		if(r < ARCHIVE_OK){
			error_num = archive_errno(arch->arch);
			error_string = archive_error_string(arch->arch);
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Decompress entry failed errno(%d):%s", error_num, error_string);
			zend_restore_error_handling(&error_handling TSRMLS_CC);
			return;
		}
		if(r == 0){
			break;
		}
		count -= r;
		len += r;
	}
	Z_STRVAL_P(return_value)[len] = 0;
	Z_STRLEN_P(return_value) = len;
	Z_TYPE_P(return_value) = IS_STRING;
    zend_restore_error_handling(&error_handling TSRMLS_CC);
}
/* ArchiveReader::getStream{{{
 *
 * */
ZEND_METHOD(ArchiveReader, getStream){
	zval *this = getThis();
	archive_file_t *arch;
    zend_error_handling error_handling;

    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);
	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	if(arch->stream){
		php_stream_to_zval(arch->stream, return_value);
	}else{
		RETURN_FALSE;
	}
}
/* ArchiveReader::getArchiveFormat{{{
 *
 * */
ZEND_METHOD(ArchiveReader, getArchiveFormat){
	long format;
	zval *this = getThis();
	archive_file_t *arch;
	struct archive *a;
    zend_error_handling error_handling;

    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);
	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_FALSE;
	}
	zend_restore_error_handling(&error_handling TSRMLS_CC);
	format = archive_format(arch->arch);

	RETURN_LONG(format);
}/*}}}*/
/* ArchiveReader::skipCurrentEntryData{{{
 * */
ZEND_METHOD(ArchiveReader, skipCurrentEntryData){
	zval *this = getThis();
	archive_file_t *arch;
	zend_error_handling error_handling;
	int r;
	
	zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	r = archive_read_data_skip(arch->arch);
	if(r != ARCHIVE_OK){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "Skip archive entry failed");
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	return;
}/*}}}*/
/* ArchiveWriter::finish {{{
 *
*/
ZEND_METHOD(ArchiveWriter, finish)
{
	zval *this = getThis();
	int resource_id;
	HashPosition pos;
	archive_file_t *arch;
	archive_entry_t **entry;
	int result, error_num;
	const char *error_string;
	mode_t mode;
	php_stream *stream;
	zend_error_handling error_handling;

	zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (zend_hash_sort(arch->entries, zend_qsort, _archive_pathname_compare, 0 TSRMLS_CC) == FAILURE) {
		RETURN_FALSE;
	}

	zend_hash_internal_pointer_reset_ex(arch->entries, &pos);
	while (zend_hash_get_current_data_ex(arch->entries, (void **)&entry, &pos) == SUCCESS) {

		mode = archive_entry_mode((*entry)->entry);

		if (S_ISREG(mode) && archive_entry_size((*entry)->entry) > 0) {
			if ((stream = php_stream_open_wrapper_ex((*entry)->filename, "r", ENFORCE_SAFE_MODE | REPORT_ERRORS, NULL, NULL))) {
				char buf[PHP_ARCHIVE_BUF_LEN];
				int header_written=0;
				int read_bytes;

				while ((read_bytes = php_stream_read(stream, buf, PHP_ARCHIVE_BUF_LEN))) {
					if (!header_written) {
						/* write header only after the first successful read */
						result = archive_write_header(arch->arch, (*entry)->entry);
						if (result == ARCHIVE_FATAL) {
							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write entry header for file %s: fatal error", (*entry)->filename);
							zend_restore_error_handling(&error_handling TSRMLS_CC);
							return;
						}
						header_written = 1;
					}
					result = archive_write_data(arch->arch, buf, read_bytes);

					if (result <= 0) {
						error_num = archive_errno(arch->arch);
						error_string = archive_error_string(arch->arch);

						if (error_num && error_string) {
							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write file %s to archive: error #%d, %s", (*entry)->filename, error_num, error_string);
						}
						else {
							php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write file %s: unknown error %d", (*entry)->filename, result);
						}
						php_stream_close(stream);
						zend_restore_error_handling(&error_handling TSRMLS_CC);
						return;
					}
				}
				php_stream_close(stream);
			}
		}
		else {
			result = archive_write_header(arch->arch, (*entry)->entry);
			if (result == ARCHIVE_FATAL) {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write entry header for file %s: fatal error", (*entry)->filename);
				zend_restore_error_handling(&error_handling TSRMLS_CC);
				return;
			}
		}
		zend_hash_move_forward_ex(arch->entries, &pos);
	}

	if ((resource_id = _archive_get_rsrc_id(this TSRMLS_CC))) {
		add_property_resource(this, "fd", 0);
		zend_list_delete(resource_id);
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_TRUE;
	}

	php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to finish writing of archive file");
	zend_restore_error_handling(&error_handling TSRMLS_CC);
}
/* ArchiveWriter::addEntry {{{
 *
*/
ZEND_METHOD(ArchiveWriter, addEntry)
{
	zval *this = getThis();
	zval *entry_obj;
	archive_file_t *arch;
	archive_entry_t *entry, *entry_copy;
	char *pathname;
	int pathname_len;
	const struct stat *stat_sb;
	zend_error_handling error_handling;

	zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "o", &entry_obj) == FAILURE) {
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (!instanceof_function(Z_OBJCE_P(entry_obj), ce_ArchiveEntry TSRMLS_CC)) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "An instance of ArchiveEntry is required");
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	if (!_archive_get_entry_struct(entry_obj, &entry TSRMLS_CC)) {
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}

	pathname = entry->filename;
	pathname_len = strlen(pathname);

	_archive_normalize_path(&pathname, &pathname_len);

	if (pathname_len == 0 || pathname[0] == '\0') {
		/* user is probably trying to add "./", "/", ".." or ".", ignoring it silently */
		zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_TRUE;
	}

	/* copy entry.. */
	entry_copy = emalloc(sizeof(archive_entry_t));
	memcpy(entry_copy, entry, sizeof(archive_entry_t));
	entry_copy->entry = archive_entry_new();
	entry_copy->filename = estrdup(entry->filename);

	entry_copy->data = NULL;
	entry_copy->data_len = 0;

	archive_entry_copy_pathname(entry_copy->entry, pathname);
	stat_sb = archive_entry_stat(entry->entry);
	archive_entry_copy_stat(entry_copy->entry, stat_sb);

	/* ..and add it to the hash */
	zend_hash_update(arch->entries, pathname, pathname_len + 1, &entry_copy, sizeof(archive_entry_t), NULL);
	zend_restore_error_handling(&error_handling TSRMLS_CC);
	RETURN_TRUE;
}
/* ArchiveReader::getNextEntry {{{
 *
*/
ZEND_METHOD(ArchiveReader, getNextEntry) 
{
	zval *this = getThis();
	archive_file_t *arch;
	int result, error_num, resource_id;
	const char *error_string;
	zend_bool fetch_entry_data = 0;
	archive_entry_t *entry;
	struct archive_entry *current_entry;
	size_t len;
	off_t offset;
	char *buf;
    zend_error_handling error_handling;
	
    zend_replace_error_handling(EH_THROW, ce_ArchiveException, &error_handling TSRMLS_CC);

	if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &fetch_entry_data) == FAILURE) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (!_archive_get_fd(this, &arch TSRMLS_CC)) {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (arch->struct_state == ARCHIVE_OK) {
		result = archive_read_next_header(arch->arch, &current_entry);
		arch->struct_state = result;
		entry = (archive_entry_t *) emalloc(sizeof(archive_entry_t));
		entry->entry = current_entry;
		entry->data = NULL;
		entry->filename = NULL;
		entry->resolved_filename = NULL;
		entry->data_len = 0;
		arch->current_entry = entry;
	}
	else {
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_FALSE;
	}
	
	if (result && result != ARCHIVE_EOF) {
		arch->current_entry = NULL;
		error_num = archive_errno(arch->arch);
		error_string = archive_error_string(arch->arch);
		efree(entry);

		if (error_num && error_string) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read file %s: error #%d, %s", arch->filename, error_num, error_string);
		}
		else {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read file %s: unknown error %d", arch->filename, result);
		}	
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		return;
	}
	
	if (result == ARCHIVE_EOF) {
		arch->current_entry = NULL;
		efree(entry);
        zend_restore_error_handling(&error_handling TSRMLS_CC);
		RETURN_FALSE;
	}
	
	object_init_ex(return_value, ce_ArchiveEntry);
	
	if (fetch_entry_data) {
		while ((result = archive_read_data_block(arch->arch, (const void **)&buf, &len, &offset)) == ARCHIVE_OK) {
			entry->data = erealloc(entry->data, entry->data_len + len + 1);
			memcpy(entry->data + entry->data_len, buf, len);
			entry->data_len += len;
		}
		
		if (result && result != ARCHIVE_EOF) {
			error_num = archive_errno(arch->arch);
			error_string = archive_error_string(arch->arch);
			efree(entry);
			
			if (error_num && error_string) {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read file %s: error #%d, %s", arch->filename, error_num, error_string);
			}
			else {
				php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to read file %s: unknown error %d", arch->filename, result);
			}	
            zend_restore_error_handling(&error_handling TSRMLS_CC);
			return;
		}	
	}

	if (entry->entry) {
		resource_id = zend_list_insert(entry,le_archive_entry);	
		add_property_resource(return_value, "entry", resource_id);
	}

    zend_restore_error_handling(&error_handling TSRMLS_CC);
}