<?php
/**
 * @file
 * @author Niklas Laxström
 * @license GPL-2.0-or-later
 */

use MediaWiki\Extension\Translate\MessageGroupProcessing\MessageGroups;
use MediaWiki\Extension\Translate\MessageLoading\MessageCollection;
use MediaWiki\Extension\Translate\MessageLoading\MessageDefinitions;
use MediaWiki\Extension\Translate\MessageLoading\MessageHandle;
use MediaWiki\Extension\Translate\MessageProcessing\StringMatcher;
use MediaWiki\Extension\Translate\Services;
use MediaWiki\Title\Title;

/**
 * Groups multiple message groups together as one group.
 *
 * Limitations:
 *  - Only groups in the same namespace.
 *  - Only groups with the same source language.
 * @ingroup MessageGroup
 */
class AggregateMessageGroup extends MessageGroupBase {
	public const UNDETERMINED_LANGUAGE_CODE = 'und';
	/** @var MessageGroup[] */
	private $groups;

	/** @inheritDoc */
	public function exists() {
		// Group exists if there are any subgroups.
		return (bool)$this->conf['GROUPS'];
	}

	/** @inheritDoc */
	public function load( $code ) {
		$messages = [];

		foreach ( $this->getGroups() as $group ) {
			$messages += $group->load( $code );
		}

		return $messages;
	}

	/** @inheritDoc */
	public function getMangler() {
		if ( $this->mangler === null ) {
			$this->mangler = new StringMatcher();
		}

		return $this->mangler;
	}

	/**
	 * Returns a list of message groups that this group consists of.
	 * @return MessageGroup[]
	 */
	public function getGroups(): array {
		if ( $this->groups === null ) {
			$groups = [];
			$ids = (array)$this->conf['GROUPS'];
			$ids = MessageGroups::expandWildcards( $ids );

			foreach ( $ids as $id ) {
				// Do not try to include self and go to infinite loop.
				if ( $id === $this->getId() ) {
					continue;
				}

				$group = MessageGroups::getGroup( $id );
				if ( $group === null ) {
					error_log( "Invalid group id in {$this->getId()}: $id" );
					continue;
				}

				if ( MessageGroups::getPriority( $group ) === 'discouraged' ) {
					continue;
				}

				$groups[$id] = $group;
			}

			$this->groups = $groups;
		}

		return $this->groups;
	}

	protected function loadMessagesFromCache( array $groups ): array {
		$messages = [];
		foreach ( $groups as $group ) {
			if ( $group instanceof self ) {
				$messages += $this->loadMessagesFromCache( $group->getGroups() );
				continue;
			}

			if ( $group instanceof FileBasedMessageGroup ) {
				$cache = $group->getMessageGroupCache( $group->getSourceLanguage() );
				if ( $cache->exists() ) {
					foreach ( $cache->getKeys() as $key ) {
						$messages[$key] = $cache->get( $key );
					}
				}
				continue;
			}

			$messages += $group->getDefinitions();
		}

		return $messages;
	}

	/** @inheritDoc */
	public function initCollection( $code ) {
		$messages = $this->loadMessagesFromCache( $this->getGroups() );
		$namespace = $this->getNamespace();
		$definitions = new MessageDefinitions( $messages, $namespace );
		$collection = MessageCollection::newFromDefinitions( $definitions, $code );

		$this->setTags( $collection );

		return $collection;
	}

	/** @inheritDoc */
	public function getMessage( $key, $code ) {
		/* Just hand over the message content retrieval to the primary message
		 * group directly. This used to iterate over the subgroups looking for
		 * the primary group, but that might actually be under some other
		 * aggregate message group.
		 * @todo Implement getMessageContent to avoid hardcoding the namespace
		 * here.
		 */
		$title = Title::makeTitle( $this->getNamespace(), $key );
		$handle = new MessageHandle( $title );
		$groupId = Services::getInstance()->getMessageIndex()->getPrimaryGroupId( $handle );
		if ( $groupId === null ) {
			error_log( "Could not determine groupId for MessageHandle of key $key" );
			return null;
		}
		if ( $groupId === $this->getId() ) {
			// Message key owned by aggregate group.
			// Should not ever happen, but it does.
			error_log( "AggregateMessageGroup $groupId cannot be primary owner of key $key" );

			return null;
		}

		$group = MessageGroups::getGroup( $groupId );
		if ( $group ) {
			return $group->getMessage( $key, $code );
		} else {
			return null;
		}
	}

	/** @inheritDoc */
	public function getTags( $type = null ) {
		$tags = [];

		foreach ( $this->getGroups() as $group ) {
			$tags = array_merge_recursive( $tags, $group->getTags( $type ) );
		}

		return $tags;
	}

	/** @inheritDoc */
	public function getKeys() {
		$keys = [];
		foreach ( $this->getGroups() as $group ) {
			// Array merge is *really* slow (tested in PHP 7.1), so avoiding it. A loop
			// followed by array_unique (which we need anyway) is magnitudes faster.
			foreach ( $group->getKeys() as $key ) {
				$keys[] = $key;
			}
		}

		return array_values( array_unique( $keys ) );
	}

	/** @inheritDoc */
	public function getSourceLanguage(): string {
		// Return undetermined language code (und) if no language is defined for the web configured
		// aggregate group
		return $this->conf['BASIC']['sourcelanguage'] ?? self::UNDETERMINED_LANGUAGE_CODE;
	}
}
