package de.avatic.taricdb.helper.zip;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import de.avatic.taricdb.helper.file.InMemFile;
import de.avatic.taricdb.service.SetupStatusService;


@Component
public class Unzipper {
	
	@Autowired
	private SetupStatusService statusService;
	 
	Logger log = LoggerFactory.getLogger(Unzipper.class);

	/**
	 * Extracts a ZIP file from a byte array and returns a list of in-memory files.
	 * The method iterates through the entries in the ZIP archive, and for each non-directory entry,
	 * it creates an {@code InMemFile} object containing the file name and content.
	 *
	 * @param zipFile A byte array containing the ZIP file data.
	 * @return A list of {@code InMemFile} objects representing the files extracted from the ZIP archive.
	 * @throws IOException If an I/O error occurs during the extraction process.
	 */
	public List<InMemFile> unzip(byte[] zipFile) throws IOException {
		List<InMemFile> files = new ArrayList<>();
		ZipInputStream zipStream = new ZipInputStream(new ByteArrayInputStream(zipFile));
		ZipEntry entry;
		
	    while ((entry = zipStream.getNextEntry()) != null) {
			if (!entry.isDirectory()) {
				files.add(new InMemFile(entry.getName(), readFileContent(zipStream)));
			}
	        zipStream.closeEntry();
	        log.info("Unzip done: " + entry.getName());
			statusService.setStatus(0, "unzipping ... " + entry.getName(), true, null);
	    }
	    
	    return files;
	}

	private static final int CHUNK_SIZE = 10240; // Konstant für die Blockgröße

	/**
	 * Reads the content of a specified {@code ZipEntry} from the provided {@code InputStream}.
	 * The method processes the input stream in chunks, stores the read data, and combines it to form the complete file content.
	 *
	 * @param zipStream The input stream of the ZIP archive.
	 * @return A byte array containing the content of the specified ZIP entry.
	 * @throws IOException If an I/O error occurs while reading the entry content.
	 */
	private byte[] readFileContent(InputStream zipStream) throws IOException {
		List<byte[]> chunks = new ArrayList<>(); // Zwischenspeicherung der gelesenen Blöcke.
		int totalRead = 0;

		int bytesRead;
		do {
			byte[] chunk = readChunk(zipStream); // Lese einen einzelnen Datenblock.
			bytesRead = chunk.length;
			if (bytesRead > 0) {
				chunks.add(chunk);
				totalRead += bytesRead;
			}
		} while (bytesRead > 0);

		// Kombiniere alle gelesenen Blöcke.
		byte[] result = new byte[totalRead];
		int destPos = 0;
		for (byte[] chunk : chunks) {
			System.arraycopy(chunk, 0, result, destPos, chunk.length);
			destPos += chunk.length;
		}

		return result;
	}

	/**
	 * Reads a single chunk of data from the {@code InputStream}.
	 *
	 * @param zipStream The input stream of the ZIP archive.
	 * @return A byte array containing the read chunk (might be smaller than CHUNK_SIZE at the end of the stream).
	 * @throws IOException If an I/O error occurs during the read operation.
	 */
	private byte[] readChunk(InputStream zipStream) throws IOException {
		byte[] buffer = new byte[CHUNK_SIZE];
		int bytesRead = zipStream.read(buffer);
		if (bytesRead == -1) {
			return new byte[0]; // Kein weiterer Inhalt vorhanden.
		}
		return Arrays.copyOf(buffer, bytesRead); // Rückgabe mit tatsächlicher Größe.
	}


}
