<?php

class DXCC extends CI_Model {

	function __construct() {
		if(!$this->load->is_loaded('Genfunctions')) {
			$this->load->library('Genfunctions');
		}
	}

	/*
	 * Fetches a list of all dxcc's, both current and deleted
	 */
	function list() {
		$this->db->order_by('name', 'ASC');
		return $this->db->get('dxcc_entities');
	}

	/*
	 * Fetches a list of all current dxcc's (non-deleted)
	 */
	function list_current($orderer = 'name') {
		$this->db->where('end', null);
		$this->db->where('adif !=', 0);

		if ($orderer == 'name') {
			$this->db->order_by('name', 'ASC');
		} elseif ($orderer == 'prefix') {
			$this->db->order_by('prefix', 'ASC');
		}
		return $this->db->get('dxcc_entities');
	}

	function get_dxcc_array($dxccArray, $bands, $postdata, $location_list, $map = false) {
		$qsl = $this->genfunctions->gen_qsl_from_postdata($postdata);

		// Create DXCC to continent lookup array
		$dxccContinentLookup = [];

		foreach ($dxccArray as $dxcc) {
			if (isset($dxcc->cont) && isset($dxcc->adif)) {
				$dxccContinentLookup[$dxcc->adif] = $dxcc->cont;
			}
		}

		// Continent names and codes
		$continentNames = [
			'AF' => 'Africa',
			'AN' => 'Antarctica',
			'AS' => 'Asia',
			'EU' => 'Europe',
			'NA' => 'North America',
			'SA' => 'South America',
			'OC' => 'Oceania'
		];

		// Initialize continent counters
		$continentCounters = [
			'worked' => [],
			'confirmed' => []
		];

		foreach ($continentNames as $code => $name) {
			$continentCounters['worked'][$code] = [];
			$continentCounters['confirmed'][$code] = [];
			foreach ($bands as $band) {
				if (($postdata['band'] != 'SAT') && ($band == 'SAT')) {
					continue;
				}
				$continentCounters['worked'][$code][$band] = [];
				$continentCounters['confirmed'][$code][$band] = [];
			}
		}

		// Initialize matrix with all DXCC entities
		foreach ($dxccArray as $dxcc) {
			$adif = $dxcc->adif ?? '0';
			$name = $dxcc->name ?? '';
			$prefix = $dxcc->prefix ?? '';
			$enddate = $dxcc->end ?? null;

			if ($adif == '0') {
				$dxccMatrix[$adif]['name'] = $name;
			} else {
				$dxccMatrix[$adif]['name'] = ucwords(strtolower($name), "- (/");
			}
			$dxccMatrix[$adif]['prefix'] = $prefix;
			if ($postdata['includedeleted']) {
				$dxccMatrix[$adif]['Deleted'] = isset($enddate) ? 1 : 0;
			}

			// Initialize all bands to dash
			foreach ($bands as $band) {
				if (($postdata['band'] != 'SAT') && ($band == 'SAT')) {
					continue;
				}
				$dxccMatrix[$adif][$band] = '-';
			}
		}

		// Initialize summary counters only for the bands passed in
		foreach ($bands as $band) {
			if (($postdata['band'] != 'SAT') && ($band == 'SAT')) {
				continue;
			}
			$summary['worked'][$band] = 0;
			$summary['confirmed'][$band] = 0;
		}
		$summary['worked']['Total'] = 0;
		$summary['confirmed']['Total'] = 0;

		$summary['worked']['Slots'] = 0;
		$summary['confirmed']['Slots'] = 0;

		// Track unique DXCC/band combinations for totals
		$workedDxccs = [];  // [band][dxcc] => true
		$confirmedDxccs = []; // [band][dxcc] => true

		// Track worked status for each DXCC
		$dxccWorkedStatus = []; // [dxcc] => count

		// Create a lookup array for valid bands
		$validBands = array_flip($bands);

		// Get all DXCC data in efficient queries
		$dxccData = $this->getDxccData($location_list, $postdata);

		$dxccDataSat = $this->getDxccDataSat($location_list, $postdata);

		foreach ($dxccData as $dxcc) {
			// Skip if this band is not in our requested bands list
			if (!isset($validBands[$dxcc->col_band])) {
				continue;
			}

			// Track worked status for this DXCC
			if (!isset($dxccWorkedStatus[$dxcc->dxcc])) {
				$dxccWorkedStatus[$dxcc->dxcc] = 0;
			}
			$dxccWorkedStatus[$dxcc->dxcc]++;

			// Check if confirmed based on the confirmation types selected in postdata
			$isConfirmed = false;
			$confirmationLetters = '';
			if (isset($postdata['qsl']) && $postdata['qsl'] == 1 && $dxcc->qsl > 0) {
				$isConfirmed = true;
				$confirmationLetters .= 'Q';
			}
			if (isset($postdata['lotw']) && $postdata['lotw'] == 1 && $dxcc->lotw > 0) {
				$isConfirmed = true;
				$confirmationLetters .= 'L';
			}
			if (isset($postdata['eqsl']) && $postdata['eqsl'] == 1 && $dxcc->eqsl > 0) {
				$isConfirmed = true;
				$confirmationLetters .= 'E';
			}
			if (isset($postdata['qrz']) && $postdata['qrz'] == 1 && $dxcc->qrz > 0) {
				$isConfirmed = true;
				$confirmationLetters .= 'Z';
			}
			if (isset($postdata['clublog']) && $postdata['clublog'] == 1 && $dxcc->clublog > 0) {
				$isConfirmed = true;
				$confirmationLetters .= 'C';
			}

			if ($isConfirmed) {
				$dxccMatrix[$dxcc->dxcc][$dxcc->col_band] = '<div class="bg-success awardsBgSuccess" additional_successinfo=">C<"><a href=\'javascript:displayContacts("'.$dxcc->dxcc.'","'. $dxcc->col_band . '","'. $postdata['sat'] . '","'. $postdata['orbit'] . '","' . $postdata['mode'] . '","DXCC2","'.$qsl.'","'.$postdata['dateFrom'].'","'.$postdata['dateTo'].'")\'>'.$confirmationLetters.'</a></div>';
				// Track confirmed DXCCs for summary
				if (!isset($confirmedDxccs[$dxcc->col_band][$dxcc->dxcc])) {
					$confirmedDxccs[$dxcc->col_band][$dxcc->dxcc] = true;
					$summary['confirmed'][$dxcc->col_band]++;
					$summary['confirmed']['Slots']++;

					// Track by continent
					if (isset($dxccContinentLookup[$dxcc->dxcc])) {
						$continent = $dxccContinentLookup[$dxcc->dxcc];
						if (!isset($continentCounters['confirmed'][$continent][$dxcc->col_band][$dxcc->dxcc])) {
							$continentCounters['confirmed'][$continent][$dxcc->col_band][$dxcc->dxcc] = true;
						}
					}

				}
			} else {
				if ($postdata['worked'] != NULL) {
					$dxccMatrix[$dxcc->dxcc][$dxcc->col_band] = '<div class="bg-danger awardsBgWarning" ><a href=\'javascript:displayContacts("'.$dxcc->dxcc.'","'. $dxcc->col_band . '","'. $postdata['sat'] . '","' . $postdata['orbit'] . '","'. $postdata['mode'] . '","DXCC2", "", "'.$postdata['dateFrom'].'", "'.$postdata['dateTo'].'")\'>W</a></div>';
				}
			}

			// Track worked DXCCs for summary
			if (!isset($workedDxccs[$dxcc->col_band][$dxcc->dxcc])) {
				$workedDxccs[$dxcc->col_band][$dxcc->dxcc] = true;
				$summary['worked'][$dxcc->col_band]++;
				$summary['worked']['Slots']++;

				// Track by continent
				if (isset($dxccContinentLookup[$dxcc->dxcc])) {
					$continent = $dxccContinentLookup[$dxcc->dxcc];
					if (!isset($continentCounters['worked'][$continent][$dxcc->col_band][$dxcc->dxcc])) {
						$continentCounters['worked'][$continent][$dxcc->col_band][$dxcc->dxcc] = true;
					}
				}

            }
		}

		if ($postdata['band'] == 'SAT') {
			foreach ($dxccDataSat as $dxcc) {
				if (($postdata['band'] != 'SAT') && ($band == 'SAT')) {
					continue;
				}
				// Skip if this band is not in our requested bands list
				if (!isset($validBands[$dxcc->col_band])) {
					continue;
				}

				// Ensure string key for consistency
				$dxccKey = (string)$dxcc->dxcc;

				// Track worked status for this DXCC
				if (!isset($dxccWorkedStatus[$dxccKey])) {
					$dxccWorkedStatus[$dxccKey] = 0;
				}
				$dxccWorkedStatus[$dxccKey]++;

				// Check if confirmed based on the confirmation types selected in postdata
				$isConfirmed = false;
				$confirmationLetters = '';
				if (isset($postdata['qsl']) && $postdata['qsl'] == 1 && $dxcc->qsl > 0) {
					$isConfirmed = true;
					$confirmationLetters .= 'Q';
				}
				if (isset($postdata['lotw']) && $postdata['lotw'] == 1 && $dxcc->lotw > 0) {
					$isConfirmed = true;
					$confirmationLetters .= 'L';
				}
				if (isset($postdata['eqsl']) && $postdata['eqsl'] == 1 && $dxcc->eqsl > 0) {
					$isConfirmed = true;
					$confirmationLetters .= 'E';
				}
				if (isset($postdata['qrz']) && $postdata['qrz'] == 1 && $dxcc->qrz > 0) {
					$isConfirmed = true;
					$confirmationLetters .= 'Z';
				}
				if (isset($postdata['clublog']) && $postdata['clublog'] == 1 && $dxcc->clublog > 0) {
					$isConfirmed = true;
					$confirmationLetters .= 'C';
				}

				if ($isConfirmed) {
					$dxccMatrix[$dxccKey][$dxcc->col_band] = '<div class="bg-success awardsBgSuccess" additional_successinfo=">C<"><a href=\'javascript:displayContacts("'.$dxcc->dxcc.'","'. $dxcc->col_band . '","'. $postdata['sat'] . '","'. $postdata['orbit'] . '","' . $postdata['mode'] . '","DXCC2","'.$qsl.'","'.$postdata['dateFrom'].'","'.$postdata['dateTo'].'")\'>'.$confirmationLetters.'</a></div>';
					// Track confirmed DXCCs for summary
					if (!isset($confirmedDxccs[$dxcc->col_band][$dxccKey])) {
						$confirmedDxccs[$dxcc->col_band][$dxccKey] = true;
						$summary['confirmed'][$dxcc->col_band]++;

						// Track by continent
						if (isset($dxccContinentLookup[$dxccKey])) {
							$continent = $dxccContinentLookup[$dxccKey];
							if (!isset($continentCounters['confirmed'][$continent][$dxcc->col_band][$dxccKey])) {
								$continentCounters['confirmed'][$continent][$dxcc->col_band][$dxccKey] = true;
							}
						}
					}
				} else {
					if ($postdata['worked'] != NULL) {
						$dxccMatrix[$dxccKey][$dxcc->col_band] = '<div class="bg-danger awardsBgWarning" ><a href=\'javascript:displayContacts("'.$dxcc->dxcc.'","'. $dxcc->col_band . '","'. $postdata['sat'] . '","' . $postdata['orbit'] . '","'. $postdata['mode'] . '","DXCC2", "", "'.$postdata['dateFrom'].'", "'.$postdata['dateTo'].'")\'>W</a></div>';
					}
				}

				// Track worked DXCCs for summary
				if (!isset($workedDxccs[$dxcc->col_band][$dxccKey])) {
					$workedDxccs[$dxcc->col_band][$dxccKey] = true;
					$summary['worked'][$dxcc->col_band]++;

					// Track by continent
					if (isset($dxccContinentLookup[$dxccKey])) {
						$continent = $dxccContinentLookup[$dxccKey];
						if (!isset($continentCounters['worked'][$continent][$dxcc->col_band][$dxccKey])) {
							$continentCounters['worked'][$continent][$dxcc->col_band][$dxccKey] = true;
						}
					}

				}
			}
		}

		// Calculate totals across all bands (excluding SAT)
		$totalWorkedDxccs = [];
		$totalConfirmedDxccs = [];
		foreach ($workedDxccs as $band => $dxccs) {
			foreach ($dxccs as $dxcc => $true) {
				if (!isset($totalWorkedDxccs[$dxcc])) {
					$totalWorkedDxccs[$dxcc] = true;
					if ($band === 'SAT') {
						continue;
					}
					$totalWorkedDxccsExSat[$dxcc] = true;
					$summary['worked']['Total']++;
				}
			}
		}
		foreach ($confirmedDxccs as $band => $dxccs) {
			foreach ($dxccs as $dxcc => $true) {
				if (!isset($totalConfirmedDxccs[$dxcc])) {
					$totalConfirmedDxccs[$dxcc] = true;
					if ($band === 'SAT') {
						continue;
					}
					$totalConfirmedDxccsExSat[$dxcc] = true; // For calculating total worked excluding SAT
					$summary['confirmed']['Total']++;
				}
			}
		}

		// Remove DXCCs based on postdata filters
		foreach ($dxccMatrix as $dxcc => $data) {
			// Remove not-worked DXCCs if filter is disabled
			if ($postdata['notworked'] == NULL && !isset($dxccWorkedStatus[$dxcc])) {
				unset($dxccMatrix[$dxcc]);
				continue;
			}

			// Remove worked-only DXCCs if filter is disabled
			if ($postdata['worked'] == NULL && isset($dxccWorkedStatus[$dxcc]) && !isset($totalConfirmedDxccs[$dxcc])) {
				unset($dxccMatrix[$dxcc]);
				continue;
			}

			// Remove confirmed DXCCs if filter is disabled
			if ($postdata['confirmed'] == NULL && isset($totalConfirmedDxccs[$dxcc])) {
				unset($dxccMatrix[$dxcc]);
				continue;
			}
		}

		// If this is for the map, return simplified format
		if ($map) {
			$mapDxccs = [];
			if ($bands[0] == 'SAT') {
				foreach ($dxccMatrix as $dxcc => $data) {
					if (isset($confirmedDxccs['SAT'][$dxcc])) {
						$mapDxccs[$dxcc] = 'C';  // Confirmed
					} elseif (isset($workedDxccs['SAT'][$dxcc])) {
						$mapDxccs[$dxcc] = 'W';  // Worked but not confirmed
					} else {
						$mapDxccs[$dxcc] = '-';  // Not worked
					}
				}
			} else {
				foreach ($dxccMatrix as $dxcc => $data) {
					if (isset($totalConfirmedDxccsExSat[$dxcc])) {
						$mapDxccs[$dxcc] = 'C';  // Confirmed
					} elseif (isset($totalWorkedDxccsExSat[$dxcc])) {
						$mapDxccs[$dxcc] = 'W';  // Worked but not confirmed
					} else {
						$mapDxccs[$dxcc] = '-';  // Not worked
					}
				}
			}
			return $mapDxccs;
		}

		if (isset($dxccMatrix)) {
			// Calculate total DXCC entities from the matrix
			$total_dxcc_entities = count($dxccMatrix);

			// Calculate continent summary data
			$continentSummary = [];

			// Get total DXCC counts per continent from the database if not available in array
			$continentTotals = [];

			// Use continent data from the input array
			foreach ($dxccArray as $dxcc) {
				if (isset($dxcc->cont) && isset($dxcc->adif)) {
					// Respect the include deleted filter
					if ($postdata['includedeleted'] == NULL && isset($dxcc->end) && $dxcc->end != null) {
						// Skip deleted entities if include deleted is not checked
						continue;
					}

					if (!isset($continentTotals[$dxcc->cont])) {
						$continentTotals[$dxcc->cont] = 0;
					}
					$continentTotals[$dxcc->cont]++;
				}
			}

			// Calculate worked and confirmed totals per continent per band
			foreach ($continentNames as $code => $name) {
				if (!isset($continentSummary[$code])) {
					$continentSummary[$code] = [
						'name' => $name,
						'total' => isset($continentTotals[$code]) ? $continentTotals[$code] : 0,
						'worked' => [],
						'confirmed' => []
					];
				}

				// Initialize worked/confirmed arrays for each band
				foreach ($bands as $band) {
					if (($postdata['band'] != 'SAT') && ($band == 'SAT')) {
						continue;
					}
					$continentSummary[$code]['worked'][$band] = 0;
					$continentSummary[$code]['confirmed'][$band] = 0;
				}

				// Count worked and confirmed per band
				if (isset($continentCounters['worked'][$code])) {
					foreach ($continentCounters['worked'][$code] as $band => $dxccs) {
						if (isset($continentSummary[$code]['worked'][$band])) {
							$continentSummary[$code]['worked'][$band] = count($dxccs);
						}
					}
				}

				if (isset($continentCounters['confirmed'][$code])) {
					foreach ($continentCounters['confirmed'][$code] as $band => $dxccs) {
						if (isset($continentSummary[$code]['confirmed'][$band])) {
							$continentSummary[$code]['confirmed'][$band] = count($dxccs);
						}
					}
				}

				// Calculate totals (excluding SAT)
				$totalWorked = [];
				$totalConfirmed = [];
				foreach ($continentCounters['worked'][$code] as $band => $dxccs) {
					foreach ($dxccs as $dxcc => $true) {
						if (!isset($totalWorked[$dxcc])) {
							$totalWorked[$dxcc] = true;
							if ($band !== 'SAT' || $postdata['band'] == 'SAT') {
								$continentSummary[$code]['worked']['Total'] = ($continentSummary[$code]['worked']['Total'] ?? 0) + 1;
							}
						}
					}
				}

				foreach ($continentCounters['confirmed'][$code] as $band => $dxccs) {
					foreach ($dxccs as $dxcc => $true) {
						if (!isset($totalConfirmed[$dxcc])) {
							$totalConfirmed[$dxcc] = true;
							if ($band !== 'SAT' || $postdata['band'] == 'SAT') {
								$continentSummary[$code]['confirmed']['Total'] = ($continentSummary[$code]['confirmed']['Total'] ?? 0) + 1;
							}
						}
					}
				}
			}

			// Return the matrix data, summary and continent summary
			return ['matrix' => $dxccMatrix, 'summary' => $summary, 'continent_summary' => $continentSummary];
		} else {
			return 0;
		}
	}

	/*
	 * Gets all DXCC data with confirmation status in efficient query using MAX aggregation
	 */
	function getDxccData($location_list, $postdata) {
		$bindings = [];
		$sql = "SELECT thcv.col_dxcc as dxcc, thcv.col_band,
			MAX(case when thcv.col_lotw_qsl_rcvd ='Y' then 1 else 0 end) as lotw,
			MAX(case when thcv.col_qsl_rcvd = 'Y' then 1 else 0 end) as qsl,
			MAX(case when thcv.col_eqsl_qsl_rcvd = 'Y' then 1 else 0 end) as eqsl,
			MAX(case when thcv.COL_QRZCOM_QSO_DOWNLOAD_STATUS= 'Y' then 1 else 0 end) as qrz,
			MAX(case when thcv.COL_CLUBLOG_QSO_DOWNLOAD_STATUS = 'Y' then 1 else 0 end) as clublog
		FROM " . $this->config->item('table_name') . " thcv
		join dxcc_entities on thcv.col_dxcc = dxcc_entities.adif
		WHERE station_id IN (" . $location_list . ") AND thcv.col_dxcc > 0";

		// Mode filter
		if ($postdata['mode'] != 'All') {
			$sql .= " AND (thcv.col_mode = ? OR thcv.col_submode = ?)";
			$bindings[] = $postdata['mode'];
			$bindings[] = $postdata['mode'];
		}

		// Date filters
		if ($postdata['dateFrom'] != NULL) {
			$sql .= " AND thcv.col_time_on >= ?";
			$bindings[] = $postdata['dateFrom'] . ' 00:00:00';
		}

		if ($postdata['dateTo'] != NULL) {
			$sql .= " AND thcv.col_time_on <= ?";
			$bindings[] = $postdata['dateTo'] . ' 23:59:59';
		}

		$sql .= " and (thcv.col_prop_mode != 'SAT' or thcv.col_prop_mode is NULL)";

		// Continent filters
		$sql .= $this->addContinentsToQuery($postdata);

		// Deleted DXCC filter
		if ($postdata['includedeleted'] == NULL) {
			$sql .= " AND (SELECT end FROM dxcc_entities d WHERE d.adif = thcv.col_dxcc) IS NULL";
		}

		$sql .= " GROUP BY thcv.col_dxcc, thcv.col_band";

		$query = $this->db->query($sql, $bindings);
		return $query->result();
	}

	function getDxccDataSat($location_list, $postdata) {
		$bindings = [];
		$sql = "SELECT thcv.col_dxcc as dxcc, 'SAT' as col_band,
			MAX(case when thcv.col_lotw_qsl_rcvd ='Y' then 1 else 0 end) as lotw,
			MAX(case when thcv.col_qsl_rcvd = 'Y' then 1 else 0 end) as qsl,
			MAX(case when thcv.col_eqsl_qsl_rcvd = 'Y' then 1 else 0 end) as eqsl,
			MAX(case when thcv.COL_QRZCOM_QSO_DOWNLOAD_STATUS= 'Y' then 1 else 0 end) as qrz,
			MAX(case when thcv.COL_CLUBLOG_QSO_DOWNLOAD_STATUS = 'Y' then 1 else 0 end) as clublog
		FROM " . $this->config->item('table_name') . " thcv
		join dxcc_entities on thcv.col_dxcc = dxcc_entities.adif
		LEFT JOIN satellite on thcv.COL_SAT_NAME = satellite.name
		WHERE station_id IN (" . $location_list . ") AND thcv.col_dxcc > 0";

		// Mode filter
		if ($postdata['mode'] != 'All') {
			$sql .= " AND (thcv.col_mode = ? OR thcv.col_submode = ?)";
			$bindings[] = $postdata['mode'];
			$bindings[] = $postdata['mode'];
		}

		// Date filters
		if ($postdata['dateFrom'] != NULL) {
			$sql .= " AND thcv.col_time_on >= ?";
			$bindings[] = $postdata['dateFrom'] . ' 00:00:00';
		}

		if ($postdata['dateTo'] != NULL) {
			$sql .= " AND thcv.col_time_on <= ?";
			$bindings[] = $postdata['dateTo'] . ' 23:59:59';
		}

		// Satellite filter
		if ($postdata['sat'] != 'All') {
			$sql .= " AND thcv.col_sat_name = ?";
			$bindings[] = $postdata['sat'];
		}

		// Orbit filter
		$sql .= $this->addOrbitToQuery($postdata, $bindings);

		// Continent filters
		$sql .= $this->addContinentsToQuery($postdata);

		// Deleted DXCC filter
		if ($postdata['includedeleted'] == NULL) {
			$sql .= " AND (SELECT end FROM dxcc_entities d WHERE d.adif = thcv.col_dxcc) IS NULL";
		}

		$sql .= " and col_prop_mode = 'SAT'";

		$sql .= " GROUP BY thcv.col_dxcc";

		$query = $this->db->query($sql, $bindings);
		return $query->result();
	}

	/*
	 * Gets mode-specific DXCC totals with proper deduplication across all bands
	 */
	function getModeDxccTotals($location_list, $postdata) {
		$bindings = [];

		// Build confirmation condition
		$confirmationConditions = [];
		if (isset($postdata['qsl']) && $postdata['qsl'] == 1) {
			$confirmationConditions[] = 'thcv.col_qsl_rcvd = \'Y\'';
		}
		if (isset($postdata['lotw']) && $postdata['lotw'] == 1) {
			$confirmationConditions[] = 'thcv.col_lotw_qsl_rcvd = \'Y\'';
		}
		if (isset($postdata['eqsl']) && $postdata['eqsl'] == 1) {
			$confirmationConditions[] = 'thcv.col_eqsl_qsl_rcvd = \'Y\'';
		}
		if (isset($postdata['qrz']) && $postdata['qrz'] == 1) {
			$confirmationConditions[] = 'thcv.COL_QRZCOM_QSO_DOWNLOAD_STATUS = \'Y\'';
		}
		if (isset($postdata['clublog']) && $postdata['clublog'] == 1) {
			$confirmationConditions[] = 'thcv.COL_CLUBLOG_QSO_DOWNLOAD_STATUS = \'Y\'';
		}

		$confirmedCondition = !empty($confirmationConditions) ? '(' . implode(' OR ', $confirmationConditions) . ')' : '1=0';

		// Base SQL - get unique DXCC counts per mode with confirmation status
		$sql = "SELECT
			COALESCE(NULLIF(thcv.col_submode, ''), thcv.col_mode) as mode,
			COUNT(DISTINCT thcv.col_dxcc) as worked_count,
			COUNT(DISTINCT CASE WHEN $confirmedCondition THEN thcv.col_dxcc END) as confirmed_count
		FROM " . $this->config->item('table_name') . " thcv
		LEFT JOIN satellite ON thcv.COL_SAT_NAME = satellite.name
		WHERE station_id IN (" . $location_list . ") AND thcv.col_dxcc > 0";

		// Mode filter
		if ($postdata['mode'] != 'All') {
			$sql .= " AND (thcv.col_mode = ? OR thcv.col_submode = ?)";
			$bindings[] = $postdata['mode'];
			$bindings[] = $postdata['mode'];
		}

		// Date filters
		if ($postdata['dateFrom'] != NULL) {
			$sql .= " AND thcv.col_time_on >= ?";
			$bindings[] = $postdata['dateFrom'] . ' 00:00:00';
		}

		if ($postdata['dateTo'] != NULL) {
			$sql .= " AND thcv.col_time_on <= ?";
			$bindings[] = $postdata['dateTo'] . ' 23:59:59';
		}

		// Satellite filter
		if ($postdata['band'] == 'SAT') {
			$sql .= " AND thcv.col_prop_mode = 'SAT'";
			if ($postdata['sat'] != 'All') {
				$sql .= " AND thcv.col_sat_name = ?";
				$bindings[] = $postdata['sat'];
			}
			$sql .= $this->addOrbitToQuery($postdata, $bindings);
		} else if ($postdata['band'] != 'All') {
			$sql .= " AND (thcv.col_prop_mode != 'SAT' OR thcv.col_prop_mode IS NULL)";
			$sql .= " AND thcv.col_band = ?";
			$bindings[] = $postdata['band'];
		} else {
			$sql .= " AND (thcv.col_prop_mode != 'SAT' OR thcv.col_prop_mode IS NULL)";
		}

		// Continent filters
		$sql .= $this->addContinentsToQuery($postdata);

		// Deleted DXCC filter
		if ($postdata['includedeleted'] == NULL) {
			$sql .= " AND (SELECT end FROM dxcc_entities d WHERE d.adif = thcv.col_dxcc) IS NULL";
		}

		$sql .= " GROUP BY 1";

		$query = $this->db->query($sql, $bindings);
		$result = [];

		foreach ($query->result() as $row) {
			$result[$row->mode] = [
				'worked' => $row->worked_count,
				'confirmed' => $row->confirmed_count
			];
		}

		return $result;
	}

	function fetchDxcc($postdata, $location_list) {
		$bindings=[];

		$sql = "select adif, cont, prefix, name, date(end) Enddate, date(start) Startdate, lat, `long`
			from dxcc_entities";

		if ($postdata['notworked'] == NULL) {
			$sql .= " join (select col_dxcc from " . $this->config->item('table_name') . " thcv
			LEFT JOIN satellite on thcv.COL_SAT_NAME = satellite.name
			where station_id in (" . $location_list . ") and col_dxcc > 0";

			if ($postdata['band'] != 'All') {
				if ($postdata['band'] == 'SAT') {
					$sql .= " and col_prop_mode = ?";
					$bindings[]=$postdata['band'];
					if ($postdata['sat'] != 'All') {
						$sql .= " and col_sat_name = ?";
						$bindings[]=$postdata['sat'];
					}
				} else {
					$sql .= " and col_prop_mode !='SAT'";
					$sql .= " and col_band = ?";
					$bindings[]=$postdata['band'];
				}
			} else {
				$sql.=" and (col_prop_mode != 'SAT' or col_prop_mode is null)";
			}

			if ($postdata['dateFrom'] != NULL) {
				$sql .= " and col_time_on >= ?";
				$bindings[]=$postdata['dateFrom'] . ' 00:00:00';
			}

			if ($postdata['dateTo'] != NULL) {
				$sql .= " and col_time_on <= ?";
				$bindings[]=$postdata['dateTo'] . ' 23:59:59';
			}

			if ($postdata['mode'] != 'All') {
				$sql .= " and (col_mode = ? or col_submode = ?)";
				$bindings[]=$postdata['mode'];
				$bindings[]=$postdata['mode'];
			}

			$sql .= $this->addOrbitToQuery($postdata, $bindings);

			$sql .= ' group by col_dxcc) x on dxcc_entities.adif = x.col_dxcc';
		}

		$sql .= " where 1 = 1";

		if ($postdata['includedeleted'] == NULL) {
			$sql .= " and end is null";
		}

		$sql .= $this->addContinentsToQuery($postdata);

		$sql .= ' order by prefix';
		$query = $this->db->query($sql,$bindings);

		return $query->result();
	}

	function countDxccEntities($postdata) {
		$sql = "SELECT COUNT(*) as cnt FROM dxcc_entities WHERE adif > 0";
		if ($postdata['includedeleted'] == NULL) {
			$sql .= " AND end IS NULL";
		}
		$sql .= $this->addContinentsToQuery($postdata);
		$query = $this->db->query($sql);
		return $query->row()->cnt;
	}

	// Made function instead of repeating this several times
	function addContinentsToQuery($postdata) {
		$sql = '';
		if ($postdata['Africa'] == NULL) {
			$sql .= " and cont <> 'AF'";
		}

		if ($postdata['Europe'] == NULL) {
			$sql .= " and cont <> 'EU'";
		}

		if ($postdata['Asia'] == NULL) {
			$sql .= " and cont <> 'AS'";
		}

		if ($postdata['SouthAmerica'] == NULL) {
			$sql .= " and cont <> 'SA'";
		}

		if ($postdata['NorthAmerica'] == NULL) {
			$sql .= " and cont <> 'NA'";
		}

		if ($postdata['Oceania'] == NULL) {
			$sql .= " and cont <> 'OC'";
		}

		if ($postdata['Antarctica'] == NULL) {
			$sql .= " and cont <> 'AN'";
		}
		return $sql;
	}

	// Adds orbit type to query
	function addOrbitToQuery($postdata,&$binding) {
		$sql = '';
		if ($postdata['orbit'] != 'All') {
			$sql .= ' AND satellite.orbit = ?';
			$binding[]=$postdata['orbit'];
		}

		return $sql;
	}

	/*
	 * Functions below are all used in the calltester controller
	*/

	/*
     * Check the dxcc_prefixes table and return (dxcc, country)
     */
	public function check_dxcc_table($call, $date) {

		$date = date("Y-m-d", strtotime($date));
		$csadditions = '/^X$|^D$|^T$|^P$|^R$|^B$|^A$|^M$|^LH$|^L$|^J$|^SK$/';

		$dxcc_exceptions = $this->db->select('`entity`, `adif`, `cqz`, `cont`')
			->where('`call`', $call)
			->where('(start <= ', $date)
			->or_where('start is null)', NULL, false)
			->where('(end >= ', $date)
			->or_where('end is null)', NULL, false)
			->get('dxcc_exceptions');

		if ($dxcc_exceptions->num_rows() > 0) {
			$row = $dxcc_exceptions->row_array();
			return array($row['adif'], $row['entity'], $row['cqz'], $row['cont']);
		}
		if (preg_match('/(^KG4)[A-Z09]{3}/', $call)) {      // KG4/ and KG4 5 char calls are Guantanamo Bay. If 4 or 6 char, it is USA
			$call = "K";
		} elseif (preg_match('/(^OH\/)|(\/OH[1-9]?$)/', $call)) {   # non-Aland prefix!
			$call = "OH";                                             # make callsign OH = finland
		} elseif (preg_match('/(^CX\/)|(\/CX[1-9]?$)/', $call)) {   # non-Antarctica prefix!
			$call = "CX";                                             # make callsign CX = Uruguay
		} elseif (preg_match('/(^3D2R)|(^3D2.+\/R)/', $call)) {     # seems to be from Rotuma
			$call = "3D2/R";                                          # will match with Rotuma
		} elseif (preg_match('/^3D2C/', $call)) {                   # seems to be from Conway Reef
			$call = "3D2/C";                                          # will match with Conway
		} elseif (preg_match('/(^LZ\/)|(\/LZ[1-9]?$)/', $call)) {   # LZ/ is LZ0 by DXCC but this is VP8h
			$call = "LZ";
		} elseif (preg_match('/(^KG4)[A-Z09]{2}/', $call)) {
			$call = "KG4";
		} elseif (preg_match('/(^KG4)[A-Z09]{1}/', $call)) {
			$call = "K";
		} elseif (preg_match('/\w\/\w/', $call)) {
			if (preg_match_all('/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/', $call, $matches)) {
				$prefix = $matches[1][0];
				$callsign = $matches[3][0];
				$suffix = $matches[5][0];
				if ($prefix) {
					$prefix = substr($prefix, 0, -1); # Remove the / at the end
				}
				if ($suffix) {
					$suffix = substr($suffix, 1); # Remove the / at the beginning
				};
				if (preg_match($csadditions, $suffix)) {
					if ($prefix) {
						$call = $prefix;
					} else {
						$call = $callsign;
					}
				} else {
					$result = $this->wpx($call, 1);                       # use the wpx prefix instead
					if ($result == '') {
						$row['adif'] = 0;
						$row['entity'] = '- NONE -';
						$row['cqz'] = 0;
						$row['cont'] = '';
						return array($row['adif'], $row['entity'], $row['cqz'], $row['cont']);
					} else {
						$call = $result . "AA";
					}
				}
			}
		}

		$len = strlen($call);
		$dxcc_array = [];
		// Fetch all candidates in one shot instead of looping
		$dxcc_result = $this->db->query("SELECT `call`, `entity`, `adif`, `cqz`, `cont`
		    FROM `dxcc_prefixes`
		    WHERE ? like concat(`call`,'%')
		    and `call` like ?
		    AND (`start` <= ?  OR start is null)
		    AND (`end` >= ?  OR end is null) order by length(`call`) desc limit 1", array($call, substr($call, 0, 1) . '%', $date, $date));

		foreach ($dxcc_result->result_array() as $row) {
			$dxcc_array[$row['call']] = $row;
		}

		// query the table, removing a character from the right until a match
		for ($i = $len; $i > 0; $i--) {
			//printf("searching for %s\n", substr($call, 0, $i));
			if (array_key_exists(substr($call, 0, $i), $dxcc_array)) {
				$row = $dxcc_array[substr($call, 0, $i)];
				// $row = $dxcc_result->row_array();
				return array($row['adif'], $row['entity'], $row['cqz'], $row['cont']);
			}
		}

		return array("Not Found", "Not Found");
	}

	function wpx($testcall, $i) {
		$prefix = '';
		$a = '';
		$b = '';
		$c = '';

		$lidadditions = '/^QRP$|^LGT$/';
		$csadditions = '/^X$|^D$|^T$|^P$|^R$|^B$|^A$|^M$|^LH$|^L$|^J$|^SK$/';
		$noneadditions = '/^MM$|^AM$/';

		# First check if the call is in the proper format, A/B/C where A and C
		# are optional (prefix of guest country and P, MM, AM etc) and B is the
		# callsign. Only letters, figures and "/" is accepted, no further check if the
		# callsign "makes sense".
		# 23.Apr.06: Added another "/X" to the regex, for calls like RV0AL/0/P
		# as used by RDA-DXpeditions....

		if (preg_match_all('/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/', $testcall, $matches)) {

			# Now $1 holds A (incl /), $3 holds the callsign B and $5 has C
			# We save them to $a, $b and $c respectively to ensure they won't get
			# lost in further Regex evaluations.
			$a = $matches[1][0];
			$b = $matches[3][0];
			$c = $matches[5][0];

			if ($a) {
				$a = substr($a, 0, -1); # Remove the / at the end
			}
			if ($c) {
				$c = substr($c, 1); # Remove the / at the beginning
			};

			# In some cases when there is no part A but B and C, and C is longer than 2
			# letters, it happens that $a and $b get the values that $b and $c should
			# have. This often happens with liddish callsign-additions like /QRP and
			# /LGT, but also with calls like DJ1YFK/KP5. ~/.yfklog has a line called
			# "lidadditions", which has QRP and LGT as defaults. This sorts out half of
			# the problem, but not calls like DJ1YFK/KH5. This is tested in a second
			# try: $a looks like a call (.\d[A-Z]) and $b doesn't (.\d), they are
			# swapped. This still does not properly handle calls like DJ1YFK/KH7K where
			# only the OP's experience says that it's DJ1YFK on KH7K.
			if (!$c && $a && $b) {                          # $a and $b exist, no $c
				if (preg_match($lidadditions, $b)) {        # check if $b is a lid-addition
					$b = $a;
					$a = null;                              # $a goes to $b, delete lid-add
				} elseif ((preg_match('/\d[A-Z]+$/', $a)) && (preg_match('/\d$/', $b) || preg_match('/^[A-Z]\d[A-Z]$/', $b))) {   # check for call in $a
					$temp = $b;
					$b = $a;
					$a = $temp;
				}
			}

			# *** Added later ***  The check didn't make sure that the callsign
			# contains a letter. there are letter-only callsigns like RAEM, but not
			# figure-only calls.

			if (preg_match('/^[0-9]+$/', $b)) {            # Callsign only consists of numbers. Bad!
				return null;            # exit, undef
			}

			# Depending on these values we have to determine the prefix.
			# Following cases are possible:
			#
			# 1.    $a and $c undef --> only callsign, subcases
			# 1.1   $b contains a number -> everything from start to number
			# 1.2   $b contains no number -> first two letters plus 0
			# 2.    $a undef, subcases:
			# 2.1   $c is only a number -> $a with changed number
			# 2.2   $c is /P,/M,/MM,/AM -> 1.
			# 2.3   $c is something else and will be interpreted as a Prefix
			# 3.    $a is defined, will be taken as PFX, regardless of $c

			if (($a == null) && ($c == null)) {                     # Case 1
				if (preg_match('/\d/', $b)) {                       # Case 1.1, contains number
					preg_match('/(.+\d)[A-Z]*/', $b, $matches);     # Prefix is all but the last
					$prefix = $matches[1];                          # Letters
				} else {                                            # Case 1.2, no number
					$prefix = substr($b, 0, 2) . "0";               # first two + 0
				}
			} elseif (($a == null) && (isset($c))) {                # Case 2, CALL/X
				if (preg_match('/^(\d)/', $c)) {                    # Case 2.1, number
					preg_match('/(.+\d)[A-Z]*/', $b, $matches);     # regular Prefix in $1
					# Here we need to find out how many digits there are in the
					# prefix, because for example A45XR/0 is A40. If there are 2
					# numbers, the first is not deleted. If course in exotic cases
					# like N66A/7 -> N7 this brings the wrong result of N67, but I
					# think that's rather irrelevant cos such calls rarely appear
					# and if they do, it's very unlikely for them to have a number
					# attached.   You can still edit it by hand anyway..
					if (preg_match('/^([A-Z]\d{2,})$/', $matches[1])) {        # e.g. A45   $c = 0
						$prefix = $matches[1] . $c;  # ->   A40
					} else {                         # Otherwise cut all numbers
						preg_match('/(.*[A-Z])\d+/', $matches[1], $match); # Prefix w/o number in $1
						$prefix = $match[1] . $c; # Add attached number
					}
				} elseif (preg_match($csadditions, $c)) {
					preg_match('/(.+\d)[A-Z]*/', $b, $matches);     # Known attachment -> like Case 1.1
					$prefix = $matches[1];
				} elseif (preg_match($noneadditions, $c)) {
					return '';
				} elseif (preg_match('/^\d\d+$/', $c)) {            # more than 2 numbers -> ignore
					preg_match('/(.+\d)[A-Z]* /', $b, $matches);    # see above
					$prefix = $matches[1][0];
				} else {                                            # Must be a Prefix!
					if (preg_match('/\d$/', $c)) {                  # ends in number -> good prefix
						$prefix = $c;
					} else {                                        # Add Zero at the end
						$prefix = $c . "0";
					}
				}
			} elseif (($a) && (preg_match($noneadditions, $c))) {                # Case 2.1, X/CALL/X ie TF/DL2NWK/MM - DXCC none
				return '';
			} elseif ($a) {
				# $a contains the prefix we want
				if (preg_match('/\d$/', $a)) {                      # ends in number -> good prefix
					$prefix = $a;
				} else {                                            # add zero if no number
					$prefix = $a . "0";
				}
			}
			# In very rare cases (right now I can only think of KH5K and KH7K and FRxG/T
			# etc), the prefix is wrong, for example KH5K/DJ1YFK would be KH5K0. In this
			# case, the superfluous part will be cropped. Since this, however, changes the
			# DXCC of the prefix, this will NOT happen when invoked from with an
			# extra parameter $_[1]; this will happen when invoking it from &dxcc.

			if (preg_match('/(\w+\d)[A-Z]+\d/', $prefix, $matches) && $i == null) {
				$prefix = $matches[1][0];
			}
			return $prefix;
		} else {
			return '';
		}
	}

	public function dxcc_lookup($call, $date) {

		$date = date("Y-m-d", strtotime($date));
		$csadditions = '/^X$|^D$|^T$|^P$|^R$|^B$|^A$|^M$|^LH$|^L$|^J$|^SK$/';

		$dxcc_exceptions = $this->db->select('`entity`, `adif`, `cqz`,`cont`,`long`,`lat`')
			->where('`call`', $call)
			->where('(start <= ', $date)
			->or_where('start is null)', NULL, false)
			->where('(end >= ', $date)
			->or_where('end is null)', NULL, false)
			->get('dxcc_exceptions');
		if ($dxcc_exceptions->num_rows() > 0) {
			$row = $dxcc_exceptions->row_array();
			return $row;
		} else {

			if (preg_match('/(^KG4)[A-Z09]{3}/', $call)) {       // KG4/ and KG4 5 char calls are Guantanamo Bay. If 4 or 6 char, it is USA
				$call = "K";
			} elseif (preg_match('/(^OH\/)|(\/OH[1-9]?$)/', $call)) {   # non-Aland prefix!
				$call = "OH";                                             # make callsign OH = finland
			} elseif (preg_match('/(^CX\/)|(\/CX[1-9]?$)/', $call)) {   # non-Antarctica prefix!
				$call = "CX";                                             # make callsign CX = Uruguay
			} elseif (preg_match('/(^3D2R)|(^3D2.+\/R)/', $call)) {     # seems to be from Rotuma
				$call = "3D2/R";                                          # will match with Rotuma
			} elseif (preg_match('/^3D2C/', $call)) {                   # seems to be from Conway Reef
				$call = "3D2/C";                                          # will match with Conway
			} elseif (preg_match('/(^LZ\/)|(\/LZ[1-9]?$)/', $call)) {   # LZ/ is LZ0 by DXCC but this is VP8h
				$call = "LZ";
			} elseif (preg_match('/(^KG4)[A-Z09]{2}/', $call)) {
				$call = "KG4";
			} elseif (preg_match('/(^KG4)[A-Z09]{1}/', $call)) {
				$call = "K";
			} elseif (preg_match('/\w\/\w/', $call)) {
				if (preg_match_all('/^((\d|[A-Z])+\/)?((\d|[A-Z]){3,})(\/(\d|[A-Z])+)?(\/(\d|[A-Z])+)?$/', $call, $matches)) {
					$prefix = $matches[1][0];
					$callsign = $matches[3][0];
					$suffix = $matches[5][0];
					if ($prefix) {
						$prefix = substr($prefix, 0, -1); # Remove the / at the end
					}
					if ($suffix) {
						$suffix = substr($suffix, 1); # Remove the / at the beginning
					};
					if (preg_match($csadditions, $suffix)) {
						if ($prefix) {
							$call = $prefix;
						} else {
							$call = $callsign;
						}
					} else {
						$result = $this->wpx($call, 1);                       # use the wpx prefix instead
						if ($result == '') {
							$row['adif'] = 0;
							$row['cont'] = '';
							$row['entity'] = '- NONE -';
							$row['ituz'] = 0;
							$row['cqz'] = 0;
							$row['long'] = '0';
							$row['lat'] = '0';
							return $row;
						} else {
							$call = $result . "AA";
						}
					}
				}
			}

			$len = strlen($call);
			$dxcc_array = [];

			// Fetch all candidates in one shot instead of looping
			$dxcc_result = $this->db->query("SELECT `dxcc_prefixes`.`record`, `dxcc_prefixes`.`call`, `dxcc_prefixes`.`entity`, `dxcc_prefixes`.`adif`, `dxcc_prefixes`.`cqz`, `dxcc_entities`.`ituz`, `dxcc_prefixes`.`cont`, `dxcc_prefixes`.`long`, `dxcc_prefixes`.`lat`, `dxcc_prefixes`.`start`, `dxcc_prefixes`.`end`
			    FROM `dxcc_prefixes`
			    LEFT JOIN `dxcc_entities` ON `dxcc_entities`.`adif` = `dxcc_prefixes`.`adif`
			    WHERE ? like concat(`call`,'%')
			    and `dxcc_prefixes`.`call` like ?
			    AND (`dxcc_prefixes`.`start` <= ?  OR `dxcc_prefixes`.`start` is null)
			    AND (`dxcc_prefixes`.`end` >= ?  OR `dxcc_prefixes`.`end` is null) order by length(`call`) desc limit 1", array($call, substr($call, 0, 1) . '%', $date, $date));

			foreach ($dxcc_result->result_array() as $row) {
				$dxcc_array[$row['call']] = $row;
			}

			// query the table, removing a character from the right until a match
			for ($i = $len; $i > 0; $i--) {
				if (array_key_exists(substr($call, 0, $i), $dxcc_array)) {
					$row = $dxcc_array[substr($call, 0, $i)];
					return $row;
				}
			}
		}

		return array(
			'adif' => 0,
			'cqz' => 0,
			'ituz' => 0,
			'long' => '',
			'lat' => '',
			'entity' => 'None',
		);
	}

	function getQsos($station_id) {
		ini_set('memory_limit', '-1');
		$sql = 'select distinct col_country, col_call, col_dxcc, date(col_time_on) date, station_profile.station_profile_name, col_primary_key
			from ' . $this->config->item('table_name') . '
			join station_profile on ' . $this->config->item('table_name') . '.station_id = station_profile.station_id
			where station_profile.user_id = ?';
		$params[] = $this->session->userdata('user_id');

		if ($station_id && is_numeric($station_id)) {
			$sql .= ' and ' . $this->config->item('table_name') . '.station_id = ?';
			$params[] = $station_id;
		}

		$sql .= ' order by station_profile.station_profile_name asc, date desc';

        $query = $this->db->query($sql, $params);

		return $query;
	}

	function mode_progress($total_dxcc_entities, $postdata, $location_list) {
		$modeSummary = [];

		$modeTotals = $this->getModeDxccTotals($location_list, $postdata);

		$allModes = array_keys($modeTotals);
		sort($allModes);

		foreach ($allModes as $mode) {
			$modeSummary[$mode] = [
				'name' => $mode,
				'total' => $total_dxcc_entities,
				'worked' => ['Total' => $modeTotals[$mode]['worked'] ?? 0],
				'confirmed' => ['Total' => $modeTotals[$mode]['confirmed'] ?? 0],
			];
		}

		return $modeSummary;
	}
}
