<?php

/**
 * Simple single value storage with a getter.
 */
class WP_Locale_Storage {

	/**
	 * @var string
	 */
	private $locale;

	/**
	 * Constructor. Stores the given locale.
	 *
	 * @param string $locale The locale.
	 */
	public function __construct( $locale ) {

		$this->locale = (string) $locale;
	}

	/**
	 * Returns the stored locale.
	 *
	 * @return string The stored locale.
	 */
	public function get() {

		return $this->locale;
	}
}

/**
 * Handles switching locales.
 */
class WP_Locale_Switcher {

	/**
	 * @var callback[]
	 */
	private $filters = array();

	/**
	 * @var string[]
	 */
	private $locales = array();

	/**
	 * @var string
	 */
	private $original_locale;

	/**
	 * @var NOOP_Translations[][]
	 */
	private $translations = array();

	/**
	 * Constructor. Stores the original locale.
	 */
	public function __construct() {

		$this->original_locale = get_locale();
	}

	/**
	 * Switches the translations according to the given locale.
	 *
	 * @param string $locale The locale.
	 */
	public function switch_to_locale( $locale ) {

		$this->locales[] = $locale;

		$current_locale = get_locale();
		if ( $current_locale === $locale ) {
			return;
		}

		global $l10n;

		$textdomains = array_keys( $l10n );

		if ( ! $this->has_translations_for_locale( $current_locale ) ) {
			foreach ( $textdomains as $textdomain ) {
				$this->translations[ $current_locale ][ $textdomain ] = get_translations_for_domain( $textdomain );
			}
		}

		$this->remove_filters();

		$this->add_filter_for_locale( $locale );

		if ( $this->has_translations_for_locale( $locale ) ) {
			foreach ( $textdomains as $textdomain ) {
				if ( isset( $this->translations[ $locale ][ $textdomain ] ) ) {
					$l10n[ $textdomain ] = $this->translations[ $locale ][ $textdomain ];
				}
			}
		} else {
			foreach ( $textdomains as $textdomain ) {
				if ( 'default' === $textdomain ) {
					load_default_textdomain();

					continue;
				}

				unload_textdomain( $textdomain );

				/**
				 * TODO: Retrieve the MO file for the current textdomain.
				 *
				 * The current problem is, as yoavf said already, that the textdomain (string) alone doesn't carry
				 * enough information to determine if we have to call `load_plugin_textdomain()`, or something else.
				 * This could be easily fixed if we were to store the file in `MO::import_from_file()`.
				 */
				load_textdomain( $textdomain, $mofile );

				$this->translations[ $locale ][ $textdomain ] = get_translations_for_domain( $textdomain );
			}
		}

		$GLOBALS['wp_locale'] = new WP_Locale();
	}

	/**
	 * Restores the translations according to the previous locale.
	 *
	 * @return string|false Locale on success, false on error.
	 */
	public function restore_locale() {

		if ( ! array_pop( $this->locales ) ) {
			// The stack is empty, bail.
			return false;
		}

		$this->remove_filters();

		if ( $locale = end( $this->locales ) ) {
			if ( isset( $filters[ $locale ] ) ) {
				add_filter( 'locale', $filters[ $locale ] );
			}
		} else {
			// There's nothing left in the stack: go back to the original locale.
			$locale = $this->original_locale;
		}

		global $l10n;

		foreach ( array_keys( $l10n ) as $textdomain ) {
			if ( isset( $this->translations[ $locale ][ $textdomain ] ) ) {
				$l10n[ $textdomain ] = $this->translations[ $locale ][ $textdomain ];
			}
		}

		$GLOBALS['wp_locale'] = new WP_Locale();

		return $locale;
	}

	/**
	 * Checks if there are cached translations for the given locale.
	 *
	 * @param string $locale The locale.
	 *
	 * @return bool True if there are cached translations for the given locale, false otherwise.
	 */
	private function has_translations_for_locale( $locale ) {

		return ! empty( $this->translations[ $locale ] );
	}

	/**
	 * Removes all filter callbacks added before.
	 */
	private function remove_filters() {

		foreach ( $this->filters as $filter ) {
			remove_filter( 'locale', $filter );
		}
	}

	/**
	 * Adds a filter callback returning the given locale.
	 *
	 * @param string $locale The locale.
	 */
	private function add_filter_for_locale( $locale ) {

		if ( ! isset( $this->filters[ $locale ] ) ) {
			// This really should be a closure, but ... well ... you know.
			$this->filters[ $locale ] = array( new WP_Locale_Storage( $locale ), 'get' );
		}

		add_filter( 'locale', $this->filters[ $locale ] );
	}
}

$GLOBALS['wp_locale_switcher'] = new WP_Locale_Switcher();

/**
 * Switches the translations according to the given locale.
 *
 * @see WP_Locale_Switcher::switch_to_locale()
 *
 * @param string $locale The locale.
 */
function switch_to_locale( $locale ) {

	/**
	 * @global WP_Locale_Switcher $wp_locale_switcher
	 */
	global $wp_locale_switcher;

	$wp_locale_switcher->switch_to_locale( $locale );
}

/**
 * Restores the translations according to the previous locale.
 *
 * @see WP_Locale_Switcher::restore_locale()
 *
 * @return string|false Locale on success, false on error.
 */
function restore_locale() {

	/**
	 * @global WP_Locale_Switcher $wp_locale_switcher
	 */
	global $wp_locale_switcher;

	return $wp_locale_switcher->restore_locale();
}
