<?php if (! defined('BASEPATH')) exit('No direct script access allowed');

class Qra {

	/*
	*	Class Description: QRA handles manipulation of the Gridsquares used within amateur radio
	*
	*	Units of measurement are the following
	*
	*	Info: Distance Function
	*
	*	M = Miles
	*	K = Kilometers
	*	N = Nautical Miles
	*/

	// Name: qra2latlong
	// Task: convert qra to lat/long
	function qra2latlong($strQRA) {
		return qra2latlong($strQRA);
	}

	// calculate  the bearing between two squares
	function bearing($tx, $rx, $unit = 'M', $ant_path = null) {
		$my = qra2latlong($tx);
		$stn = qra2latlong($rx);

		if ($my !== false && $stn !== false) {
			$bearing = bearing($my[0], $my[1], $stn[0], $stn[1], $unit, $ant_path);
			return $bearing;
		} else {
			return false;
		}
	}

	/*
	* Function: calculate the distance between two gridsqaures
	*
	*	Inputs are QRA's TX and TX and the unit
	*
	*/
	function distance($tx, $rx, $unit = 'M', $ant_path = null) {
		if (empty($tx) || empty($rx)) {
			return null;
		}
		// Calc LatLongs
		$my = qra2latlong($tx);
		$stn = qra2latlong($rx);

		// Check if qra2latlong returned valid values
		if ($my && $stn) {
			// Feed in Lat Longs plus the unit type
			try {
				$total_distance = calc_distance($my[0], $my[1], $stn[0], $stn[1], $unit, $ant_path);
			} catch (Exception $e) {
				$total_distance = false;
			}

			// Return the distance
			return $total_distance;
		} else {
			// Handle the case where qra2latlong did not return valid values
			return false;
		}
	}

	/*
	* Function returns just the bearing
	*  Input locator1 and locator2
	*/
	function get_bearing($tx, $rx, $ant_path = null) {
		$my = qra2latlong($tx);
		$stn = qra2latlong($rx);
		if ($my !== false && $stn !== false) {
			return get_bearing($my[0], $my[1], $stn[0], $stn[1], $ant_path);
		}
		return false;
	}

	/*
	Find the Midpoint between two gridsquares using lat / long

	Needs following passed

	$coords[]=array('lat' => '53.344104','lng'=>'-6.2674937');
	$coords[]=array('lat' => '51.5081289','lng'=>'-0.128005');

*/

	function get_midpoint($coords) {
		$count_coords = count($coords);
		$xcos = 0.0;
		$ycos = 0.0;
		$zsin = 0.0;

		foreach ($coords as $lnglat) {
			$lat = $lnglat['lat'] * pi() / 180;
			$lon = $lnglat['lng'] * pi() / 180;

			$acos = cos($lat) * cos($lon);
			$bcos = cos($lat) * sin($lon);
			$csin = sin($lat);
			$xcos += $acos;
			$ycos += $bcos;
			$zsin += $csin;
		}

		$xcos /= $count_coords;
		$ycos /= $count_coords;
		$zsin /= $count_coords;
		$lon = atan2($ycos, $xcos);
		$sqrt = sqrt($xcos * $xcos + $ycos * $ycos);
		$lat = atan2($zsin, $sqrt);

		return array($lat * 180 / pi(), $lon * 180 / pi());
	}

	function echoQrbCalcLink($mygrid, $grid, $vucc, $isVisitor = false) {
		$echo = "";
		$target = !empty($grid) ? $grid : $vucc;
		if (!empty($target)) {
			$echo = html_escape($target);
			if (!empty($mygrid)) {
				$echo .= (!$isVisitor) ? (' <a href=\'javascript:spawnQrbCalculator(' . js_escape($mygrid) . ',' . js_escape($target) . ')\'><i class="fas fa-globe"></i></a>') : '';
			}
		}
		return $echo;
	}

	/**
	 * Function to calculate the maximum distance in a bunch of QSOs
	 *
	 * @param string 	$mylocator	Maidenhead locator of the station
	 * @param object 	$qsos		Object of QSOs
	 * @param string 	$unit		Unit of measurement
	 * 
	 * @return object 	$maxdistanceqso		Object of the QSO with the maximum distance
	 * @return float 	$maxdistance		Maximum distance
	 */

	function getMaxDistanceQSO($mylocator, $qsos, $unit = "M") {
		//return nothing for empty QSO set
		if (count($qsos) < 1) {
			return null;
		}

		//collect maximum data
		$maxdistance = 0;
		$maxdistanceqso = null;

		//iterate through all qsos
		foreach ($qsos as $row) {

			if (empty($row['gridsquare_recv'])) {
				continue;
			}

			//get distance in kilometers
			$distance = $this->distance($mylocator, $row['gridsquare_recv'], $unit);

			//store new highscore if present
			if ($distance > $maxdistance) {
				$maxdistance = $distance;
				$maxdistanceqso = $row;
			}
		}

		$result = [];
		$result['qso'] = $maxdistanceqso;
		$result['distance'] = $maxdistance;

		//return findings
		return $result;
	}

	/**
	 * Function to calculate the center of a bunch of coordinates
	 * Source: https://www.phpsnippet.com/snippet/center-point-of-multiple-gps-coordinates-with-php
	 * 
	 * @param array 	$coordinates	Array of coordinates [latitude, longitude]
	 * 					Example:
	 * 					$coordinates = [
	 *						[37.7797, -122.41924],
	 *						[37.77323, -122.41114],
	 *						[37.79203, -122.40864],
	 *						[37.7952, -122.4222]
	 *					];
	 * 					
	 * @return array 	[latitude, longitude]	Center of the coordinates
	 */

	function getCenterLatLng($coordinates) {
		$x = $y = $z = $n = 0;
		foreach ($coordinates as $point)
		{
			if (isset($point[0]) && isset($point[1])) {
				$n++;
				$lt = $point[0] * pi() / 180;
				$lg = $point[1] * pi() / 180;
				$x += cos($lt) * cos($lg);
				$y += cos($lt) * sin($lg);
				$z += sin($lt);
			}
		}
		$x /= $n;
		$y /= $n;

		return [atan2(($z / $n), sqrt($x * $x + $y * $y)) * 180 / pi(), atan2($y, $x) * 180 / pi()];
	}

	function validate_grid($grid, $type='any') {
		// (Try to) Detect placeholders like AA00aa (returned from qrz.com for example)
		if (strlen($grid) == 6 && strtoupper($grid) == 'AA00AA') {
			return false;
		}
		if ($type == 'vucc') goto vucc;
		// Allow 6-digit locator
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2}$/', $grid)) return true;
		// Allow 4-digit locator
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}$/', $grid)) return true;
		// Allow 2-digit locator
		if (preg_match('/^[A-Ra-r]{2}$/', $grid)) return true;
		// Allow 8-digit locator
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2}[0-9]{2}$/', $grid)) return true;
		// Allow 10-digit locator
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2}[0-9]{2}[A-Za-z]{2}$/', $grid)) return true;
		if ($type == 'grid') return false;

		vucc:
		// Allow 4-digit grid line
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2},[A-Ra-r]{2}[0-9]{2}$/', $grid)) return true;
		// Allow 6-digit grid line
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2},[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2}$/', $grid)) return true;

		// Allow 4-digit grid corner
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2},[A-Ra-r]{2}[0-9]{2},[A-Ra-r]{2}[0-9]{2},[A-Ra-r]{2}[0-9]{2}$/', $grid)) return true;
		// Allow 6-digit grid corner
		if (preg_match('/^[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2},[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2},[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2},[A-Ra-r]{2}[0-9]{2}[A-Za-z]{2}$/', $grid)) return true;
		return false;
	}
}


function calc_distance($lat1, $lon1, $lat2, $lon2, $unit = 'M', $ant_path = null) {
	$theta = $lon1 - $lon2;
	$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
	$dist = acos($dist);
	$dist = rad2deg($dist);
	$dist = $dist * 60 * 1.1515;

	if ($ant_path == "L") { // we only need to calculate the distance for long paths, all other paths are the same
		$dist = 24880 - $dist;
	}

	if ($unit == "K") {
		$dist *= 1.609344;
	} else if ($unit == "N") {
		$dist *= 0.8684;
	}
	if ((is_nan($dist)) || !(is_numeric($dist))) { 	// Special-Case, catch same grid and/or Errors
		$dist = 0;
	}
	return round($dist, 1);
}

function bearing($lat1, $lon1, $lat2, $lon2, $unit = 'M', $ant_path = null) {
	$dist = calc_distance($lat1, $lon1, $lat2, $lon2, $unit, $ant_path);
	$dist = round($dist, 0);

	$bearing = get_bearing($lat1, $lon1, $lat2, $lon2);
	if ($ant_path == 'L') {
		$bearing = ($bearing + 180) % 360;
	}

	$dirs = array("N", "E", "S", "W");

	$rounded = round($bearing / 22.5) % 16;
	if (($rounded % 4) == 0) {
		$dir = $dirs[$rounded / 4];
	} else {
		$dir = $dirs[2 * floor(((floor($rounded / 4) + 1) % 4) / 2)];
		$dir .= $dirs[1 + 2 * floor($rounded / 8)];
		#if ($rounded % 2 == 1)
		#  $dir = $dirs[round_to_int($rounded/4) % 4] . "-" . $dir;
	}
	$var_dist = "";
	#return $dir;
	if (isset($dist)) {
		$var_dist = $dist;
		switch ($unit) {
			case 'M':
				$var_dist .= " miles";
				break;
			case 'N':
				$var_dist .= " nautic miles";
				break;
			case 'K':
				$var_dist .= " kilometers";
				break;
		}
	}
	return round($bearing, 0) . "&#186; " . $dir . " " . $var_dist;
}

function get_bearing($lat1, $lon1, $lat2, $lon2, $ant_path = null) {
	$bearing = (int)(rad2deg(atan2(sin(deg2rad($lon2) - deg2rad($lon1)) * cos(deg2rad($lat2)), cos(deg2rad($lat1)) * sin(deg2rad($lat2)) - sin(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($lon2) - deg2rad($lon1)))) + 360) % 360;
	if ($ant_path == 'L') {	// Long path is the reciprocal of the short-path bearing
		$bearing = ($bearing + 180) % 360;
	}
	return $bearing;
}

function qra2latlong($strQRA) {
	$strQRA = preg_replace('/\s+/', '', ($strQRA ?? ''));
	if (substr_count($strQRA, ',') > 0) {
		$grids = explode(',', $strQRA);
		if (count($grids) != 2 && count($grids) != 4) {
			return false;
		}
		$lengths = array_map('strlen', $grids);
		if (count(array_unique($lengths)) != 1) {
			return false;
		}
		$lat = 0; $lng = 0;
		foreach ($grids as $g) {
			$c = qra2latlong($g);
			if (!$c) { return false; }
			$lat += $c[0]; $lng += $c[1];
		}
		$n = count($grids);
		return array($lat / $n, $lng / $n);
	}

	if ((strlen($strQRA) % 2 == 0) && (strlen($strQRA) <= 10)) {	// Check if QRA is EVEN (the % 2 does that) and smaller/equal 8
		$strQRA = strtoupper($strQRA);
		if     (strlen($strQRA) == 2)  $strQRA .= "55AA00AA";
		elseif (strlen($strQRA) == 4)  $strQRA .= "MM00AA";
		elseif (strlen($strQRA) == 6)  $strQRA .= "55AA";
		elseif (strlen($strQRA) == 8)  $strQRA .= "MM";

		if (!preg_match('/^[A-R]{2}[0-9]{2}[A-X]{2}[0-9]{2}[A-X]{2}$/', $strQRA)) {
			return false;
		}

		list($a, $b, $c, $d, $e, $f, $g, $h, $i, $j) = str_split($strQRA, 1);	// Maidenhead is always alternating. e.g. "AA00AA00AA00" - doesn't matter how deep. 2 chars, 2 numbers, etc.
		$a = ord($a) - ord('A');
		$b = ord($b) - ord('A');
		$c = ord($c) - ord('0');
		$d = ord($d) - ord('0');
		$e = ord($e) - ord('A');
		$f = ord($f) - ord('A');
		$g = ord($g) - ord('0');
		$h = ord($h) - ord('0');
		$i = ord($i) - ord('A');
		$j = ord($j) - ord('A');

		$nLong = ($a * 20) + ($c * 2) + ($e / 12) + ($g / 120) + ($i / 2880) - 180;
		$nLat = ($b * 10) + $d + ($f / 24) + ($h / 240) + ($j / 5760) - 90;

		$arLatLong = array($nLat, $nLong);
		return $arLatLong;
	} else {
		return array(0, 0);
	}
}



/* End of file Qra.php */
