<?php

namespace MightyPork\Utils;
use Lang;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\IWriter;

/**
 * Abstraction above PhpOffice's spreadsheets with stable API.
 * Fixing incompatibilities in one place will fix them in the entire application.
 */
class SpreadsheetBuilder
{
	private $activeSheetNum = -1;
	private $sheetCount = 0;

	/** @var Spreadsheet Spreadsheet */
	private $book = null;

	/** @var Worksheet */
	private $activeSheet = null;

	/** @var string - document title */
	private $title;

	public function __construct($title, $subject = '', $creator='PhpOffice Spreadsheet')
	{
		$this->title = $title;
		$this->book = new Spreadsheet();

		$this->book->getProperties()
			->setCreator($creator)
			->setTitle($title)
			->setSubject($subject)
			->setDescription('Created ' . date('r'));
	}

	/**
	 * @return string
	 */
	public function getTitle()
	{
		return $this->title;
	}

	/**
	 * Add a sheet and select it as active.
	 * Returns sheet index.
	 *
	 * @param $label - sheet label
	 * @return int - new sheet number, 0-based
	 */
	public function addSheet($label)
	{
		if ($this->activeSheetNum == -1) {
			// PhpOffice workbook has one sheet by default,
			// we require user to 'add' it before using it
			$this->sheetCount = 1;
			$this->activeSheet = $this->book->setActiveSheetIndex(0);
			$this->activeSheetNum = 0;
		}
		else {
			$this->sheetCount++;
			$this->activeSheet = $this->book->addSheet(new Worksheet());
			$this->activeSheetNum = $this->sheetCount-1;
		}

		$this->activeSheet->setTitle($label);
		return $this->activeSheetNum;
	}

	/**
	 * Select a sheet for cell modifications
	 *
	 * @param int $number - sheet index, 0-based
	 */
	public function selectSheet($number)
	{
		$this->activeSheet = $this->book->setActiveSheetIndex($number);
		$this->activeSheetNum = $number;

	}

	/**
	 * Set a cell value in the current sheet
	 *
	 * @param int $row - 0-based row
	 * @param int $column - 0-based column
	 * @param mixed $value - value to set
	 */
	public function setValue($row, $column, $value)
	{
		$this->activeSheet->setCellValueByColumnAndRow($column+1, $row+1, $value);
	}

	/**
	 * Set a row of values
	 *
	 * @param int $row - 0-based row
	 * @param int $startcol - first column to fill
	 * @param array $values - array of values to write into the row
	 */
	public function setRow($row, $startcol, $values)
	{
		$i = 0;
		foreach ($values as $value) {
			$this->setValue($row, $startcol + $i, $value);
			$i++;
		}
	}

	/**
	 * Set a row of values
	 *
	 * @param int $startrow - 0-based start row
	 * @param int $startcol - 0-based start column
	 * @param array $values - array of row vectors ([ [first,row], [second,row], ...]
	 */
	public function setGrid($startrow, $startcol, $values)
	{
		foreach ($values as $row => $row_values) {
			$this->setRow($startrow + $row, $startcol, $row_values);
		}
	}

	/**
	 * Make a cell a given style
	 *
	 * @param int $row - 0-based row
	 * @param int $column - 0-based column
	 */
	private function setStyle($row, $column, $array)
	{
		$coord = Coordinate::stringFromColumnIndex($column+1) . ($row+1);

		$this->activeSheet->getStyle($coord)
			->applyFromArray($array);
	}

	/**
	 * Make an area of cells a given style
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 */
	private function setStyleArea($startrow, $startcol, $rows, $cols, $array)
	{
		$coord = Coordinate::stringFromColumnIndex($startcol+1) . ($startrow+1) .
			':' . Coordinate::stringFromColumnIndex($startcol+$cols) . ($startrow+$rows);

		$this->activeSheet->getStyle($coord)
			->applyFromArray($array);
	}

	/**
	 * Make a cell bold
	 *
	 * @param int $row - 0-based row
	 * @param int $column - 0-based column
	 */
	public function setBold($row, $column)
	{
		$this->setStyle($row, $column, ['font' => ['bold' => 'true']]);
	}

	/**
	 * Make an area of cells bold
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 */
	public function setBoldArea($startrow, $startcol, $rows, $cols)
	{
		$this->setStyleArea($startrow, $startcol, $rows, $cols, ['font' => ['bold' => 'true']]);
	}

	/**
	 * Make a cell italic
	 *
	 * @param int $row - 0-based row
	 * @param int $column - 0-based column
	 */
	public function setItalic($row, $column)
	{
		$this->setStyle($row, $column, ['font' => ['italic' => 'true']]);
	}

	/**
	 * Make an area of cells italic
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 */
	public function setItalicArea($startrow, $startcol, $rows, $cols)
	{
		$this->setStyleArea($startrow, $startcol, $rows, $cols, ['font' => ['italic' => 'true']]);
	}

	private function convertColor($color)
	{
		if ($color[0] == '#') $rgb = substr($color, 1);
		else {
			$map = [
				'black' => '000000',
				'gray' => '808080',
				'silver' => 'C0C0C0',
				'white' => 'FFFFFF',
				'maroon' => '800000',
				'red' => 'FF0000',
				'olive' => '808000',
				'yellow' => 'FFFF00',
				'green' => '008000',
				'lime' => '00FF00',
				'teal' => '008080',
				'aqua' => '00FFFF',
				'navy' => '000080',
				'blue' => '0000FF',
				'purple' => '800080',
				'fuchsia' => 'FF00FF',
			];

			if (isset($map[$color])) {
				$rgb = $map[$color];
			} else {
				throw new \LogicException("Bad color format $color");
			}
		}

		return ['rgb' => $rgb];
	}

	/**
	 * Set cell colors
	 *
	 * @param int $row    - 0-based row
	 * @param int $column - 0-based column
	 * @param string $text - foreground
	 * @param string $background - background
	 */
	public function setColors($row, $column, $text = null, $background = null)
	{
		$this->setColorsArea($row, $column, 1, 1, $text, $background);
	}

	/**
	 * Set colors of an area of cells
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 * @param string $text - foreground
	 * @param string $background - background
	 */
	public function setColorsArea($startrow, $startcol, $rows, $cols, $text = null, $background = null)
	{
		$ar = [];

		if ($text !== null) {
			$ar['font'] = [
				'color' => $this->convertColor($text),
			];
		}

		if ($background !== null) {
			$ar['fill'] = [
				'fillType' => Fill::FILL_SOLID,
				'color' => $this->convertColor($background),
			];
		}

		$this->setStyleArea($startrow, $startcol, $rows, $cols, $ar);
	}

	/**
	 * Merge cells
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 */
	public function mergeCells($startrow, $startcol, $rows, $cols)
	{
		$coord = Coordinate::stringFromColumnIndex($startcol+1) . ($startrow+1) .
			':' . Coordinate::stringFromColumnIndex($startcol+$cols) . ($startrow+$rows);

		$this->activeSheet->mergeCells($coord);
	}

	/**
	 * Merge row cells
	 *
	 * @param $startrow - 0-based first row
	 * @param $startcol - 0-based first column
	 * @param $rows     - number of rows
	 * @param $cols     - number of columns
	 */
	public function mergeRowCells($startrow, $startcol, $cols)
	{
		$this->mergeCells($startrow, $startcol, 1, $cols);
	}

	/**
	 * Set a column's width
	 *
	 * @param int $column - 0-based column number
	 * @param int $width - width in pixels
	 */
	public function setColumnWidth($column, $width)
	{
		$this->activeSheet->getColumnDimensionByColumn($column+1)->setWidth($width);
	}

	/**
	 * Prepare writer and resolve mime type for export
	 *
	 * @param string $format - one of : xls, xlsx, csv, ods
	 * @return array (mime, writer)
	 */
	private function prepareExport($format = 'xlsx')
	{
		switch ($format) {
			case 'xls':
				$writerName = 'Xls';
				$mimeType = 'application/vnd.ms-excel';
				break;

			case 'xlsx':
				$writerName = 'Xlsx';
				$mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
				break;

			case 'ods':
				$writerName = 'Ods';
				$mimeType = 'application/vnd.oasis.opendocument.spreadsheet';
				break;

			default:
			case 'csv':
				$writerName = 'Csv';
				$mimeType = 'text/csv';
				break;
		}

		// Set active sheet index to the first sheet, so Excel opens this as the first sheet
		$this->book->setActiveSheetIndex(0);
		$writer = \PhpOffice\PhpSpreadsheet\IOFactory::createWriter($this->book, $writerName);

		return [$mimeType, $writer];
	}

	/**
	 * Export and send to browser as a response for user to download
	 *
	 * @param string $base_filename - filename without suffix
	 * @param string $format - one of : xls, xlsx, csv, ods
	 */
	public function exportToBrowser($base_filename, $format = 'xlsx')
	{
		list($mimeType, $writer) = $this->prepareExport($format);

		/** @var IWriter $writer */
		$base_filename .= '.' . $format;

		ob_end_clean();

		// Redirect output to a client’s web browser (Xlsx)
		header("Content-Type: $mimeType; charset=utf-8");
		header("Content-Disposition: attachment;filename=\"$base_filename\"");
		header('Content-Language: ' . Lang::locale());

		// Cache headers

		header('Cache-Control: max-age=0');
		// IE9
		header('Cache-Control: max-age=1');
		// Other IE headers
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
		header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
		header('Pragma: no-cache'); // HTTP/1.0

		$writer->save('php://output');
		exit;
	}

	/**
	 * Export and send to browser as a response for user to download
	 *
	 * @param string $base_filename - filename without suffix
	 * @param string $format        - one of : xls, xlsx, csv, ods
	 * @return string - the mime type
	 */
	public function exportToFile($base_filename, $format = 'xlsx', $path='/tmp')
	{
		list($mimeType, $writer) = $this->prepareExport($format);

		/** @var IWriter $writer */
		$writer->save($path . DIRECTORY_SEPARATOR . "$base_filename.$format");

		return $mimeType;
	}
}