<?php

namespace JetBackup\Destination\Vendors\S3;

use JetBackup\Destination\Integration\DestinationChunkedUpload;
use JetBackup\Destination\Vendors\S3\Client\ClientManager;
use JetBackup\Destination\Vendors\S3\Client\Exception\ClientException;
use JetBackup\Exception\IOException;
use JetBackup\Web\File\FileChunk;

if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');

class ChunkedUpload implements DestinationChunkedUpload {

	private ClientManager $_client;
	private string $_destination;
	private object $_data;
	private ?array $_parts=null;
	private int $_currentPart=0;

	public function __construct(ClientManager $client, $destination) {
		$this->_client = $client;
		$this->_destination = $destination;
		$this->_data = new \stdClass();
	}
	
	/**
	 * @return object
	 * @throws ClientException
	 */
	public function prepare():object {
		$output = new \stdClass();
		$output->upload_id = $this->_client->createUploadID($this->_destination);
		return $output;
	}

	public function setData(object $data):void {
		$this->_data = $data;		
	}

	/**
	 * @throws IOException
	 * @throws ClientException
	 */
	public function _loadParts() {
		if(!isset($this->_data->upload_id)) throw new IOException("No upload id was found");
		if($this->_parts !== null) return;
		$this->_parts = [];
		$partNumberMarker = null;

		do {
			$parts = $this->_client->listUploadParts($this->_destination, $this->_data->upload_id, $partNumberMarker);
			if(!isset($parts->Body->Part)) break;

			foreach($parts->Body->Part as $part_details) {
				$part = new \stdClass();
				$part->number = (int) $part_details->PartNumber;
				$part->etag = trim($part_details->ETag, '"');
				$part->size = (int) $part_details->Size;
				$this->_parts[] = $part;
			}

			$isTruncated = isset($parts->Body->IsTruncated) && (string) $parts->Body->IsTruncated === 'true';
			if($isTruncated && isset($parts->Body->NextPartNumberMarker)) {
				$partNumberMarker = (int) $parts->Body->NextPartNumberMarker;
			} else {
				$isTruncated = false;
			}
		} while($isTruncated);
	}
	
	/**
	 * @return int
	 * @throws IOException|ClientException
	 */
	public function getOffset():int {
		$this->_loadParts();
		$offset = 0;
		foreach($this->_parts as $part) $offset += $part->size;
		return $offset;
	}

	/**
	 * @return int|null
	 */
	public function getChunkSize():?int { return null; }
	
	/**
	 * @param FileChunk $chunk
	 *
	 * @return void
	 * @throws IOException|ClientException
	 */
	public function upload(FileChunk $chunk):void {
		if(!isset($this->_data->upload_id)) throw new IOException("No upload id was found");
		$this->_loadParts();
		$this->_currentPart = sizeof($this->_parts);
		$this->_currentPart++;
		$result = $this->_client->putChunk($chunk, $this->_destination, $this->_data->upload_id, $this->_currentPart);

		$part = new \stdClass();
		$part->number = $this->_currentPart;
		$part->etag = trim($result->Headers->etag, '"');
		$part->size = $chunk->getSize();
		
		$this->_parts[] = $part;
	}

	/**
	 * @return void
	 * @throws IOException|ClientException
	 */
	public function finalize():void {
		if(!isset($this->_data->upload_id)) throw new IOException("No upload id was found");
		$this->_loadParts();
		$xml = new \SimpleXMLElement('<CompleteMultipartUpload/>');
		foreach($this->_parts as $part_details) {
			$part = $xml->addChild('Part');
			$part->addChild('PartNumber', $part_details->number);
			$part->addChild('ETag', $part_details->etag);
		}
		$this->_client->closeChunkedUpload($this->_destination, $this->_data->upload_id, $xml->asXML());
	}
}