<?php 

require_once (LIB_PATH.'XLSXWriter'.SLASH.'XLSXWriter.class.php');

class ExcelExport
{

	public static $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
	public static $extension = 'xlsx';
	public static $description = 'Fichier classeur de données Excel';

	public static function sample($dataset){
		foreach($dataset as $macro => $infos) {
			$data[]['Macros disponibles :'] = ($infos['type']=='list') ? '{{#'.$macro.'}}{{/'.$macro.'}} : '.$infos['desc'] : '{{'.$macro.'}} : '.$infos['desc'];
		}
		$stream = Excel::exportArray($data, null ,'Sans titre');
		return  $stream;
	}	

	//Remplacement du tag d'image par l'image concernée
	//dans le fichier modèle
	public static function add_image($worksheet, $macro, $value, $cellCoord, $scale=2){
		$value = substr($value,2);
		//On supprime le tag
		$cellVal = str_replace('{{'.$macro.'}}', '', $worksheet->getCell($cellCoord)->getValue());
		$worksheet->getCell($cellCoord)->setValue($cellVal);

		preg_match_all('~[A-Z]+|\d+~', $cellCoord, $cellMatches);
		$cellMatches = reset($cellMatches);
		$cellIndex = $cellMatches[0];
		$rowIndex = $cellMatches[1];

		//On récupère les infos de l'image
		list($width, $height) = getimagesize($value);

		//On définit la taille de la cellule à celle de l'image
		$cellWidth = $worksheet->getColumnDimension($cellIndex);
		$rowHeight = $worksheet->getRowDimension($rowIndex);
		//La largeur d'une colonne est définie en unité (Microsoft). 1px correpond à peu près à 0.2 unité
		$cellWidth->setWidth(($width/$scale)*0.2);
		//0.75 correspond à 1px en pt, et la hauteur d'une ligne est définie en pt
		$rowHeight->setRowHeight(($height/$scale)*0.75);

		//On ajoute l'image dans la feuille
		$drawing = new \PhpOffice\PhpSpreadsheet\Worksheet\Drawing();
		$drawing->setName($macro);
		$drawing->setPath($value);
		$drawing->setResizeProportional(true);
		$drawing->setWidthAndHeight($width/$scale,$height/$scale);
		$drawing->setCoordinates($cellCoord);
		$drawing->setWorksheet($worksheet);
	}

	//Remplace les données d'un jeu de données en fonction
	//des tags rencontrés et fournis par le jeu de données
	public static function replace_data($worksheet, $data=array(), $cellCoord, $cellContent, $imgScale=2){
		//Pour chaque élément de l'entité
		foreach ($data as $tag => $value) {
			if(strpos($cellContent, '{{'.$tag.'}}') !== false){
				$cellVal = $worksheet->getCell($cellCoord)->getValue();
				//Ajout des images
				if(substr($value,0,2)=='::'){
					ExcelExport::add_image($worksheet, $tag, $value, $cellCoord, $imgScale);
					continue;
				}
				if(is_numeric($value) || preg_match("/\d+(?:\.\d{1,2})? [€,$,£,₽,¥]/",$value))
					$worksheet->getStyle($cellCoord)->getNumberFormat()->setFormatCode(PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER);

				$cellVal = str_replace('{{'.$tag.'}}', $value, $cellVal);
				$worksheet->getCell($cellCoord)->setValue($cellVal);
			}
		}
	}

	//Récupère et gère la structure du remplacement
	//des données dans le fichier template fourni
	public static function from_template($source, $data, $return){
		require(LIB_PATH.'PhpSpreadsheet'.SLASH.'vendor'.SLASH.'autoload.php');

		$destination = File::dir().'tmp'.SLASH.'template.'.time().'-'.rand(0,100).'.xlsx';
		$spreadsheet = \PhpOffice\PhpSpreadsheet\IOFactory::load(File::dir().$source);
		$spreadsheet->getActiveSheet()->getPageMargins()->setTop(0.2);
		$spreadsheet->getActiveSheet()->getPageMargins()->setRight(0.2);
		$spreadsheet->getActiveSheet()->getPageMargins()->setLeft(0.2);
		$spreadsheet->getActiveSheet()->getPageMargins()->setBottom(0.2);
		$spreadsheet->getActiveSheet()->getPageMargins()->setFooter(0.5);
		$spreadsheet->getActiveSheet()->getPageMargins()->setHeader(0.5);
		$spreadsheet->getActiveSheet()->getPageSetup()->setHorizontalCentered(true);

		//Pour chaque feuille dans le classeur Excel
		foreach ($spreadsheet->getAllSheets() as $wrkSheetIdx => $worksheet) {
			//On récupère la zone de travail (pour ne pas se perdre dans des cellules vide)
			//Avec la colonne max et la ligne max
			$maxCol = 'A';
			$maxRow = 0;
			foreach ($worksheet->getCoordinates() as $coord) {
				preg_match_all('~[A-Z]+|\d+~', $coord, $matches);
				$matches = reset($matches);
				$currCol = PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($matches[0]);
				$currRow = $matches[1];

				if($maxCol < $currCol)
					$maxCol = $currCol;

				if($maxRow < $currRow)
					$maxRow = $currRow;
			}

			//On parcours une fois le contenu du la feuille
			//pour avoir les différents bords des boucles, si
			//il y en a dans le fichier.
			$rows = $worksheet->toArray('', true, true, true);
			$loopDatas = array('startLine' => 0,'endLine' => 0);
			$finalValues = array();
			$loopStart = $loopEnd = false;
			foreach ($rows as $rowIdx => $cell) {
				foreach ($cell as $cellIdx => $content) {
					if(empty($content) && PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($cellIdx)>$maxCol) continue;

					foreach ($data as $macro => $value) {
						if(!is_array($value)) continue;
						$entityCount = count($value);

						if(strpos($content, '{{/'.$macro.'}}') !== false) {
							$loopDatas['endLine'] = $rowIdx;
							$loopDatas['endColumn'] = $cellIdx;
							$loopDatas['endCoord'] = $cellIdx.$rowIdx;
							$loopDatas['totalRow'] = $loopDatas['endLine'] - $loopDatas['startLine'];
							$loopDatas['totalCol'] = letters_to_numbers($loopDatas['endColumn']) - letters_to_numbers($loopDatas['startColumn']);
							$loopEnd = true;
						}

						if(!$loopEnd && strpos($content, '{{#'.$macro.'}}') !== false) {
							$loopDatas['startLine'] = $rowIdx;
							$loopDatas['startColumn'] = $cellIdx;
							$loopDatas['startCoord'] = $cellIdx.$rowIdx;
							$loopStart = true;
						}
					}
				}
			}
			//Définition type de boucle
			if($loopDatas['startLine'] != 0 && $loopDatas['endLine'] != 0)
				$loopDatas['loopType'] = ($loopDatas['startLine'] != $loopDatas['endLine']) ? 'vertical' : 'horizontal';

			//Récupération des infos en fonction du type de boucle
			if(isset($loopDatas['loopType'])){
				if($loopDatas['loopType'] == 'vertical'){
					//On parcourt par ligne puis par colonne
					foreach ($rows as $rowIdx => $cell) {
						foreach ($cell as $cellIdx => $content) {
							if($rowIdx > $loopDatas['startLine'] && $rowIdx < $loopDatas['endLine'])
								$finalValues[$rowIdx][$cellIdx][] = $content;
						}
					}
				} else {
					//On parcourt par colonne puis par ligne
					for($col = $loopDatas['startColumn']; $col <= $loopDatas['endColumn']; ++$col){
						if($col == $loopDatas['startColumn'] || $col == $loopDatas['endColumn']) continue;
						for($row = $loopDatas['startLine']; $row <= $maxRow; ++$row) {
						    $content = $worksheet->getCell($col.$row)->getValue();
							$finalValues[$col][$row][] = $content;
						}
					}
				}
			}

			//On remplace les données à l'intérieur
			//des boucles si des boucles sont présentes
			if(isset($finalValues) && !empty($finalValues)){

				//Pour chaque value du jeu de données
				foreach ($data as $macro => $value) {
					if(!is_array($value)) continue;
					$worksheet->getCell($loopDatas['startCoord'])->setValue('');
					$worksheet->getCell($loopDatas['endCoord'])->setValue('');

					//Pour chaque entité
					foreach ($value as $i => $entity) {
						$rowIterator = $colIterator = 0;
						if($loopDatas['loopType'] == 'vertical'){
							unset($finalValues[$loopDatas['endLine']]);

							//Pour chaque ligne
							foreach ($finalValues as $rIdx => $cell) {
								$lineIdx = ($loopDatas['startLine']+1)+(($loopDatas['totalRow']-1)*$i)+$rowIterator;
								//On ajoute 1 car on insère AVANT la ligne, et non APRÈS
								$rowWhereInsert = $i==0 ? $loopDatas['endLine']+1 : $rowWhereInsert += 1;
								$worksheet->insertNewRowBefore($rowWhereInsert, 1);

								//Pour chaque cellule
								foreach ($cell as $cIdx => $content) {
									$currCoord = $cIdx.$lineIdx;
									$lineRef = $lineIdx-($loopDatas['totalRow']-1);
									//Dans le cas où $i vaut 0, cas particulier, on remplace directement les données dans la feuille
									$referentCell = $i==0 ? $currCoord : $cIdx.$lineRef;

									$worksheet->setCellValue($currCoord, $content[0]);
									$worksheet->duplicateStyle($worksheet->getStyle($referentCell),$currCoord);

									//On remplace les données
									ExcelExport::replace_data($worksheet, $entity, $currCoord, $content[0], 4);
								}
								$rowIterator++;
							}
						} else {
							//Pour chaque colonne
							foreach ($finalValues as $cIdx => $col) {
								$colIdx = numbers_to_letters((letters_to_numbers($loopDatas['startColumn'])+1)+(($loopDatas['totalCol']-1)*$i)+$colIterator);

								//On ajoute 1 car on insère AVANT la ligne, et non APRÈS
								$colWhereInsert = $i==0 ? numbers_to_letters(letters_to_numbers($loopDatas['endColumn'])+1) : numbers_to_letters(letters_to_numbers($colWhereInsert)+1);
								$worksheet->insertNewColumnBefore($colWhereInsert, 1);

								//Pour chaque cellule
								foreach ($col as $rIdx => $content) {
									$currCoord = $colIdx.$rIdx;
									//Dans le cas où $i vaut 0, cas particulier, on remplace directement les données dans la feuille
									$referentCell = $i==0 ? $currCoord : $cIdx.$rIdx;
									
									$worksheet->setCellValue($currCoord, $content[0]);
									$worksheet->duplicateStyle($worksheet->getStyle($referentCell),$currCoord);

									//On remplace les données
									ExcelExport::replace_data($worksheet, $entity, $currCoord, $content[0], 4);
								}
								$colIterator++;
							}
						}
					}
				}
			}

			//On remplace le reste des tag présents
			//sur la feuille de calcul du fichier template
			foreach ($worksheet->getRowIterator() as $row) {
				$cellIterator = $row->getCellIterator();

				foreach ($cellIterator as $cell) {
					$cellVal = $cell->getValue();
					$cellIndex = $cell->getColumn();
					if(empty($cellVal) && PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($cellIndex)>$maxCol) continue;

					foreach ($data as $macro => $value) {
						if(is_array($value)) continue;

						//Ajout des valeurs
						if(strpos($cellVal, '{{'.$macro.'}}') !== false){
							//Ajout des images
							if(substr($value,0,2)=='::'){
								ExcelExport::add_image($worksheet, $macro, $value, $cell->getCoordinate(), 2);
								continue;
							}
							
							if(is_numeric($value)) 
								$worksheet->getStyle($cell->getCoordinate())->getNumberFormat()->setFormatCode(PhpOffice\PhpSpreadsheet\Style\NumberFormat::FORMAT_NUMBER_00); 
							$cellVal = str_replace('{{'.$macro.'}}', $value, $cellVal);
							$cell->setValue($cellVal);
						}
					}
				}
			}
		}

		$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx');
		$writer->save($destination);

		if($return!='stream') return $destination;

		$stream = file_get_contents($destination);
		unlink($destination);
		return $stream;
	}

	//Copie l'intégralité de la ligne depuis des 
	//positions données à l'endroit voulu
	public static function copy_full_row(&$ws_from, &$ws_to, $row_from, $row_to) {
		$ws_to->getRowDimension($row_to)->setRowHeight($ws_from->getRowDimension($row_from)->getRowHeight());
		$lastColumn = $ws_from->getHighestColumn();
		++$lastColumn;
		for($c = 'A'; $c != $lastColumn; ++$c) {
			$cell_from = $ws_from->getCell($c.$row_from);
			$cell_to = $ws_to->getCell($c.$row_to);
			$cell_to->setXfIndex($cell_from->getXfIndex()); // black magic here
			$cell_to->setValue($cell_from->getValue());
		}
	}
}

