<?php
namespace App;

/**
 * The class is used for collecting and managing messages during certain operations (for general purpose).
 * @author Gabriel
 */
class MessagesCollector
{

	/**
	 * The collected messages - an array of \App\Message objects.
	 * @var \App\Message[]
	 */
	protected $messages = [];

	/**
	 * Associative array that indicates if the current message collector contains a certain type of message.
	 * @var array
	 */
	protected $hasType = [];

	/**
	 * This is just to stop phpstan showing errors regarding new static issues
	 */
	final public function __construct()
	{ }

	/**
	 * Static method for instantiating a new messages collector object.
	 * @return \App\MessagesCollector
	 */
	static public function create()
	{
		return new static();
	}

	/**
	 * Serialize the object.
	 * @return string
	 */
	public function serialize() // TODO: used or not?
	{
		return serialize( ['messages'=>$this->messages, 'hasType' => $this->hasType] );
	}

	/**
	 * Unserialize the object.
	 * @param String $serialized
	 */
	public function unserialize($serialized) // TODO: used or not?
	{
		$arr = unserialize($serialized);
		$this->messages = $arr['messages'];
		$this->hasType = $arr['hasType'];

		return $this;
	}

	/**
	 * Adds a new message to the internal messages collector.
	 * It also sets the hasType flag.
	 * @param \App\Message $message
	 */
	public function addMessage(\App\Message $message)
	{
		$this->messages[] = $message;
		$this->hasType[$message->getType()] = true;
		return $this;
	}

	/**
	 * Adds an error message.
	 * @param String $message
	 */
 	public function addError($text, $code=null)
	{
		return $this->addMessage( \App\Message::create('error', $text, $code) );
	}

	/**
	 * Adds a warning message.
	 * @param String $message
	 */
	public function addWarning($text, $code=null)
	{
		return $this->addMessage( \App\Message::create('warning', $text, $code) );
	}

	/**
	 * Adds an info message.
	 * @param String $message
	 */
	public function addInfo($text, $code=null)
	{
		return $this->addMessage( \App\Message::create('info', $text, $code) );
	}

	/**
	 * Adds a success message.
	 * @param String $message
	 */
	public function addSuccess($text, $code=null)
	{
		return $this->addMessage( \App\Message::create('success', $text, $code) );
	}

	/**
	 * Indicates if the current messages contain at leats one error message.
	 * @return boolean
	 */
	public function hasError()
	{
		return (bool) $this->hasType['error'];
	}

	/**
	 * Indicates if the current executing operation must be stopped (it has a warning or an error message).
	 * @return Boolean
	 */
	public function hasErrorOrWarning()
	{
		return ($this->hasType['error'] || $this->hasType['warning']);
	}


	/**
	 * Returns the messages stored inside the object.
	 * @return \App\Message[]
	 */
	public function getMessages()
	{
		return $this->messages;
	}


	/**
	 * Groups messages by type
	 * @return \App\MessagesCollector
	 */
	public function groupMessagesByType()
	{
		$groupedMessages = [];
		foreach($this->messages as $msg) {
			$msgKey = $msg->getText();
			if(!$groupedMessages[$msg->getType()]) {

				$groupedMessages[$msg->getType()] = [$msgKey=>1];
			}
			else {
				$groupedMessages[$msg->getType()][$msgKey]++;
			}
		}

		$messages = [];
		foreach($groupedMessages as $type=>$messageList) {
			$msg = '';
			foreach($messageList as $text=>$repeats) {
				$repeatTimes = ($repeats>1) ? ' (x'.$repeats.')' : '';
				$msg .= sprintf("%s%s\n", $text, $repeatTimes);
			}
			$messages[] = \App\Message::create($type,trim($msg));
		}
		$this->messages = $messages;
		return $this;
	}

	/**
	 * Groups messages by type and text
	 * @return \App\MessagesCollector
	 */
	public function groupMessagesByTypeAndText()
	{
		$groupedMessages = [];
		foreach ($this->messages as $mes) {
			$key = sprintf("%s-%s", $mes->getType(), $mes->getText());

			if (!$groupedMessages[$key]) {
				$groupedMessages[$key] = [];
			}

			$groupedMessages[$key][]= $mes;
		}

		$messages = [];
		foreach ($groupedMessages as $key=>$messageList) {
			if (count($messageList)>1) {
				$messages[]= \App\Message::create($messageList[0]->getType(), $messageList[0]->getText().' (x'.count($messageList).')');
			}
			else {
				$messages[]= $messageList[0];
			}
		}

		$this->messages = $messages;
		return $this;
	}


	/**
	 * Returns the count of messages stored internally
	 * @return int
	 */
	public function getMessagesCount()
	{
		return count($this->messages);
	}


	/**
	 * Returns the count of messages stored internally grouped by type
	 * @return int
	 */
	public function getMessagesCountByType($type)
	{
		$i = 0;
		foreach($this->messages as $msg) {
			if($msg->getType()==$type) {
				$i++;
			}
		}
		return $i;
	}



	/**
	 * Appends new messages from another message collector object to the current object. It can also
	 * format the appended messages using sprintf (%s must be present).
	 * @param \App\MessagesCollector $msgs
	 * @param string $format - %s must be present once
	 */
	public function appendMessages(\App\MessagesCollector $msgs, $format=null)
	{
		foreach ($msgs->getMessages() as $msg) {
			// set the new text
			if ($format and strpos($format, '%s')!==false) {
				$msg->setText( sprintf($format, $msg->getText()) );
			}

			// add this message to the list
			$this->addMessage($msg);
		}
		return $this;
	}


	/**
	 * Returns a bulk message text of all collected messages.
	 * Only certain types can be included/filtered.
	 * @param array $types
	 * @return String
	 */
	public function getMessageBulk(array $types=[]) :string
	{
		$messages = [];
		foreach($this->messages as $msg) {
			if (!$types or in_array($msg->getType(), $types)) {
				$messages[]=$msg->getTextWithField();
			}
		}
		return implode(' ', $messages);
	}


	/**
	 * Eliminates the success messages.
	 */
	public function removeSuccess()
	{
		foreach ($this->messages as $idx=>$msg) {
			if ($msg->getType()=='success') {
				unset($this->messages[$idx]);
			}
		}
		$this->hasType['success'] = false;

		return $this;
	}

	/**
	 * Eliminates messages by callback filtering
	 */
	public function removeByCallback($filterCb)
	{
		foreach ($this->messages as $idx=>$msg) {
			if ($filterCb($msg, $idx)) {
				unset($this->messages[$idx]);
			}
		}

		$this->hasType = [];
		foreach ($this->messages as $msg) {
			$this->hasType[$msg->getType()] = true;
		}

		return $this;
	}

}
