["94A"] = "Charlie Lake, British Columbia|Charlie Lake",
["94A1"] = "Shearer Dale",
["94A2"] = "Fort St. John, British Columbia|Fort St. John",
["94A3"] = "Moberly River",
["94A4"] = "Hudson's Hope|Hudson Hope",
["94A5"] = "Ground Birch Creek",
["94A6"] = "Bear Flat, British Columbia|Bear Flat",
["94A7"] = "North Pine, British Columbia|North Pine",
["94A8"] = "Alces River",
["94A9"] = "Osborn River",
["94A10"] = "Rose Prairie",
["94A11"] = "Murdale",
["94A12"] = "Deadhorse Creek (British Columbia)|Deadhorse Creek",
["94A13"] = "Aitken Creek",
["94A14"] = "Snyder Creek (British Columbia)|Snyder Creek",
["94A15"] = "Milligan Creek",
["94A16"] = "Doig River",

["94B"] = "Halfway River",
["94B1"] = "Butler Ridge",
["94B2"] = "Jones Peak (British Columbia)|Jones Peak",
["94B3"] = "Mount Brewster (British Columbia)|Mount Brewster",
["94B4"] = "Wicked River",
["94B5"] = "Gauvreau Creek",
["94B6"] = "Emerslund Lakes",
["94B7"] = "Hackney Hills",
["94B8"] = "Kobes Creek",
["94B9"] = "Aikman Creek",
["94B10"] = "Chowade River",
["94B11"] = "Christina Falls",
["94B12"] = "Mount Lady Laurier",
["94B13"] = "Mount Robb",
["94B14"] = "Mount Laurier",
["94B15"] = "Cypress Creek (British Columbia)|Cypress Creek",
["94B16"] = "Blair Creek (British Columbia)|Blair Creek",

["94C"] = "Mesilinka River",
["94C1"] = "Omineca Arm",
["94C2"] = "End Lake",
["94C3"] = "Uslika Lake",
["94C4"] = "Notch Peak (British Columbia)|Notch Peak",
["94C5"] = "Aiken Lake",
["94C6"] = "Blackpine Lake",
["94C7"] = "Lorimer Creek",
["94C8"] = "Lafferty Arm",
["94C9"] = "Davis River",
["94C10"] = "Factor Ross Creek",
["94C11"] = "Ingenika Mine",
["94C12"] = "Orion Creek",
["94C13"] = "Tucha Creek",
["94C14"] = "Ed Bird Creek",
["94C15"] = "Chowika Creek",
["94C16"] = "",

["94D"] = "McConnell Creek",
["94D1"] = "Nanitsch Lake",
["94D2"] = "Salix Creek",
["94D3"] = "Motase Lake",
["94D4"] = "Sicintine River",
["94D5"] = "Slamgeesh River",
["94D6"] = "Birdflat Creek",
["94D7"] = "Asitka River",
["94D8"] = "Carruthers Pass",
["94D9"] = "Johanson Lake",
["94D10"] = "Moosevale Creek",
["94D11"] = "South Pass Peak",
["94D12"] = "Alma Creek",
["94D13"] = "Malloch Creek",
["94D14"] = "Tatlatui Lake",
["94D15"] = "Thorne Lake",
["94D16"] = "Fredrikson Creek",

["94E"] = "Toodoggone River",
["94E1"] = "LaForce Creek",
["94E2"] = "Attycelley Creek",
["94E3"] = "Sturdee River",
["94E4"] = "Stalk Lakes",
["94E5"] = "Laslui Lake",
["94E6"] = "Moosehorn Creek",
["94E7"] = "Mount Katherine (British Columbia)|Mount Katherine",
["94E8"] = "Mount Bower (British Columbia)|Mount Bower",
["94E9"] = "Cutoff Creek",
["94E10"] = "Mount Cushing",
["94E11"] = "Moosehorn Lake",
["94E12"] = "Spruce Hill (British Columbia)|Spruce Hill",
["94E13"] = "",
["94E14"] = "Lunar Creek",
["94E15"] = "Thudaka Peak",
["94E16"] = "Sifton Pass",

["94F"] = "Kwadacha|Ware",
["94F1"] = "Mount Ford (British Columbia)|Mount Ford",
["94F2"] = "",
["94F3"] = "Truncate Creek",
["94F4"] = "Mount Russel",
["94F5"] = "Kwadacha|Ware",
["94F6"] = "Paul River",
["94F7"] = "",
["94F8"] = "Cyclops Peak (British Columbia)|Cyclops Peak",
["94F9"] = "Mount Justin",
["94F10"] = "Ipec Lake",
["94F11"] = "Mount Alcock",
["94F12"] = "Mount Chief Davie",
["94F13"] = "Mount McCook",
["94F14"] = "Haworth Lake",
["94F15"] = "Mount Lloyd George",
["94F16"] = "",

["94G"] = "Trutch, British Columbia|Trutch",
["94G1"] = "Julienne Creek",
["94G2"] = "Pink Mountain, British Columbia|Pink Mountain",
["94G3"] = "Marion Lake (British Columbia)|Marion Lake",
["94G4"] = "Mount McCusker",
["94G5"] = "Redfern Lake",
["94G6"] = "Mount Withrow",
["94G7"] = "Caribou Creek (British Columbia)|Caribou Creek",
["94G8"] = "Medana Creek",
["94G9"] = "Donnie Creek",
["94G10"] = "Trutch, British Columbia|Trutch",
["94G11"] = "Minaker River",
["94G12"] = "Richards Creek",
["94G13"] = "Kluachesi Lake",
["94G14"] = "Bunch Creek (British Columbia)|Bunch Creek",
["94G15"] = "Bougie Creek",
["94G16"] = "Boat Creek",

["94H"] = "Beatton River",
["94H1"] = "Adskwatim Creek",
["94H2"] = "Big Arrow Creek",
["94H3"] = "Nig Creek",
["94H4"] = "Bubbles Creek",
["94H5"] = "La Prise Creek",
["94H6"] = "Birley Creek",
["94H7"] = "Milligan Hills",
["94H8"] = "",
["94H9"] = "Ring Reid Creek",
["94H10"] = "Heck Creek",
["94H11"] = "River Lake (British Columbia)|River Lake",
["94H12"] = "West Conroy Creek",
["94H13"] = "Tommy Lakes",
["94H14"] = "Katah Creek",
["94H15"] = "Helicopter Creek",
["94H16"] = "Etthithun Lake",

["94I"] = "Fontas River",
["94I1"] = "Beaverskin Creek",
["94I2"] = "Bedji Creek",
["94I3"] = "Niteal Creek",
["94I4"] = "Dehacho Creek",
["94I5"] = "Fontas",
["94I6"] = "Elleh Lake",
["94I7"] = "Ekwan Creek",
["94I8"] = "Little Buffalo River (British Columbia)|Little Buffalo River",
["94I9"] = "Timberwolf Creek",
["94I10"] = "Townsoitoi Creek",
["94I11"] = "Kyklo Creek",
["94I12"] = "Nogah Creek",
["94I13"] = "Gunnell Creek",
["94I14"] = "Lichen Creek",
["94I15"] = "Datcin Creek",
["94I16"] = "Shekilie River",

["94J"] = "Fort Nelson, British Columbia|Fort Nelson",
["94J1"] = "Klua Lakes",
["94J2"] = "Prophet River, British Columbia|Prophet River",
["94J3"] = "Tenaka Creek",
["94J4"] = "Gathto Creek",
["94J5"] = "Tuchodi River",
["94J6"] = "Cheves Creek",
["94J7"] = "Big Beaver Creek (British Columbia)|Big Beaver Creek",
["94J8"] = "Klua Creek",
["94J9"] = "Clarke Lake (British Columbia)|Clarke Lake",
["94J10"] = "Jackfish Creek",
["94J11"] = "Akue Creek",
["94J12"] = "Chischa River",
["94J13"] = "Kledo Creek",
["94J14"] = "Raspberry Creek (British Columbia)|Raspberry Creek",
["94J15"] = "Fort Nelson, British Columbia|Fort Nelson",
["94J16"] = "Chuatse Creek",

["94K"] = "Tuchodi Lakes",
["94K1"] = "Mount Sylvia (British Columbia)|Mount Sylvia",
["94K2"] = "Sicily Mountain",
["94K3"] = "Churchill Peak",
["94K4"] = "South Gataga River",
["94K5"] = "",
["94K6"] = "Normandy Mountain",
["94K7"] = "Wokkpash Lake",
["94K8"] = "Chlotapecta Creek",
["94K9"] = "North Tetsa River",
["94K10"] = "Mount St. George",
["94K11"] = "Yedhe Mountain",
["94K12"] = "Yedhe Creek",
["94K13"] = "Muncho Lake",
["94K14"] = "Toad Hot Springs",
["94K15"] = "Stone Mountain (British Columbia)|Stone Mountain",
["94K16"] = "McClennan Creek",

["94L"] = "Kechika River",
["94L1"] = "Braid Creek",
["94L2"] = "Johiah Lake",
["94L3"] = "Mount Irving (British Columbia)|Mount Irving",
["94L4"] = "",
["94L5"] = "Tucho Lake",
["94L6"] = "Denetiah Lake",
["94L7"] = "Paddy Creek",
["94L8"] = "Through Creek",
["94L9"] = "",
["94L10"] = "Gataga Mountain",
["94L11"] = "Denetiah Creek",
["94L12"] = "Sharktooth Mountain",
["94L13"] = "Moodie Lakes",
["94L14"] = "Moodie Creek",
["94L15"] = "",
["94L16"] = "Gundahoo Pass",

["94M"] = "Rabbit River (British Columbia)|Rabbit River",
["94M1"] = "Skeezer Lake",
["94M2"] = "",
["94M3"] = "Scoop Lake (British Columbia)|Scoop Lake",
["94M4"] = "Turnagain River",
["94M5"] = "Aeroplane Lake",
["94M6"] = "Gemini Lakes",
["94M7"] = "Fishing Lake (British Columbia)|Fishing Lake",
["94M8"] = "Vents River",
["94M9"] = "Teeter Creek",
["94M10"] = "Grant Lake (British Columbia)|Grant Lake",
["94M11"] = "Fireside, British Columbia|Fireside",
["94M12"] = "Tatisno Mountain",
["94M13"] = "Egnell Lakes",
["94M14"] = "Hillgren Lakes",
["94M15"] = "Shaw Creek (British Columbia)|Shaw Creek",
["94M16"] = "Smith River (Liard River tributary)|Smith River",

["94N"] = "Toad River",
["94N1"] = "Dunedin River",
["94N2"] = "Scaffold Creek",
["94N3"] = "Eight Mile Creek (British Columbia)|Eight Mile Creek",
["94N4"] = "Trout River (British Columbia)|Trout River",
["94N5"] = "Mount Prudence",
["94N6"] = "Grayling River",
["94N7"] = "Toad River",
["94N8"] = "Nelson Forks",
["94N9"] = "Catkin Creek",
["94N10"] = "Scatter River",
["94N11"] = "Bulwell Creek",
["94N12"] = "Vizer Creek",
["94N13"] = "Thorpe Creek",
["94N14"] = "Beavercrow Mountain",
["94N15"] = "Crow River (British Columbia)|Crow River",
["94N16"] = "Beaver River (Liard River tributary)|Beaver River",

["94O"] = "Maxhamish Lake Provincial Park and Protected Area|Maxhamish Lake",
["94O1"] = "Sahtaneh River",
["94O2"] = "Tsimeh Creek",
["94O3"] = "Stanolind Creek",
["94O4"] = "Etane Creek",
["94O5"] = "Capot-Blanc Creek",
["94O6"] = "Patry Lake",
["94O7"] = "Kiwigana River",
["94O8"] = "Two Island Lake (British Columbia)|Two Island Lake",
["94O9"] = "Trail Lake (British Columbia)|Trail Lake",
["94O10"] = "Tightfit Lake",
["94O11"] = "Tsinhia Lake",
["94O12"] = "La Jolie Butte",
["94O13"] = "Sandy Creek (British Columbia)|Sandy Creek",
["94O14"] = "Maxhamish Lake Provincial Park and Protected Area|Maxhamish Lake",
["94O15"] = "Emile Creek",
["94O16"] = "Stanislas Creek",

["94P"] = "Petitot River",
["94P1"] = "Tooga Creek",
["94P2"] = "Tooga Lake",
["94P3"] = "Kotcho Lake",
["94P4"] = "Courvoisier Creek",
["94P5"] = "Komie Creek",
["94P6"] = "Thetlaandoa Creek",
["94P7"] = "Kwokwullie Lake",
["94P8"] = "Pesh Creek",
["94P9"] = "Thinahtea Lake",
["94P10"] = "Kimea Creek",
["94P11"] = "Etset Lake",
["94P12"] = "Gote Creek",
["94P13"] = "Estsine Lake",
["94P14"] = "Hossitl Creek",
["94P15"] = "Midwinter Lake",
["94P16"] = "June Lake (Peace River Land District)|June Lake",

require ('strict');																-- alarm when global variables etc are used

local nts = require ('Module:Canada NTS');										-- for extents_from_grid(), nts_series_validate, nts_are_validate, nts_sheet_validate
local data = mw.loadData ('Module:Canada NTS/data');							-- load the ~/data module


--[[--------------------------< W I K I D A T A _ L A T _ L O N G _ G E T >------------------------------------

returns latitude and longitude from wikidata for <qid>; nil else

]]

local function wikidata_lat_lon_get (qid)
	if qid then
		local value_t = mw.wikibase.getBestStatements (qid, 'P625')[1];			-- attempt to get P625; nil when article does not have P625
		if value_t then
			value_t = value_t.mainsnak.datavalue.value;							-- point to the value table
			return value_t.latitude, -1.0 * value_t.longitude;					-- return coordinates from value_t; longitude normalized
		end
	end
end


--[[--------------------------< T A B B I N G >----------------------------------------------------------------

return the number of tabs necessary to more-or-less position comments at column 80 (the right-side margin line
in the wiki code editor)

]]

local function tabbing (str)
	return math.ceil ((80 - 4 - str:len()) / 4);								-- the -4 because every line begins with a tab character
end


--[[--------------------------< S O R T >----------------------------------------------------------------------

sort by keys; ascending order numerically by series then by area (alpha sort) then numerically by sheet

]]

local function sort (a, b)
	local a_key_series, a_key_area, a_key_sheet = a:match ('(%d+)(%u)(%d*)');	-- extract series, area, sheet NTS id parts from <a>
	local b_key_series, b_key_area, b_key_sheet = b:match ('(%d+)(%u)(%d*)');	-- extract series, area, sheet NTS id parts from <b>
	
	a_key_series = tonumber (a_key_series);										-- convert numerical parts of key from <a> to numbers for comparison
	a_key_sheet = tonumber (a_key_sheet);										-- nil when <a_key_sheet> is empty string
	a_key_sheet = a_key_sheet and a_key_sheet or 0								-- optional so when omitted set to 0 for comparison
	
	b_key_series = tonumber (b_key_series);										-- convert numerical parts of key from <a> to numbers for comparison
	b_key_sheet = tonumber (b_key_sheet);										-- nil when <b_key_sheet> is empty string
	b_key_sheet = b_key_sheet and b_key_sheet or 0								-- optional so when omitted set to 0 for comparison

	if a_key_series ~= b_key_series then										-- do the comparisons; by series first
		return a_key_series < b_key_series;
	elseif a_key_area ~= b_key_area then										-- then by area
		return a_key_area < b_key_area;
	else
		return a_key_sheet < b_key_sheet;										-- and lastly by sheet
	end
end


--[[--------------------------< L A T _ B O U N D S _ C H E C K >----------------------------------------------

is <lat> same as or north of <south> and is <lat> same as or south of <north>?

returns direction 'north' or 'south' when <lat> out of bounds; nil else

]]

local function lat_bounds_check (lat, north, south)
	if lat < south then
		return 'south';															-- out of bounds south of <south>
	elseif lat > north then
		return 'north';															-- out of bounds north of <north>
	end
end
	

--[[--------------------------< L O N _ B O U N D S _ C H E C K >----------------------------------------------

is <lon> same as or east of <west> and is <lat> same as or west of <east>?

returns direction 'east' or 'west' when <lon> out of bounds; nil else

]]

local function lon_bounds_check (lon, west, east)
	if lon < east then
		return 'east';															-- out of bounds east of <east>
	elseif lon > west then
		return 'west';															-- out of bounds west of <west>
	end
end
	

--[[--------------------------< I S _ I N _ B O U N D S >------------------------------------------------------

get map extents using series, area, sheet.  check to see that lat/lon fall within the map extents.

returns empty string for concatenation when lat/lon fall within map extents; an error message else

only one error mesage is returned; if both <lat> and <lon> are out of bounds, this function returns an error
message for only one of them

because series, area, sheet are taken from Module:Canada NTS/data, they are presumed to be correct

]]

local function is_in_bounds (series, area, sheet, lat, lon)
	local extents_t = nts.extents_from_grid (tonumber(series), area, tonumber(sheet));	-- fetch extents for this nts key

	local lat_fail, lon_fail;													-- flags
	local north, west, south, east;												-- map extents

	if '' == sheet then															-- is this searies, area, sheet an area map?
		north, west, south, east = extents_t.area_north, extents_t.area_west, extents_t.area_south, extents_t.area_east; 
		lat_fail = lat_bounds_check (lat, north, south);
		lon_fail = lon_bounds_check (lon, west, east);
	else																		-- here when sheet map
		north, west, south, east = extents_t.north, extents_t.west, extents_t.south, extents_t.east; 
		lat_fail = lat_bounds_check (lat, north, south);
		lon_fail = lon_bounds_check (lon, west, east);
	end

	if lat_fail or lon_fail then												-- build error message
		if 'north' == lat_fail then
			return string.format ('P625 %s latitude out of bounds: WD lat/lon: <%s>,%s (NW: <%s>,%s / SE: %s,%s)',
				lat_fail, lat, lon, north, west, south, east);
		elseif 'south' == lat_fail then
			return string.format ('P625 %s latitude out of bounds: WD lat/lon: <%s>,%s (NW: %s,%s / SE: <%s>,%s)',
				lat_fail, lat, lon, north, west, south, east);
		elseif 'west' == lon_fail then
			return string.format ('P625 %s longitude out of bounds: WD lat/lon: %s,<%s> (NW: %s,<%s> / SE: %s,%s)',
				lon_fail, lat, lon, north, west, south, east);
		elseif 'east' == lon_fail then
			return string.format ('P625 %s longitude out of bounds: WD lat/lon: %s,<%s> (NW: %s,%s / SE: %s,<%s>)',
				lon_fail, lat, lon, north, west, south, east);
		end
	end
	
	return '';																	-- in bounds so return empty string message for concatenation
end


--[[--------------------------< M A I N >----------------------------------------------------------------------

fetch <map_series> entries from Module:Canada NTS/data.  For each of those, attempt to get qid that matches the
en.wiki article title from wikidata.  Replace article title with the qid, sort and make all pretty like for
manual replacement in ~/data.

to use the results of this function, Module:Canada NTS requires support for qids in lieu of article names which,
as of 2022-04-12, does not yet exist

This function may be called from an invoke or from the debug console

<frame> is a text string when called from debug console:
	=p.main ('92')

	{{#invoke:Sandbox/trappist the monk/nts|main|94}}

]]

local function main (frame)
	local map_series;															-- number of the series that we are operating on; for invokes, this is only number or start of a range
	local map_series_end;														-- for invokes only; end of a range of map series
	local invoked;

	if 'table' == type (frame) then
		map_series = frame.args[1];												-- for an invoke
		if frame.args[2] then
			map_series_end = frame.args[2];
		end
			
		invoked = true;															-- flag used to modify final output rendering
	else
		map_series = frame;														-- when called from the debug console
	end

	local temp_t = {}															-- output goes in this sequence table until rendering
	for k, v in pairs (data) do													-- for each entry in the data mofule
		local series, area, sheet = k:match ('^(%d+)(%u)(%d*)');				-- fetch series, area, sheet from the entry's key
		
		if tonumber(series) >= tonumber(map_series) and tonumber(series) <= tonumber(map_series_end or map_series) then	-- when this map is one of the map series we're looking for
			local map_title_parts = mw.text.split (v, '|');						-- extract an article title
			local qid = mw.wikibase.getEntityIdForTitle (map_title_parts[1]);	-- does this article title have a wikidata entry
			local lat, lon = wikidata_lat_lon_get (qid);						-- attempt to get P625 lat/lon from wikidata's entry for the article title
			local bounds_msg = '';												-- gets an error message if lat/lon from wikidata not within bounds of NTS map

			if qid and lat then													-- when article title has wikidata entry an wikidata has P625 (latitude/longitude)
				bounds_msg = is_in_bounds (series, area, sheet, lat, lon);		-- empty string when in bounds; message else
			elseif qid then														-- lat can be nil when qid points to dab page or article does not have P625
				bounds_msg = string.format ('%s missing P625', qid);			-- no lat/lon so no P625
			end
			
			local str;
			if not qid then														-- when article title doesn't have a wikidata entry
				str = string.format ('\t["%s"] = "%s",', k, v);					-- mimic the original
			elseif not lat then													-- has qid but does not have P625
				str = string.format ('["%s"] = "%s",', k, v);					-- mimic the original + bounds_msg
				str = string.format ('\t%s%s-- %s', str, string.rep ('\t', tabbing (str)), bounds_msg);
			elseif map_title_parts[2] then										-- for piped article links
				str = string.format ('["%s"] = "%s|%s",', k, qid, map_title_parts[2]);
				str = string.format ('\t%s%s-- %s; %s', str, string.rep ('\t', tabbing (str)), map_title_parts[1], bounds_msg);
			else																-- map name same as en.wiki article title
				str = string.format ('["%s"] = "%s",', k, qid, map_title_parts[1]);
				str = string.format ('\t%s%s-- %s; %s', str, string.rep ('\t', tabbing (str)), map_title_parts[1], bounds_msg);
			end

			if invoked then														-- when this function called through an invoke
				str = str:gsub ('\t', '&#9;')									-- replace plain-text tabs with html numeric entities
			end

			table.insert (temp_t, str);											-- save 
		end
	end

	table.sort (temp_t, sort);													-- ascending numerical sort by key (NTS id)
	for i=#temp_t, 1, -1 do														-- working backwards through the table
		if temp_t[i]:match ('%d+%u"%]') then									-- if this key is an area key (no sheet)
			table.insert (temp_t, i, '');										-- insert an empty string to separate area-from-area
		elseif i > 1 and temp_t[i]:match ('%["(%d+)') ~= temp_t[i-1]:match ('%["(%d+)') then	-- because some series list don't begin with area maps (15 for example)
			table.insert (temp_t, i, '');										-- insert an empty string to separate area-from-area
		end
	end

	if invoked then																-- when this function called through an invoke
		return table.concat ({'<pre>', table.concat (temp_t, '<br />'),'</pre>'});	-- concatenate into a big string using <br /> tags and wrapped in <pre>...</pre> tagsand done
	else
		return table.concat (temp_t, '\n');										-- concatenate into a big string using plain-text newlines and done
	end
end


--[[--------------------------< N T S _ K E Y _ V A L I D A T E >----------------------------------------------

debug console function to makes sure that all keys in Module:Canada NTS/data have the correct form

=p.nts_key_validate()

]]

local function nts_key_validate()
	for k, _ in pairs (data) do
		local series, area, sheet = k:match ('^(%d+)(%u)(%d*)$');
		if not series then
			return '<span style="color:#d33">invalid key: ' .. k .. '</span>';
		end
		
		if not nts.nts_series_validate (tonumber(series)) then
			return '<span style="color:#d33">invalid series: ' .. k .. '</span>';
		end
		if not nts.nts_area_validate (tonumber(series), area) then
			return '<span style="color:#d33">invalid area: ' .. k .. '</span>';
		end
		if '' ~= sheet and not nts.nts_sheet_validate (tonumber(sheet)) then
			return '<span style="color:#d33">invalid sheet: ' .. k .. '</span>';
		end
	end
	return 'keys are valid';
end

	
--[[--------------------------< E X P O R T E D   F U N C T I O N S >------------------------------------------
]]

return {
	main = main,
	nts_key_validate = nts_key_validate,
	}