<?php

namespace Vinteract;

use DirectoryIterator;

use DateTime;
use DateTimeZone;
use DateInterval;
use DatePeriod;

class Utils {
	
	function __construct() {
		
	}
	
	/*
		
		Other
		
	*/
	
	// https://stackoverflow.com/a/50010131
	
	public static function mergeArrayOfArrays($array) {
		return call_user_func_array("array_merge", $array);
	}
	
	// https://stackoverflow.com/questions/381265/better-way-to-check-variable-for-null-or-empty-string
	
	public static function IsNullOrEmptyString($str) {
		return (!isset($str) || trim($str) === "");
	}
	
	public static function extractBase64ImageData($data = "") {
		list($type, $data) = explode(';', $data);
		list(, $data) = explode(',', $data);
		return base64_decode($data);
	}
	
	public static function extractBearerToken($str = "") {
		list($token) = sscanf($str, "Bearer %s");
		return $token;
	}
	
	public static function formatMoney($amount, $include_dollar_sign = false) {
		$amount = number_format($amount, 2);
		if ($include_dollar_sign) {
			return "$" . $amount;
		}
		return $amount;
	}
	
	public static function replaceVariablesInString($string = "", $variables = []) {
		foreach ($variables as $key => $value) {
			$string = str_replace($key, $value, $string);
		}
		return $string;
	}
	
	/*
		
		URLs
		
	*/
	
	public static function protocol() {
		if(isset($_SERVER["HTTPS"])) {
			return "https://";
		}
		return "http://";
	}
	
	public static function domain() {
		if(isset($_SERVER["HTTP_HOST"])) {
			return $_SERVER["HTTP_HOST"];
		}
		return $_SERVER["SERVER_NAME"];
	}
	
	public static function getSiteURL() {
		$protocol = self::protocol();
		$domain = self::domain();
		return "{$protocol}{$domain}";
	}	
	
	public static function getCmsURL($extra = "") {
		
		$site_url = self::getSiteURL();
		
		if (ENVIRONMENT == "development") {
			return "{$site_url}/vinteract_client/{$extra}";
		}
		
		return "https://www.vinteract.net.au/client/{$extra}";
		
	}
	
	/*
		
		Files / Paths
		
	*/
	
	public static function getFolderContentsAndSize($path = "") {
		
		$files = [];
		
		$size = 0;
		
		$directory = new DirectoryIterator($path);
		
		foreach ($directory as $fileInfo) {
			
		    if ($fileInfo->isDot()) {
				continue;
			};
			
			if ($fileInfo->getFilename() === "index.html") {
				continue;
			}
			
			$files[] = [
				"name" => $fileInfo->getFilename(),
				"path" => $fileInfo->getPath() . "/" . $fileInfo->getFilename(),
				"size" => $fileInfo->getSize(),
			];
			
			$size += $fileInfo->getSize();
			
		}
		
		return [
			"size" => $size,
			"count" => count($files),
			"files" => $files,
		];
		
	}
	
	public static function getRootPath($extra = "") {
		if (ENVIRONMENT === "development") {
			return "{$_SERVER["DOCUMENT_ROOT"]}/{$extra}";
		}
		return "/home/v/{$extra}";
	}
	
	public static function getPublicPath() {
		if (ENVIRONMENT === "development") {
			return self::getRootPath();
		}
		return self::getRootPath("public_html/");
	}
	
	public static function getCmsPath($extra = "") {
		$root = self::getPublicPath();
		if (ENVIRONMENT === "development") {
			return "{$root}/vinteract_client/{$extra}";
		}
		return "{$root}/client/{$extra}";
	}
	
	public static function getPaymentsApiPath($extra = "") {
		$root = self::getPublicPath();
		if (ENVIRONMENT === "development") {
			return "{$root}/vinteract_payments_api/{$extra}";
		}
		return "{$root}/__websites/pay.vinteract.net.au/{$extra}";
	}
	
	public static function getPaymentsApiUrl($extra = "") {
		if (ENVIRONMENT === "development") {
			return "http://localhost/vinteract_payments_api/{$extra}";
		}
		return "https://pay.vinteract.net.au/{$extra}";
	}
	
	/*
		
		Codes
		
	*/
	
	public static function generateUuid($data = null) {
		
		if (isset($data)) {
			$data = $data;
		} else {
			$data = random_bytes(16);
		}
		
		assert(strlen($data) == 16);

	    $data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
	    $data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10

	    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
		
	}
	
	public static function generateUniqueId() {
		return uniqid();
	}
	
	public static function generateHash($data = null) {
		$toHash = isset($data) ? $data : mt_rand();
		return hash("sha256",$toHash);
	}
	
	public static function generateRandomNumber($length = 10) {
	    $result = '';
	    for($i = 0; $i < $length; $i++) {
	        $result .= mt_rand(0, 9);
	    }
	    return $result;
	}
	
	public static function generatePublicId($length = 5) {
		
		$characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
		$randomString = '';
		
		for ($i = 0; $i < $length; $i++) {
			$randomString .= $characters[rand(0, strlen($characters) - 1)];
		}
		
		return $randomString;
		
	}
	
	/*
		
		Dates
		
	*/
	
	public static function currentTimeIsWithinRange($starts, $ends, $timezone = null) {
		
		$now = new DateTime();
		
		if (isset($timezone)) {
			$now->setTimezone(new DateTimeZone($timezone));
		}
		
		$opens = explode(":", date("H:i", strtotime($starts)));
		$opens_date = clone $now;
		$opens_date->setTime($opens[0], $opens[1]);
		
		$closes = explode(":", date("H:i", strtotime($ends)));
		$closes_date = clone $now;
		$closes_date->setTime($closes[0], $closes[1]);
		
		return ($now > $opens_date) and ($now < $closes_date);
		
	}
	
	public static function getWeekStartAndEndDates($year, $week, $timezone = null) {
		
		$date = new DateTime();
		
		if (isset($timezone)) {
			$date->setTimezone(new DateTimeZone($timezone));
		}
		
		$start = clone $date;
		$start->setTime(0, 0);
		$start->setISODate($year, $week);
		
		$end = clone $date;
		$end->setTime(0, 0);
		$end->setISODate($year, $week)->modify("+6 days");
		
		return [
			"start_iso8601_date" => $start->format("Y-m-d"),
			"start_iso8601_time" => $start->format("H:i:sP"),
			"start_iso8601_datetime" => $start->format("c"),
			"end_iso8601_date" => $end->format("Y-m-d"),
			"end_iso8601_time" => $end->format("H:i:sP"),
			"end_iso8601_datetime" => $end->format("c"),
		];
		
	}
	
	public static function getDateRange($start, $end, $include_end_date = false, $key = null) {
		
		/*
			Variables
		*/
		
		$dates = [];
		
		$start_date = new DateTime($start);
		
		$end_date = new DateTime($end);
		
		if ($include_end_date === true) {
			$end_date->modify("+1 day"); 
		}
		
		$period = new DatePeriod($start_date, new DateInterval("P1D"), $end_date);
		
		/*
			Iterate period and generate an array of formatted date values 
		*/
		
		foreach ($period as $index => $date) {
			
			$values = [
				"iso8601_date" => $date->format("Y-m-d"),
				"iso8601_time" => $date->format("H:i:sP"),
				"iso8601_datetime" => $date->format("c"),
				"day" => $date->format("l"),
				"day_of_the_month" => $date->format("j"),
				"month" => $date->format("F"),
				"year" => $date->format("Y"),
			];
			
			if (isset($key)) {
				$dates[$date->format($key)] = $values;
			} else {
				$dates[] = $values;
			}
			
		}
		
		/*
			Return dates
		*/
		
		return $dates;
		
	}
	
	public static function getPublicHolidays($year = "2019", $timezone = null) {
		
		/*
			Variables
		*/
		
		$dates = [];
		
		$easter_sunday_dates = [
			"2019" => "21 apr",
			"2020" => "12 apr",
			"2021" => "4 apr",
			"2022" => "17 apr",
			"2023" => "9 apr",
			"2024" => "31 mar",
			"2025" => "20 apr",
			"2026" => "5 apr",
			"2027" => "28 mar",
			"2028" => "16 apr",
			"2029" => "1 apr",
			"2030" => "21 apr",
		];
		
		$holidays = [
			
			/*
				National
			*/
			
			[
				"key" => "new_years_day",
				"name" => "New Year's Day",
				"date" => function() use ($year) {
					$date = new DateTime("first day of jan {$year}");
					if ($date->format("l") === "Saturday") {
						$date->add(new DateInterval("P2D"));
					}
					if ($date->format("l") === "Sunday") {
						$date->add(new DateInterval("P1D"));
					}
					return $date;
				},
			],
			[
				"key" => "australia_day",
				"name" => "Australia Day",
				"date" => function() use ($year) {
					$date = new DateTime("26 jan {$year}");
					if ($date->format("l") == "Saturday") {
						$date->add(new DateInterval("P2D"));
					}
					if ($date->format("l") == "Sunday") {
						$date->add(new DateInterval("P1D"));
					}
					return $date;
				},
			],
			[
				"key" => "good_friday",
				"name" => "Good Friday",
				"date" => function() use ($year, $easter_sunday_dates) {
					$date = new DateTime($easter_sunday_dates[$year] . " {$year}");
					$date->sub(new DateInterval("P2D"));
					return $date;
				},
			],
			[
				"key" => "easter_monday",
				"name" => "Easter Monday",
				"date" => function() use ($year, $easter_sunday_dates) {
					$date = new DateTime($easter_sunday_dates[$year] . " {$year}");
					$date->add(new DateInterval("P1D"));
					return $date;
				},
			],
			[
				"key" => "anzac_day",
				"name" => "Anzac Day",
				"date" => function() use ($year) {
					$date = new DateTime("25 apr {$year}");
					if ($date->format("l") == "Sunday") {
						$date->add(new DateInterval("P1D"));
					}
					return $date;
				},
			],
			[
				"key" => "queens_birthday",
				"name" => "Queen's Birthday",
				"date" => function() use ($year, $timezone) {
					switch ($timezone) {
						case "Australia/Perth":
							return new DateTime("last monday of sep {$year}");
						case "Australia/Brisbane":
							return new DateTime("first monday of oct {$year}");
						default:
							return new DateTime("second monday of jun {$year}");
					}
				},
			],
			[
				"key" => "labour_day",
				"name" => "Labour Day",
				"date" => function() use ($year, $timezone) {
					switch ($timezone) {
						case "Australia/Perth":
							return new DateTime("first monday of mar {$year}");
						case "Australia/Melbourne":
							return new DateTime("second monday of mar {$year}");
						case "Australia/Brisbane":
							return new DateTime("first monday of may {$year}");
						default:
							return new DateTime("first monday of oct {$year}");
					}
				},
				"relevant" => function() use ($timezone) {
					return $timezone !== "Australia/Darwin" and $timezone !== "Australia/Hobart";
				}
			],
			[
				"key" => "christmas_day",
				"name" => "Christmas Day",
				"date" => function() use ($year) {
					$date = new DateTime("25 dec {$year}");
					if ($date->format("l") == "Saturday") {
						$date->add(new DateInterval("P2D"));
					}
					if ($date->format("l") == "Sunday") {
						$date->add(new DateInterval("P1D"));
					}
					return $date;
				},
			],
			[
				"key" => "boxing_day",
				"name" => "Boxing Day",
				"date" => function() use ($year) {
					$date = new DateTime("26 dec {$year}");
					if ($date->format("l") == "Saturday") {
						$date->add(new DateInterval("P2D"));
					}
					if ($date->format("l") == "Monday") {
						$date->add(new DateInterval("P1D"));
					}
					return $date;
				},
			],
			
			/*
				Northern Territory
			*/
			
			[
				"key" => "may_day",
				"name" => "May Day",
				"date" => function() use ($year) {
					return new DateTime("first monday of may {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Darwin";
				}
			],
			
			[
				"key" => "picnic_day",
				"name" => "Picnic Day",
				"date" => function() use ($year) {
					return new DateTime("first monday of aug {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Darwin";
				}
			],
			
			/*
				ACT
			*/
			
			[
				"key" => "canberra_day",
				"name" => "Canberra Day",
				"date" => function() use ($year) {
					return new DateTime("second monday of mar {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Canberra";
				}
			],
			
			/*
				South Australia
			*/
			
			[
				"key" => "adelaide_cup_day",
				"name" => "Adelaide Cup Day",
				"date" => function() use ($year) {
					return new DateTime("second monday of mar {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Adelaide";
				}
			],
			
			/*
				Tasmania
			*/
			
			[
				"key" => "eight_hours_day",
				"name" => "Eight Hours Day", // AKA Labour Day
				"date" => function() use ($year) {
					return new DateTime("second monday of mar {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Hobart";
				},
			],
			
			/*
				Victoria
			*/
			
			[
				"key" => "melbourne_cup",
				"name" => "Melbourne Cup",
				"date" => function() use ($year) {
					return new DateTime("first tuesday of nov {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Melbourne";
				},
			],
			
			/*
				Western Australia
			*/
			
			[
				"key" => "western_australia_day",
				"name" => "Western Australia Day",
				"date" => function() use ($year) {
					return new DateTime("first monday of jun {$year}");
				},
				"relevant" => function() use ($timezone) {
					return $timezone === "Australia/Perth";
				},
			]
			
		];
		
		/*
			Iterate holidays and generate the information
		*/
		
		foreach($holidays as $holiday) {
			
			$date = $holiday["date"]();
			$relevant = true;
			
			if (array_key_exists("relevant", $holiday)) {
				$relevant = $holiday["relevant"]();
			}
			
			if ($relevant) {
				$dates[$date->format("Y-m-d")] = [
					"key" => $holiday["key"],
					"name" => $holiday["name"],
					"date_formatted" => $date->format("j F Y"),
					"date" => [
						"iso8601_date" => $date->format("Y-m-d"),
						"iso8601_time" => $date->format("H:i:sP"),
						"iso8601_datetime" => $date->format("c"),
						"day" => $date->format("l"),
						"day_of_the_month" => $date->format("j"),
						"month" => $date->format("F"),
						"year" => $date->format("Y"),
					],
				];
			}
			
		}
		
		/*
			Return dates
		*/
		
		return $dates;
		
	}
	
}
