<?php

namespace WPML\ST\MO\Hooks;

use WPML\Collect\Support\Collection;
use WPML\ST\MO\Generate\MissingMOFile;
use WPML\WP\OptionManager;
use function WPML\Container\make;

class LoadMissingMOFiles implements \IWPML_Action {

	const MISSING_MO_FILES_DIR                = '/wpml/missing/';
	const OPTION_GROUP                        = 'ST-MO';
	const MISSING_MO_OPTION                   = 'missing-mo';
	const TIMEOUT                             = 10;
	const WPML_VERSION_INTRODUCING_ST_MO_FLOW = '4.3.0';

	/**
	 * @var MissingMOFile
	 */
	private $generateMissingMoFile;
	/**
	 * @var OptionManager
	 */
	private $optionManager;

	/** @var \WPML_ST_Translations_File_Dictionary_Storage_Table */
	private $moFilesDictionary;

	public function __construct(
		MissingMOFile $generateMissingMoFile,
		OptionManager $optionManager,
		\WPML_ST_Translations_File_Dictionary_Storage_Table $moFilesDictionary
	) {
		$this->generateMissingMoFile = $generateMissingMoFile;
		$this->optionManager         = $optionManager;
		$this->moFilesDictionary     = $moFilesDictionary;
	}

	public function add_hooks() {
		if ( $this->wasWpmlInstalledPriorToMoFlowChanges() ) {
			add_filter( 'load_textdomain_mofile', [ $this, 'recordMissing' ], 10, 2 );

			if ( $this->isThemeAndLocalizationPage() ) {
				// By now this complete feature is probably only required to load
				// generated files, but for the rare case that someone updates
				// from < 4.3.0 and has missing files, the genration will still
				// be triggered on the Themen and Plugins localization page.
				add_action( 'shutdown', [ $this, 'generateMissing' ] );
			}
		}
	}

	/**
	 * @param string $mofile
	 * @param string $domain
	 *
	 * @return string
	 */
	public function recordMissing( $mofile, $domain ) {
		if ( strpos( $mofile, WP_LANG_DIR . '/themes/' ) === 0 ) {
			return $mofile;
		}
		if ( strpos( $mofile, WP_LANG_DIR . '/plugins/' ) === 0 ) {
			return $mofile;
		}

		$missing = $this->getMissing();

		if ( self::isReadable( $mofile ) ) {
			if ( $missing->has( $domain ) ) {
				$this->saveMissing( $missing->forget( $domain ) );
			}

			return $mofile;
		}

		// Light check to see if a file was already generated.
		$generatedFile = $this->getGeneratedFileName( $mofile, $domain );
		if (
			self::isReadable( $generatedFile )
			&& $this->moFilesDictionary->is_path_handled( $mofile, $domain )
		) {
			// The file exists AND the path is handled by ST.
			return $generatedFile;
		}

		if ( ! $this->isThemeAndLocalizationPage() ) {
			// All following code is for genarating missing files and that's
			// only happening on the Theme and Plugins localization page.
			// -> by now we should consider removing the generation completely.
			return $mofile;
		}

		// Heavy check to see if the file is handled by String Translation.
		if ( ! $this->moFilesDictionary->find( $mofile ) ) {
			// Not handled by String Translation.
			return $mofile;
		}

		if ( $this->generateMissingMoFile->isNotProcessed( $generatedFile ) ) {
			$this->saveMissing( $missing->put( $domain, $mofile ) );
		}

		return $mofile;
	}

	public function generateMissing() {
		$lock = make( 'WPML\Utilities\Lock', [ ':name' => self::class ] );

		$missing = $this->getMissing();
		if ( $missing->count() && $lock->create() ) {

			$generate = function ( $pair ) {
				list( $domain, $mofile ) = $pair;
				$generatedFile = $this->getGeneratedFileName( $mofile, $domain );
				$this->generateMissingMoFile->run( $generatedFile, $domain );
			};

			$unProcessed = $missing->assocToPair()
			                       ->eachWithTimeout( $generate, self::getTimeout() )
			                       ->pairToAssoc();
			$this->saveMissing( $unProcessed );

			$lock->release();
		}
	}

	public static function isReadable( $mofile ) {
		return is_readable( $mofile );
	}

	/**
	 * @return \WPML\Collect\Support\Collection
	 */
	private function getMissing() {
		return wpml_collect( $this->optionManager->get( self::OPTION_GROUP, self::MISSING_MO_OPTION, [] ) );
	}

	/**
	 * @param \WPML\Collect\Support\Collection $missing
	 */
	private function saveMissing( \WPML\Collect\Support\Collection $missing ) {
		$this->optionManager->set( self::OPTION_GROUP, self::MISSING_MO_OPTION, $missing->toArray() );
	}

	public static function getTimeout() {
		return self::TIMEOUT;
	}

	/**
	 * @return bool
	 */
	private function wasWpmlInstalledPriorToMoFlowChanges() {
		$wpml_start_version = \get_option( \WPML_Installation::WPML_START_VERSION_KEY, '0.0.0' );

		return version_compare( $wpml_start_version, self::WPML_VERSION_INTRODUCING_ST_MO_FLOW, '<' );
	}

	/**
	 * @param string $mofile
	 * @param string $domain
	 *
	 * @return string
	 */
	private function getGeneratedFileName( $mofile, $domain ) {
		$fileName = basename( $mofile );

		if ( $this->isNonDefaultWithMissingDomain( $fileName, $domain ) ) {
			$fileName = $domain . '-' . $fileName;
		}

		return WP_LANG_DIR . self::MISSING_MO_FILES_DIR . $fileName;
	}

	/**
	 * There's a fallback for theme that is looking for
	 * this kind of file `wp-content/themes/hybrid/ru_RU.mo`.
	 * We need to add the domain otherwise it collides with
	 * the MO file for the default domain.
	 *
	 * @param string $fileName
	 * @param string $domain
	 *
	 * @return bool
	 */
	private function isNonDefaultWithMissingDomain( $fileName, $domain ) {
		return 'default' !== $domain
		       && preg_match( '/^[a-z]+_?[A-Z]*\.mo$/', $fileName );
	}


	private function isThemeAndLocalizationPage() {
		global $sitepress;

		return $sitepress
			->get_wp_api()
			->is_core_page( 'theme-localization.php' );
	}
}
