<?php

/**
 * Vvveb
 *
 * Copyright (C) 2022  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\System\Extensions;

use function Vvveb\__;
use function Vvveb\download;
use function Vvveb\getUrl;
use Vvveb\System\Event;
use Vvveb\System\Functions\Str;
use Vvveb\System\Import\Rss;
use function Vvveb\unzip;

abstract class Extensions {
	static protected $extensions = [];

	static protected $categories = [];

	static protected $extension = 'extension';

	static protected $baseDir = 'extension';

	static protected $url;

	static protected $feedUrl;

	const KEY_VALUE_REGEX = '/^([\w ]+):\s+(.+)$/m';

	static function getParams($comments) {
		$results = [];

		if (preg_match_all(static :: KEY_VALUE_REGEX, $comments, $matches)) {
			$matches[1] = array_map(function ($key) {
				return str_replace(' ','-',strtolower(trim($key)));
			}, $matches[1]);

			$results = array_combine($matches[1], $matches[2]);
		}

		return $results;
	}

	static function getComments($content) {
		//$content = file_get_contents($file);
		if (function_exists('token_get_all')) {
			$docComments = [];

			foreach (token_get_all($content) as $entry) {
				if ($entry[0] == T_DOC_COMMENT || $entry[0] == T_COMMENT) {
					$docComments[] = $entry[1];
				}
			}

			return implode("\n", $docComments);
		} else {
			if (preg_match_all('@(?s)/\*.*?\*/@', $content, $matches, PREG_PATTERN_ORDER)) {
				return implode("\n", $matches[0] ?? []);
			}
		}

		return '';
	}

	static function getInfo($content, $name = false) {
		$comments = static :: getComments($content);
		$params   = static ::  getParams($comments);

		if (isset($params['status'])) {
			unset($params['status']);
		}

		if (isset($params['category']) && $name) {
			static :: $categories[$params['category']][] = $name;
		}

		return $params;
	}

	static function getListInfo($path) {
		if (isset(static :: $extensions[static :: $extension])) {
			return static :: $extensions[static :: $extension];
		} else {
			static :: $extensions[static :: $extension] = [];
		}

		$adminPath = \Vvveb\adminPath();
		$list    	 = glob($path);

		foreach ($list as $file) {
			$content		      = file_get_contents($file);
			$dir			         = Str::match('@(.+)/[a-z]+.\w+$@', $file);
			$folder		       = Str::match('@/([^/]+)/[a-z]+.\w+$@', $file);
			$info           = static::getInfo($content, $folder);
			$info['file']   = $file;
			$info['folder'] = $folder;
			$info['import'] = file_exists($dir . DS . 'import');

			if (isset($info['settings'])) {
				$info['settings']= str_replace('/admin/', $adminPath, $info['settings']);
			}

			// plugin folder does not match slug
			if (! isset($info['slug']) || ($info['slug'] != $info['folder'])) {
				$info['status'] = 'slug_folder_mismatch';
				$info['slug']   = $info['folder'];
			}

			static :: $extensions[static :: $extension][$folder] = $info;
		}

		return static :: $extensions[static :: $extension];
	}

	static function getCategories() {
		return static :: $categories;
	}

	static function download($url) {
		//$temp = tmpfile();
		$f    = false;
		$temp = tempnam(sys_get_temp_dir(), 'vvveb_plugin');

		if ($content = download($url)) {
			$f  = file_put_contents($temp, $content, LOCK_EX);

			return $temp;
		}

		return $f;
	}

	static function install($extensionZipFile, $slug = false, $validate = true) {
		$extension   = static :: $extension;
		$success     = true;
		$extractTo   = static :: $baseDir;
		$fileCheck   = "$extension.php";

		$zip = new \ZipArchive();

		if ($zip->open($extensionZipFile/*, \ZipArchive::OVERWRITE*/) === true) {
			$info       = false;
			$folderName = $zip->getNameIndex(0);

			//check if first entry is a directory
			if (substr($folderName, -1, 1) != '/') {
				if ($validate) {
					throw new \Exception(sprintf('%s zip must have only %s folder!', $extensionZipFile, ucfirst($extension)));
				}
			} else {
				$slug = trim($folderName, '/');
			}

			for ($i = $zip->numFiles - 1; ($i > 0 && $success == true); $i--) {
				$file = $zip->getNameIndex($i);

				if ($validate) {
					//check if all files inside the extension folder
					if (strpos($file, $folderName) === false) {
						$extractTo .= DS . $slug;

						if ($validate) {
							throw new \Exception(sprintf(__('%s zip should not have other files than %s folder!'), $extension, $extension));
						}
					}
				}

				// plugin.php must be in the top most folder
				if (strpos($file, $fileCheck) !== false && substr_count($file, '/') < 2) {
					$content = $zip->getFromName($file);
					$info    = static::getInfo($content);

					if (isset($info['slug']) && ($folderName == ($info['slug'] . '/'))) {
						// Unzip Path
						if ($zip->extractTo($extractTo)) {
							$success = $info['slug'];
						} else {
							$success = false;
						}
					} else {
						if ($validate) {
							throw new \Exception(sprintf(__('%s slug `%s` does not match folder %s!'), $extension, $info['slug'] ?? '', $folderName));
						} else {
							if ($zip->extractTo($extractTo . DS . $slug)) {
								$info    = ['slug' => $slug];
								$success = $info['slug'];
							} else {
								$success = false;
							}
						}
					}

					break;
				}
			}

			// no extension.php found then treat as generic extension/theme
			if ($success) {
				$extractTo .= DS . $slug;

				if ($zip->extractTo($extractTo)) {
					$success = $slug;
					$info    = ['slug' => $slug];
				} else {
					$success = false;
				}
			}

			$zip->close();

			if (! $info) {
				throw new \Exception(sprintf(__('No `%s.php` info found inside zip!', $extension)));
			}
		} else {
			throw new \Exception(__('File is not a valid zip archive!'));
		}

		Event :: trigger(__CLASS__, __FUNCTION__, $extensionZipFile, $success);

		return $success;
	}

	static function marketUrl() {
		return static :: $url;
	}

	static function getMarketUrl($url, $params = [], $key = null) {
		$query            = http_build_query($params);
		$content          = getUrl($url . '?' . $query);

		if ($content) {
			$rss  = new Rss($content);

			$key                                = $key ?? static :: $extension . 's';
			$result[$key]                       = $rss->get($params['start'] ?? 1, $params['limit'] ?? 10);
			$result['count']                    = $rss->value('count');

			return $result;
		}

		return [];
	}

	static function getMarketCategories($params = [], $key = null) {
		return static :: getMarketUrl(static :: $categoriesFeedUrl, $params, $key);
	}

	static function getMarketList($params = [], $key = null) {
		return static :: getMarketUrl(static :: $feedUrl, $params, $key);
	}
}
