<?php
namespace App\Utils\CSV\Reader;

class RowValidator
{
	/**
	 * The list of expected columns
	 * @var array
	 */
	protected $expectedColumns = null;

	/**
	 * How to handle the list of expected columns
	 * @var string: strict, allow-more, allow-none
	 */
	protected $expectedColumns_type = "strict";

	/**
	 * The parent object
	 * @var \App\Utils\CSV\Reader\AbstractReader
	 */
	protected $spreadsheet = null;

	/**
	 * For a detailed formatting documentaion, see \App\Utils\CSV\Reader\RowFormatter->columnFormats
	 * @var array
	 */
	protected $columnFormats = [];

	/**
	 * Build the validator object
	 * @param \App\Utils\CSV\Reader\AbstractReader $spreadsheet
	 */
	public function __construct(\App\Utils\CSV\Reader\AbstractReader $spreadsheet)
	{
		$this->spreadsheet = $spreadsheet;
	}

	public function __destruct()
	{
		$this->spreadsheet = null;
	}

	/**
	 * Set the lsit of expected columns
	 * @param array $expectedColumns
	 * @param string $expectedColumns_type
	 */
	public function setExpectedColumns(array $expectedColumns, string $expectedColumns_type='strict')
	{
		$this->expectedColumns = 		$expectedColumns;
		$this->expectedColumns_type = 	$expectedColumns_type;
	}

	/**
	 * Get the list of expected columns
	 * @return array
	 */
	public function getExpectedColumns()
	{
		return $this->expectedColumns;
	}

	/**
	 * Set the column format. Used for cell-validation
	 * @param array $columnFormats
	 * @return $this
	 */
	public function setColumnFormats($columnFormats)
	{
		$this->columnFormats = $columnFormats;
		return $this;
	}

	/**
	 * Get the defined columns formats
	 * @return array
	 */
	public function getColumnFormats()
	{
		return $this->columnFormats;
	}

	/**
	 * Validate a cell value. Throws exception on invalid values
	 * @param mixed $value
	 * @param string $rawValue
	 * @param string $field
	 * @param \App\Utils\CSV\Reader\Row $row
	 */
	public function validate($value, string $rawValue, string $field, \App\Utils\CSV\Reader\Row $row)
	{
		$cfg = $this->columnFormats[$field];

		switch ($cfg['type']) {
			case 'callback':
				if ($cfg['callback:validate']) {
					$cfg['callback:validate']($cfg, $value, $field, $row);
				}
				return;
			break;

			case 'S':
				// all strings are valid
			break;

			case 'N':
				if (!is_numeric($rawValue)) {
					throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid format on line {$row->getLineIndex()}");
				}
			break;

			case 'NI': case 'NI+': case 'NI-':
				if ($rawValue && !is_int($value)) {
					throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid format on line {$row->getLineIndex()}");
				}

				// only positive int (non zero)
				if ($cfg['type']=='NI+') {
					if ($value<=0) {
						throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid value on line {$row->getLineIndex()}");
					}
				}

				// only negative int(non zero)
				if ($cfg['type']=='NI-') {
					if ($value>+0) {
						throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid value on line {$row->getLineIndex()}");
					}
				}

			break;
			case 'NF': case 'NF+': case 'NF-':
				if ($rawValue && !is_float($value)) {
					throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid format on line {$row->getLineIndex()}");
				}

				// only positive floats (non zero)
				if ($cfg['type']=='NF+') {
					if ($value<=0) {
						throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid value on line {$row->getLineIndex()}");
					}
				}

				// only negative floats(non zero)
				if ($cfg['type']=='NF-') {
					if ($value>+0) {
						throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid value on line {$row->getLineIndex()}");
					}
				}
			break;

			case 'D':	// date format
				// if the preFormat wasn't able to detect and extract the date, then this is in an invalid format
				if ($rawValue && !$value) {
					throw new \Exception\Validator\InvalidValue("Field '{$field}', with value '{$value}' has an invalid format on line {$row->getLineIndex()}");
				}
			break;
		}

		if ($cfg['req']==='M') {
			if (!$value) {
				throw new \Exception\Validator\EmptyValue("Field '{$field}', with value '{$value}' has an invalid value on line {$row->getLineIndex()}");
			}
		}
	}

	/**
	 * Validate the whole row
	 * @param \App\Utils\CSV\Reader\Row $row
	 * @throws \Exception
	 */
	public function validateRow(\App\Utils\CSV\Reader\Row $row)
	{
		if ($this->expectedColumns!==null) {
			$rowData = $row->getRawFields();
			$totalExpectedColumns = count($this->expectedColumns);
			$matchedColumns = 0;

			foreach ($this->expectedColumns as $col) {
				if (isset($rowData[$col])) {
					$matchedColumns++;
				}
			}

			switch ($this->expectedColumns_type) {
				case 'strict':
					if ($matchedColumns!==$totalExpectedColumns) {
						throw new \Exception\Validator\MismatchedValue("It should have {$totalExpectedColumns} columns and i found {$matchedColumns} columns. Column names expected: ".implode(",", $this->expectedColumns));
					}
				break;

				case 'allow-more':
					if ($matchedColumns<$totalExpectedColumns) {
						throw new \Exception\Validator\MismatchedValue("It should have more than {$totalExpectedColumns} columns and i found {$matchedColumns} columns");
					}
				break;

				case 'allow-none':
					if ($matchedColumns!==$totalExpectedColumns && $matchedColumns!==0) {
						throw new \Exception\Validator\MismatchedValue("It should have either {$totalExpectedColumns} columns or 0 column. I found {$matchedColumns} columns");
					}
				break;

				default:
					throw new \Exception("invalid expectedColumns_type specified ('{$this->expectedColumns_type}' found)");
				break;
			}

		}

	}

}
