<?php
/**
 * @package   awf
 * @copyright Copyright (c)2014-2025 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU GPL version 3 or later
 */

/**
 * The Session package in Awf is based on the Session package in Aura for PHP. Please consult the LICENSE file in the
 * Awf\Session package for copyright and license information.
 */

namespace Awf\Session;

use Awf\Session\Encoder\Base32Encoder;
use Awf\Session\Encoder\Base64Encoder;
use Awf\Session\Encoder\EncoderInterface;

/**
 * A session segment; lazy-loads from the session.
 */
class Segment implements SegmentInterface
{
	/**
	 *
	 * The session manager.
	 *
	 * @var Manager
	 *
	 */
	protected $session;

	/**
	 *
	 * The segment name.
	 *
	 * @var string
	 *
	 */
	protected $name;

	/**
	 *
	 * The data in the segment is a reference to a $_SESSION key.
	 *
	 * @var array
	 *
	 */
	protected $data;

	/**
	 * Session data encoder
	 *
	 * @var  EncoderInterface
	 */
	protected $encoder;

	/**
	 *
	 * Constructor.
	 *
	 * @param   Manager  $session  The session manager.
	 *
	 * @param   string   $name     The segment name.
	 *
	 */
	public function __construct(Manager $session, $name)
	{
		$this->session = $session;
		$this->name    = $name;
		$this->encoder = new Base64Encoder();

		if (!$this->encoder->isAvailable())
		{
			$this->encoder = new Base32Encoder();
		}
	}

	/**
	 *
	 * Checks to see if the segment data has been loaded; if not, checks to
	 * see if a session has already been started or is available, and then
	 * loads the segment data from the session.
	 *
	 * @return bool
	 *
	 */
	protected function isLoaded()
	{
		if ($this->data !== null)
		{
			return true;
		}

		if (!$this->session->isStarted())
		{
			$this->session->start();
		}

		if ($this->session->isAvailable())
		{
			$this->load();

			return true;
		}

		return false;
	}

	/**
	 *
	 * Forces a session start (or reactivation) and loads the segment data
	 * from the session.
	 *
	 * @return void
	 *
	 */
	protected function load()
	{
		// is data already loaded?
		if ($this->data !== null)
		{
			// no need to re-load
			return;
		}

		// if the session is not started, start it
		if (!$this->session->isStarted())
		{
			$this->session->start();
		}

		// Initialize data
		$this->data = [];

		// Try loading data from the session
		if (isset($_SESSION[$this->name]) && !empty($_SESSION[$this->name]))
		{
			$this->data = $this->encoder->decode($_SESSION[$this->name]) ?? [];
		}
	}

	/**
	 * Commit the session data to PHP's session storage using a safely encoded format to prevent PHP session
	 * unserialization attacks.
	 *
	 * @return  void
	 */
	public function save()
	{
		$_SESSION[$this->name] = $this->encoder->encode($this->data);
	}

	/**
	 *
	 * Returns the value of a key in the segment.
	 *
	 * @param   string  $key  The key in the segment.
	 *
	 * @return  mixed
	 *
	 */
	public function __get($key)
	{
		if ($this->isLoaded())
		{
			return isset($this->data[$key]) ? $this->data[$key] : null;
		}

		return null;
	}

	/**
	 *
	 * Sets the value of a key in the segment.
	 *
	 * @param   string  $key  The key to set.
	 *
	 * @param   mixed   $val  The value to set it to.
	 *
	 */
	public function __set($key, $val)
	{
		$this->load();
		$this->data[$key] = $val;
	}

	/**
	 *
	 * Check whether a key is set in the segment.
	 *
	 * @param   string  $key  The key to check.
	 *
	 * @return bool
	 *
	 */
	public function __isset($key)
	{
		if ($this->isLoaded())
		{
			return isset($this->data[$key]);
		}

		return false;
	}

	/**
	 *
	 * Unsets a key in the segment.
	 *
	 * @param   string  $key  The key to unset.
	 *
	 * @return void
	 *
	 */
	public function __unset($key)
	{
		if ($this->isLoaded())
		{
			unset($this->data[$key]);
		}
	}

	/**
	 *
	 * Clear all data from the segment.
	 *
	 * @return void
	 *
	 */
	public function clear()
	{
		if ($this->isLoaded())
		{
			$this->data = [];
		}
	}

	/**
	 *
	 * Gets the segment name.
	 *
	 * @return string
	 *
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 *
	 * Sets a read-once flash value on the segment.
	 *
	 * @param   string  $key  The key for the flash value.
	 *
	 * @param   mixed   $val  The flash value itself.
	 *
	 */
	public function setFlash($key, $val)
	{
		$this->load();
		$this->data['__flash'][$key] = $val;
	}

	/**
	 *
	 * Reads the flash value for a key, thereby removing it from the session.
	 *
	 * @param   string  $key  The key for the flash value.
	 *
	 * @return  mixed The flash value itself.
	 *
	 */
	public function getFlash($key)
	{
		if ($this->isLoaded() && isset($this->data['__flash'][$key]))
		{
			$val = $this->data['__flash'][$key];
			unset($this->data['__flash'][$key]);

			return $val;
		}

		return null;
	}

	/**
	 *
	 * Checks whether a flash key is set, without reading it.
	 *
	 * @param   string  $key  The flash key to check.
	 *
	 * @return bool True if it is set, false if not.
	 *
	 */
	public function hasFlash($key)
	{
		if ($this->isLoaded())
		{
			return isset($this->data['__flash'][$key]);
		}

		return false;
	}

	/**
	 *
	 * Clears all flash values.
	 *
	 * @return void
	 *
	 */
	public function clearFlash()
	{
		if ($this->isLoaded())
		{
			unset($this->data['__flash']);
		}
	}

	/**
	 * Does this segment have the specified session variable?
	 *
	 * @param   string  $key  The session variable's key (name)
	 *
	 * @return  boolean  True if the session variable exists
	 */
	public function has($key)
	{
		if ($this->isLoaded())
		{
			return (isset($this->data[$key]));
		}
		else
		{
			return false;
		}
	}

	/**
	 * Set a session variable
	 *
	 * @param   string  $key  The name of the session variable
	 * @param   mixed   $val  The value to set it to
	 *
	 * @return  void
	 */
	public function set($key, $val)
	{
		if (!$this->isLoaded())
		{
			$this->load();
		}

		$this->data[$key] = $val;
	}

	/**
	 * Get the value of a session variable. If the session variable does not exist it is initialised with $default
	 *
	 * @param   string  $key      The session variable's name
	 * @param   mixed   $default  [Optional] The defualt value to use if the session variable doesn't exist
	 *
	 * @return  mixed  The value of the session variable
	 */
	public function get($key, $default = null)
	{
		if ($this->isLoaded())
		{
			if (!isset($this->data[$key]))
			{
				$this->data[$key] = $default;
			}
		}
		else
		{
			$this->set($key, $default);
		}

		return $this->data[$key];
	}

	/**
	 * Removes a session variable
	 *
	 * @param   string  $key  The session variable's name
	 *
	 * @return  void
	 */
	public function remove($key)
	{
		if ($this->isLoaded() && isset($this->data[$key]))
		{
			unset($this->data[$key]);
		}
	}

	public function setEncoder(EncoderInterface $encoder)
	{
		$this->encoder = $encoder;
	}

	public function getEncoder(): EncoderInterface
	{
		return $this->encoder;
	}
}
