<?php

namespace WpieFw\Modules;

use WpieFw\Exceptions\WpieRuntimeException;
use WpieFw\Files\WpieFileFinder;
use WpieFw\Helpers\WpieMultisiteHelper;
use WpieFw\Modules\Files\WpieModulesFileCollection;
use WpieFw\Modules\Iterator\WpieModulesCollection;
use WpieFw\Modules\WpieModulesBuilder;
use WpieFw\Settings\WpieSettingsProcessor;
use WpieFw\Wpie\WpieGlobals;

if( ! defined( 'ABSPATH' ) ) exit;

/**
 * @author WeePie Plugins
 *
 */
final class WpieModulesGenerator
{
	/**
	 * @var WpieModulesBuilder
	 */
	private $builder;

	/**
	 * @var WpieFileFinder
	 */
	private $finder;

	/**
	 * @var WpieModulesValues
	 */
	private $values;

	/**
	 * @var WpieModulesFileCollection
	 */
	private $files;

	/**
	 * @var WpieSettingsProcessor
	 */
	private $settingsProcessor;

	/**
	 * @var WpieGlobals
	 */
	private $globals;

	/**
	 * Flag if files have been included
	 *
	 * @var boolean
	 */
	private $included = false;

	/**
	 * Flag if paths should be stored relative or absolute
	 *
	 * @var bool
	 */
	public $relativePath = true;

	/**
	 * Stored WpieModule instances
	 *
	 * @var WpieModulesCollection
	 */
	public $collection;

	/**
	 * Constructor
	 *
	 * @param WpieModulesBuilder $builder
	 * @param WpieFileFinder $finder
	 * @param WpieModulesValues $values
	 * @param WpieSettingsProcessor $settingsProcessor
	 * @param WpieGlobals $globals
	 */
	public function __construct( WpieModulesBuilder $builder, WpieFileFinder $finder, WpieModulesValues $values, WpieSettingsProcessor $settingsProcessor, WpieGlobals $globals )
	{
		if( !\WpieFw\Helpers\WpieMiscHelper::fontType( $globals->nameSpace ) ) {
			throw new \WpieFw\Exceptions\WpieFyException( '' );
		}

		$this->builder = $builder;
		$this->finder = $finder;
		$this->values = $values;
		$this->settingsProcessor = $settingsProcessor;
		$this->collection = new WpieModulesCollection();
		$this->globals = $globals;
	}

	/**
	 * @return WpieModulesCollection
	 */
	public function getCollection() {
		return $this->collection;
	}

	/**
	 * Resolve module files
	 *
	 * @param array $files
	 *
	 * @uses WpieModulesValues::getFiles()
	 * @uses WpieFileFinder::getFilesPathArray()
	 * @uses WpieFileFinder::getFilesPathArrayRelative()
	 * @uses WpieModulesFileCollection
	 */
	public function resolveFiles()
	{
		$persist = false;
		$stored = $this->values->getFiles();
		$toPersist = [];

		if( empty( $stored ) ) {
			$files = $this->finder->getFilesPathArray();
			$persist = true;

			if( $this->relativePath ) {
				$toPersist = $this->finder->getFilesPathArrayRelative( 'wp-content' );
			} else {
				$toPersist = $files;
			}
		} else {
			$files = $stored;
		}

		// Create collection for files
		$this->files = new WpieModulesFileCollection( $files, $this->finder->getPrefix(), 'wp-content' );

		if( $persist ) {
			// Persist the files array
			$this->values->persist( 'files', $toPersist );
		}
	}

	/**
	 * Maybe reset the modules
	 *
	 * @uses self::resolveFiles()
	 */
	public function maybeReset()
	{
		$isActivating = $this->globals->get( 'isActivating' );
		$isUpgrading = $this->globals->get( 'isUpgrading' );
		$isResetting = $this->globals->get( 'isResetting' );
		$hasPermissions = current_user_can( 'activate_plugins');

		$networkReset = is_network_admin() || WpieMultisiteHelper::isMs() && $hasPermissions;
		$reset = is_admin() || !WpieMultisiteHelper::isMs() && $hasPermissions;

		// check if there is an activating, upgrade or reset request
		if( $isActivating || $isUpgrading || $isResetting ) {
			if( $networkReset ) {
				$current = get_current_blog_id();
				$sites = WpieMultisiteHelper::getSites();

				foreach ( $sites as $site ) {
					switch_to_blog( $site->blog_id );

					// do the reset
					$this->reset();
				}

				// switch back to the original site
				switch_to_blog( $current );

			} elseif ( $reset ) {
				// do the reset
				$this->reset();
			}
		}
	}

	/**
	 * Include the module files
	 *
	 * @throws WpieRuntimeException
	 */
	public function load()
	{
		if( !$this->files->hasCollection() ) {
			return;
		}

		$missing = [];
		ob_start();
		foreach ( $this->files as $file ) {
			if( !$file->isReadable() ) {
				$missing[] = $file->getIndex();
			} else {
				include_once $file->getRealPath();
			}
		}
		$err = ob_get_contents();
		ob_end_clean();

		if( '' !== $err ) {
			throw new WpieRuntimeException( sprintf( 'Something went wrong during including the modules:' . "\n" . '%s' , $err ) );
		}
		if( !empty( $missing ) ) {
			throw new WpieRuntimeException( sprintf( 'The following modules are missing: %s.', join(', ', $missing ) ) );
		}

		// No errors, files have been included
		$this->included = true;
	}

	/**
	 * Instantiate the modules classes
	 *
	 * Each WpieModule instance is added to the WpieModulesCollection
	 *
	 * @param array $vars
	 *
	 * @uses WpieModulesBuilder::instance()
	 * @uses WpieModulesCollection::set()
	 */
	public function create( array $vars = [] )
	{
		// Return if files have not been included
		if( !$this->included ) {
			return;
		}

		// Loop over each WpieFile and instantiate the Module classes
		foreach ( $this->files as $file ) {
			// Create the instance
			$module = $this->builder->instance( $file );

			// if module is not false, let the builder add more data to the Module
			if( $module ) {
				// get the modules index and real path from the file
				$idx = $file->getIndex();
				$path = $file->getRealPath();

				/**
				 * Let others hook after the module class is instantiated
				 *
				 * @param WpieModule $module
				 * @param string $path
				 * @param string $nameSpace
				 *
				 * @since 1.4.0
				 */
				do_action( $this->globals->nameSpace . '_module_after_instantiate', $module, $path, $this->globals->nameSpace );

				// add some params to the module
				$this->builder
					->setIndex( $idx )
					->setPath( $path )
					->setVars( $vars )
					->setGlobals( $this->globals )
					->setSettingsProcessor( $this->settingsProcessor );

				// If Module is active, add it to the collection
				if( $module->getActive() ) {
					$this->collection->set( $idx, $module );
				}
			}
		}

		// clear mem for $module param
		unset( $module );
	}

	/**
	 * Reset the modules
	 *
	 * @uses WpieModulesValues::deleteFiles()
	 * @uses self::resolveFiles()
	 */
	private function reset()
	{
		// delete the files entry
		$this->values->deleteFiles();

		// files are deleted in the DB,
		// so resolve the files again
		$this->resolveFiles();
	}
}