<?php

declare(strict_types=1);

/**
 * This file is part of LEPTON Core, released under the GNU GPL
 * Please see LICENSE and COPYING files in your package for details, specially for terms and warranties.
 * 
 * NOTICE:LEPTON CMS Package has several different licenses.
 * Please see the individual license in the header of each single file or info.php of modules and templates.
 *
 * @author          LEPTON Project
 * @copyright       2010-2026 LEPTON Project
 * @link            https://lepton-cms.org
 * @license         https://gnu.org/licenses/gpl.html
 * @license_terms   please see LICENSE and COPYING files in your package
 *
 */

/**
 *  This is only an abstract class for LEPTON specific classes and inside modules.
 *
 */
abstract class LEPTON_abstract
{
    /**
     * Default string if a value is not set in the info.php of the module.
     * @type    string
     *
     */
    const NOT_SET_IN_INFO = "(unknown - not set in info.php)";

    /**
     *  Array with the language(-array) of the child-object.
     *  @type   array
     *
     */
    public array $language = [];

    /**
     *  Array with the names of all parents (desc. order)
     *  @type   array
     *
     */
    public array $parents = [];

    /**
     *  The module directory from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_directory = "";

    /**
     *  The module name from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_name = "";

    /**
     *  The module function from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_function = "";

    /**
     *  The module version from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_version = "";

    /**
     *  The module platform from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_platform = "";
	
    /**
     *  The module delete from the info.php of the child.
     *  @type   boolean
     *
     */
    public int $module_delete = 1;

    /**
     *  The module author from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_author = "";

    /**
     *  The module license from the info.php of the child.
     *  @type   string
     *
     */
    public string  $module_license = "";

    /**
     *  The module license terms from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_license_terms = "";

    /**
     *  The module description from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_description = "";

    /**
     *  The module guid from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_guid = "";

    /**
     *  The module home from the info.php of the child.
     *  @type   string
     *
     */
    public string $module_home = "";

    /**
     *  @var    object  The reference to the *Singleton* instance of this class.
     *  @notice         Keep in mind that a child-object has to have his own one!
     */
    public static $instance;

    /**
     *  Return the instance of this class.
     *
     */
    public static function getInstance(mixed $value = null): object
    {
        if (null === static::$instance)
        {
            static::$instance = new static();
            static::$instance->getParents();
            static::$instance->getLanguageFile();
            static::$instance->getModuleInfo();
            static::$instance->initialize($value);
        }
        return static::$instance;
    }

    /**
     *  Try to get all parents form the current instance as a simple linear list.
     */
    protected function getParents(): void
    {
        // First the class itself
        static::$instance->parents[] = get_class(static::$instance);

        // Now the parents
        $aTempParents = class_parents(static::$instance, true);
        foreach($aTempParents as $sParentName)
        {
            static::$instance->parents[] = $sParentName;
        }
    }

    /**
     *  Try to read the module specific info.php from the module-Directory
     *  and update the current class-properties.
     *
     */
    protected function getModuleInfo(): void
    {

        foreach(static::$instance->parents as $sModuleDirectoryTop)
        {
            //  strip namespace
            $aTemp = explode("\\", $sModuleDirectoryTop);
            foreach($aTemp as $sModuleDirectory)
            {             

                $aMainClassNames = $this->getMainClassNames( $sModuleDirectory );

                foreach($aMainClassNames as $sTempModuleDirectory)
                {
                    $sLookUpPath = __DIR__."/../../modules/".$sTempModuleDirectory."/info.php";

                    if( file_exists($sLookUpPath) )
                    {
                        require $sLookUpPath;
                        // [1.1] mandatory information
                        static::$instance->module_name = ($module_name ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_directory = ($module_directory ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_function = ($module_function ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_version = ($module_version ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_author = ($module_author ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_license = ($module_license ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_description = ($module_description ?? self::NOT_SET_IN_INFO);
                        static::$instance->module_guid = ($module_guid ?? self::NOT_SET_IN_INFO);

                        // [1.2] optional information
                        if(isset($module_platform))
                        {
                            static::$instance->module_platform = $module_platform;
                        }
                        if(isset($module_delete))
                        {
                            static::$instance->module_delete = intval($module_delete);
                        }						
                        if(isset($module_license_terms))
                        {
                            static::$instance->module_license_terms = $module_license_terms;
                        }						
                        if(isset($module_home))
                        {
                            static::$instance->module_home = $module_home;
                        }
                        break;
                    }
                }
            }            
        }
    }

    /**
     *  Try to get a module-specific language file.
     */
    protected function getLanguageFile(): void
    {
        if(defined("LEPTON_PATH"))
        {
            $aLookUpFilenames = [
                LANGUAGE."_add.php",
                LANGUAGE."_custom.php",
                LANGUAGE.".php",
                "EN_add.php",
                "EN_custom.php",
                "EN.php"
            ];

            foreach (static::$instance->parents as $sClassNameTop)
            {
                //  strip namespace
                $aTemp = explode("\\", $sClassNameTop);
                $bExitGraceful = false;
                foreach( $aTemp as $sClassName )
                {          
                    $aMainClassNames = $this->getMainClassNames( $sClassName );

                    foreach($aMainClassNames as $sTempModuleDirectory)
                    {
                        $lookUpPath = LEPTON_PATH."/modules/".$sTempModuleDirectory."/languages/";

                        $bFoundFile = false;

                        $sUsedFilename = "";
                        foreach ($aLookUpFilenames as $sTempFilename)
                        {
                            if (true === file_exists($lookUpPath.$sTempFilename))
                            {
                                require $lookUpPath.$sTempFilename;
                                $sUsedFilename = $sTempFilename;
                                $bFoundFile = true;
                                break;
                            }
                        }

                        if (false === $bFoundFile)
                        {
                            continue;
                        }

                        $tempName = "MOD_".strtoupper($sTempModuleDirectory);
                        if (isset(${$tempName}))
                        {
                            /**
                             *  Is this is a "*_add.php" language file?
                             */
                            if (str_contains($sUsedFilename, "_add.php"))
                            {
                                $sDefaultFileName = str_replace("_add", "", $sUsedFilename);
                                $aLanguageAdd = ${$tempName};

                                require $lookUpPath.$sDefaultFileName;

                                ${$tempName} = array_merge(${$tempName}, $aLanguageAdd);
                            }

                            static::$instance->language = ${$tempName};
                            $bExitGraceful = true;
                            break;
                        }
                    }
                    
                    if (true === $bExitGraceful)
                    {
                        break;
                    }
                }
            }
        }
    }

    /**
     *  Needed for modules like e.g. "news", "topics", "bakery", "forum", "xDisplay".
     *  To be overwritten by the module(-child) itself.
     *
     *  @return array   Assoc array with at last three keys for (additional) "title","description" and "keywords"
     */
    public function getHeadInfo(): array
    {
        return array(
            "title"         => "",
            "description"   => "",
            "keywords"      => ""
        );
    }

    /**
     *  Private function to "handle" sub_class-names
     *
     */
    protected function getMainClassNames( $sAnyClassname )
    {
        $aElements = explode("_",  $sAnyClassname);

        $sTempName = array_shift($aElements);

        $aReturnValue = array( $sTempName );

        foreach($aElements as $term)
        {
            $sTempName .= "_".$term;
            $aReturnValue[] = $sTempName;
        }

        if( 1 < count($aReturnValue) )
        {
            $aReturnValue = array_reverse( $aReturnValue );
        }

        return $aReturnValue;
    }

    /**
     *  Public function to "store" a given section_id into the $_SESSION (superglobal).
     *
     *  @param  int $iSectionID     A valid section_id. If not given or NULL parsed, the last global "section_id" is used!.
     *  @return void
     *
     *  @code{.php}
	 *		$this->saveLastEditSection( $section_id);
	 *      // or looking into global scope.
	 *      $this->saveLastEditSection();
	 *      // or static
	 *      LEPTON_abstract::saveLastEditSection();
	 *	@endcode
	 *
     */
    public static function saveLastEditSection(int $iSectionID = 0): void
    {
        if ($iSectionID === 0)
        {
            if (true === isset($GLOBALS['section_id']))
            {
                $_SESSION['last_edit_section'] = $GLOBALS['section_id'];
            }
        } else {
            $_SESSION['last_edit_section'] = $iSectionID;
        }
    }

    public static function getConstants(): array
    {
        // "static::class" here does the magic
        $reflectionClass = new \ReflectionClass(static::class);
        return $reflectionClass->getConstants();
    }

    /**
     *  Abstract declarations - to be overwritten by the child-instance.
     */
    abstract protected function initialize();

        
	/**
	 * [7.1] L* V.5: additional custom-classes
	 * Try to get an instance from a "setting"-class or the "settings_custom" of the parent.
	 * 
	 * @since   5.5.0
	 * @return  mixed Instance of the (custom-) class, or NULL if none found.
	 * 
	 */
	public function getEditorSettings()
	{
		// [1] base class name
		$sBaseClassName = $this->parents[0];
		
		// [2] custom class?
		$sCustom = (class_exists( $sBaseClassName."_settings_custom", true)) ? "_custom" : "";
		// [2.1] class exists?
		if(class_exists($sBaseClassName."_settings".$sCustom, true))
		{
			//  [2.2] try to get instance of the class
			return eval("return ".$sBaseClassName."_settings".$sCustom."::getInstance();");    
		}
		else
		{   
			//  [2.3] class not exists - return NULL
			return NULL;
		}
	}
	
	/**
	 * [7.2] L* V.5: like [7.1] but more flexible.
	 * Useful to get information from other classes/modules out of this context.
	 * 
	 * @since 5.5.0
	 * @param string $basename  Default is "settings".
	 * @param string $classname Default is "" - looking for the parent of this instance.
	 * 
	 * @return mixed Instance of the (custom-) class, or NULL if none found.
	 * 
	 */
	public function getAdditionalClass( string $basename="settings", string $classname="") : mixed
	{
		// [1] Basename
		if($classname === "")
		{
			$classname = $this->parents[0];
		}
		
		// [2] Custom?
		$custom = (class_exists($classname."_".$basename."_custom", true)) ? "_custom" : "";
		
		// [3] class exists?
		if( class_exists( $classname."_".$basename.$custom, true))
		{
			return eval("return ".$classname."_".$basename.$custom."::getInstance();");
		}
		else
		{
			return NULL;
		}
	}
}