<?php

/**
 * @author          Tassos Marinos <info@tassos.gr>
 * @link            https://www.tassos.gr
 * @copyright       Copyright © 2024 Tassos All Rights Reserved
 * @license         GNU GPLv3 <http://www.gnu.org/licenses/gpl.html> or later
 */

namespace Tassos\Framework\Helpers\Widgets;

defined('_JEXEC') or die;

use Tassos\Framework\Mimes;
use Joomla\CMS\Helper\ModuleHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Uri\Uri;

class Gallery
{
	/**
	 * Stores all gallery parsed directories info txt file `*.gallery_info.txt` data in format:
	 * GALLERY DIRECTORY => ARRAY OF `*.gallery_info.txt` file data
	 * 
	 * @var  array
	 */
	static $gallery_directories_info_file = [];

	/**
	 * Stores all galleries info file names in format:
	 * 
	 * GALLERY DIRECTORY => INFO FILE NAME
	 * 
	 * @var  array
	 */
	static $gallery_directories_info_file_names = [];
	
	/**
	 * The directory information file holding all gallery item details.
	 * 
	 * @var  string
	 */
	const directory_gallery_info_file = 'gallery_info.txt';
	
	/**
	 * Parses the given gallery items.
	 * 
	 * @param   mixed  	$input     			 	A string to a directory/path/URL or an array of a URL item containing its information.
	 * @param   array   $allowed_file_types	 	The allowed file types.
	 * 
	 * @return  mixed
	 */
	public static function parseGalleryItems($input, $allowed_file_types = [])
	{
		if (is_string($input))
		{
			$fullpath_input = JPATH_ROOT . DIRECTORY_SEPARATOR . ltrim($input, DIRECTORY_SEPARATOR);

			// Parse Directory
			if (is_dir($fullpath_input))
			{
				return self::parseDirectory($fullpath_input, $allowed_file_types);
			}

			// Skip invalid URLs
			if ($url = self::parseURL($input))
			{
				return [$url];
			}

			// Parse Image
			if ($image_data = self::parseImage($fullpath_input, $allowed_file_types))
			{
				return [$image_data];
			}
		}

		return [self::parseURL($input)];
	}

	/**
	 * Parse the directory by finding all of its images and their information.
	 * 
	 * @param   string  $dir
	 * @param   array   $allowed_file_types
	 * 
	 * @return  mixed
	 */
	public static function parseDirectory($dir, $allowed_file_types = [])
	{
		if (!is_string($dir) || !is_dir($dir) || empty($allowed_file_types))
		{
			return;
		}

		$items = [];

		// Get images
		$files = array_diff(scandir($dir), ['.', '..', '.DS_Store']);

		foreach ($files as $key => $filename)
		{
			// Skip directories
			if (is_dir($dir . DIRECTORY_SEPARATOR . $filename))
			{
				continue;
			}

			$image_path = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
			
			if (!$image_data = self::parseImage($image_path, $allowed_file_types))
			{
				continue;
			}

			$items[] = $image_data;
		}

		return $items;
	}

	/**
	 * Parse the directory image and return its information.
	 * 
	 * @param   string  $image_path
	 * @param   string  $allowed_file_types
	 * 
	 * @return  mixed
	 */
	public static function parseImage($image_path, $allowed_file_types = null)
	{
		if (!is_string($image_path))
		{
			return;
		}

		$data = [
			'path' => $image_path,
			'url' => self::directoryImageToURL($image_path)
		];

		if (!is_file($image_path))
		{
			return array_merge($data, [
				'invalid' => true
			]);
		}

		// Skip not allowed file types
		if (!is_null($allowed_file_types) && !Mimes::check($allowed_file_types, Mimes::detectFileType($image_path)))
		{
			return;
		}

		// Check if there is a `*.gallery_info.txt` helper file and get any information about the image
		$gallery_info_file_data = self::getGalleryInfoFileData(dirname($image_path));
		if (!$gallery_info_file_data)
		{
			return $data;
		}

		$image_filename = pathinfo($image_path, PATHINFO_BASENAME);

		// If no information from the text field about this image was found, stop
		if (!isset($gallery_info_file_data[$image_filename]))
		{
			return $data;
		}

		$image_data = $gallery_info_file_data[$image_filename];

		return array_merge($data, [
			'caption' => isset($image_data['caption']) ? $image_data['caption'] : ''
		]);
	}

	/**
	 * Parses a single URL either as a String or as an Array.
	 * 
	 * @param   mixed  $item
	 * 
	 * @return  mixed
	 */
	public static function parseURL($item)
	{
		// URL is a string
		if (is_string($item))
		{
			if (!filter_var($item, FILTER_VALIDATE_URL))
			{
				return;
			}

			return [
				'url' => $item
			];
		}
		
		// URL is an array
		if (!is_array($item) || !count($item))
		{
			return;
		}

		// If a thumbnail URL is given but no URL, use it as the full image URL
		if (isset($item['thumbnail_url']) && !isset($item['url']))
		{
			$item['url'] = $item['thumbnail_url'];
		}

		if (!isset($item['url']))
		{
			return;
		}

		if (!filter_var($item['url'], FILTER_VALIDATE_URL))
		{
			return;
		}
		
		return $item;
	}

	/**
	 * Loads a module by its ID.
	 * 
	 * @param   string  $id
	 * 
	 * @return  string
	 */
	public static function loadModule($id)
	{
		$module = ModuleHelper::getModuleById($id);
		$params = ['style' => 'none'];

		return $module->id > 0 ? Factory::getDocument()->loadRenderer('module')->render($module, $params) : '';
	}

	/**
	 * Read the `*.gallery_info.txt` file for the given directory.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  mixed
	 */
	public static function getGalleryInfoFileData($dir)
	{
		if (isset(self::$gallery_directories_info_file[$dir]) && !empty(self::$gallery_directories_info_file[$dir]))
		{
			return self::$gallery_directories_info_file[$dir];
		}

		if (!$file = self::findGalleryInfoFile($dir))
		{
			return [];
		}

		// Read file
		if (!$handle = fopen($file, 'r'))
		{
			return [];
		}

		$data = [];

		$line_defaults = ['', '', ''];

		// Loop each line
		while (($line = fgets($handle)) !== false)
		{
			list($filename, $caption, $hash) = explode('|', $line) + $line_defaults;

			// If no filename is given, continue
			if (!$filename)
			{
				continue;
			}

			$data[$filename] = [
				'filename' => $filename,
				'caption' => trim($caption),
				'hash' => trim($hash)
			];
		}
		
		// Close file
		fclose($handle);

		self::$gallery_directories_info_file[$dir] = $data;

		return $data;
	}

	/**
	 * Finds the source image and whether it has been edited.
	 * 
	 * @param   string  $source
	 * @param   string  $destination_folder
	 * 
	 * @return  mixed
	 */
	public static function findSourceImageDetails($source, $destination_folder)
	{
		$source_filename = pathinfo($source, PATHINFO_BASENAME);

		$data = self::getGalleryInfoFileData(dirname($source));

		$image_data = isset($data[$source_filename]) ? $data[$source_filename] : false;
		
		if (!$image_data)
		{
			return false;
		}
		
		if (empty($image_data['hash']))
		{
			return false;
		}

		$sourceHash = self::calculateFileHash($source);

		return [
			'path' => $destination_folder . $image_data['filename'],
			'edited' => $image_data['hash'] !== $sourceHash
		];
	}

	/**
	 * Updates or Inserts the given image information from the gallery info file.
	 * 
	 * @param   string   $source
	 * @param   array    $image_data
	 * 
	 * @return  mixed
	 */
	public static function updateImageDataInGalleryInfoFile($source, $image_data)
	{
		// Source directory
		$source_directory = dirname($source);

		// Check whether the gallery info file exists, if not, create it
		if (!$file = self::findGalleryInfoFile($source_directory))
		{
			$file = self::createGalleryInfoFile($source_directory);
		}

		// Open files
		$reading = fopen($file, 'r');
		$writing = fopen($file . '.tmp', 'w');
	
		$replaced = false;
	
		while (!feof($reading))
		{
			// Get each file line
			$line = fgets($reading);

			// Remove new line at the end
			$line = trim(preg_replace('/\s\s+/', ' ', $line));

			// Skip empty lines
			if (empty($line))
			{
				continue;
			}

			list($filename, $caption, $hash) = explode('|', $line) + ['', '', ''];

			// We need to manipulate current file
			if (strtolower($filename) !== strtolower(basename($image_data['path'])))
			{
				fputs($writing, $line . "\n");
				continue;
			}

			$replaced = true;

			$line = $filename . '|' . $caption . '|' . self::calculateFileHash($source) . "\n";;

			// Write changed line
			fputs($writing, $line);
		}

		// Close files
		fclose($reading);
		fclose($writing);

		// If we replaced a line, update the text file
		if ($replaced) 
		{
			rename($file . '.tmp', $file);
		}
		// No line was replaced, append image details
		else
		{
			unlink($file . '.tmp');
			
			self::appendImageDataToGalleryInfoFile($file, $source, $image_data);
		}
	}

	/**
	 * Removes the image from the gallery info file.
	 * 
	 * @param   string   $source
	 * 
	 * @return  boolean
	 */
	public static function removeImageFromGalleryInfoFile($source)
	{
		// Get the gallery info file from destination folder
		if (!$file = self::findGalleryInfoFile(dirname($source)))
		{
			return false;
		}
		
		// Open files
		$reading = fopen($file, 'r');
		$writing = fopen($file . '.tmp', 'w');
	
		$found = false;
	
		while (!feof($reading))
		{
			// Get each file line
			$line = fgets($reading);

			// Remove new line at the end
			$line = trim(preg_replace('/\s\s+/', ' ', $line));

			// Skip empty lines
			if (empty($line))
			{
				continue;
			}

			list($filename, $caption, $hash) = explode('|', $line) + ['', '', ''];

			// We need to manipulate current file
			if ($filename !== pathinfo($source, PATHINFO_BASENAME))
			{
				$found = true;
				fputs($writing, $line . "\n");
				continue;
			}
		}

		// Close files
		fclose($reading);
		fclose($writing);

		if (!$found)
		{
			return false;
		}

		// Save the changes
		rename($file . '.tmp', $file);

		return true;
	}

	/**
	 * Appends the image data into the info file.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  void
	 */
	public static function createGalleryInfoFile($dir)
	{
		$file = self::getLanguageInfoFileName($dir);
		
		file_put_contents($file, '');

		return $file;
	}

	/**
	 * Appends the image data into the info file.
	 * 
	 * @param   string  $file
	 * @param   string  $source
	 * @param   object  $image_data
	 * 
	 * @return  void
	 */
	public static function appendImageDataToGalleryInfoFile($file, $source, $image_data)
	{
		$caption = isset($image_data['caption']) ? $image_data['caption'] : '';
		
		$hash = self::calculateFileHash($source);
		
		$line = pathinfo($source, PATHINFO_BASENAME) . '|' . $caption . '|' . $hash . "\n";

		file_put_contents($file, $line, FILE_APPEND);
	}

	/**
	 * Finds the `*.gallery_info.txt` file if it exists in the given directory.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  mixed
	 */
	public static function findGalleryInfoFile($dir)
	{
		if (isset(self::$gallery_directories_info_file_names[$dir]))
		{
			return self::$gallery_directories_info_file_names[$dir];
		}
		
		// Method 1: With language prefix
		$file = self::getLanguageInfoFileName($dir);

		// Check if the file exists
		if (file_exists($file))
		{
			self::$gallery_directories_info_file_names[$dir] = $file;
			
			return $file;
		}

		// Method 2: Without the language prefix
		$file = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . self::directory_gallery_info_file;

		// Check if the file exists
		if (file_exists($file))
		{
			self::$gallery_directories_info_file_names[$dir] = $file;

			return $file;
		}

		return false;
	}

	/**
	 * Returns the info file with the language prefix.
	 * 
	 * @param   string  $dir
	 * 
	 * @return  string
	 */
	public static function getLanguageInfoFileName($dir)
	{
		return rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . Factory::getLanguage()->getTag() . '.' . self::directory_gallery_info_file;
	}

	/**
	 * Calculates the file hash of a file.
	 * 
	 * Hash = md5(file path + last modified date of file)
	 * 
	 * @param   string  $file_path
	 * 
	 * @return  string
	 */
	public static function calculateFileHash($file_path)
	{
		return md5($file_path . filemtime($file_path));
	}

	/**
	 * Transforms an image path to a URL.
	 * 
	 * @param   string  $image_path
	 * 
	 * @return  string
	 */
	public static function directoryImageToURL($image_path)
	{
		return rtrim(Uri::root(), DIRECTORY_SEPARATOR) . mb_substr($image_path, strlen(JPATH_BASE), null);;
	}
}