Documentation for this module may be created at Module:Sandbox/Wnt/FindFeatures/displayDatabase/doc

 -- This module finds features with coordinates in a certain area on a globe.
 -- current focus is Mars.  Earth, celestial, etc. would be nice also.
 -- all three dimensionality is hereby banned from the procedure on account of requiring far too much intelligence to get working.

local getArgs = require('Module:Arguments').getArgs
local p = {}
debuglog=""

function tidyNum(text)
	text = mw.ustring.gsub(text, " ", "")
	text = mw.ustring.gsub(text, ",", ".")
	return tonumber(text)
end

function parseValue(text)
	-- extract 3 or 2 or 1 values from the string.  Can contain . or , as a decimal, no spaces allowed.
    local d, m, s = mw.ustring.match(text,"(%-?%d+[%.,]?%d*)[%a%c%s%z!@#$%%^&%*%(%)%=|\~']+(%-?%d+[%.,]?%d*)[%a%c%s%z!@#$%%^&%*%(%)%=|\~']+(%-?%d+[%.,]?%d*)")
    if not d then d, m = mw.ustring.match(text,"(%-?%d+[%.,]?%d*)[%a%c%s%z!@#$%%^&%*%(%)%=|\~']+(%-?%d+[%.,]?%d*)") end
    if not d then d = mw.ustring.match(text,"(%-?%d+[%.,]?%d*)") end
    if d then
    	d = tidyNum(d or "0") + tidyNum(m or "0")/60 + tidyNum(s or "0")/3600
    end
    debuglog = debuglog .. tostring(d)
    return d
end

function parseDirection(text)
	local direction = mw.ustring.match(text,"%A([NSEWnsew])%A") or mw.ustring.match(text,"^([NSEWnsew])%A") or mw.ustring.match(text,"%A([NSEWnsew])$")
    if (not direction) then
    	direction = mw.ustring.match(text,"([Nn])[Oo][Rr][Tt][Hh]") or mw.ustring.match(text,"([Ss])[Oo][Uu][Tt][Hh]") or mw.ustring.match(text,"([Ee])[Aa][Ss][Tt]") or mw.ustring.match(text,"([Ww])[Ee][Ss][Tt]")
    end
    if direction then direction = mw.ustring.upper(direction) end
    return direction
end

function parseCoord(text)
	local text = mw.ustring.upper(text) -- we're only getting direction letters and numbers here
	local coord = {}
	local ok
	-- maybe it's a Coord call like "{{Coord|37.3|N|259.0|E|globe:Mars_type:mountain}}" - then only search the template
    text = mw.ustring.match(text,"{{COORD(.-)}}") or text
	-- maybe it's a simple coordinate like 37N,33E?
	-- note: currently does NOT hunt for deg, min, sec variations.  ASSuMEs that order.
	-- In this case, parsing what to do based on three numbers starts to fall apart (what if there are five?)
	-- Instead, look for the direction markers first, then split into two bound parsing problems
    local first, second = mw.ustring.match(text,"^(.-%A)[NSEW](%A.-)$")
    if first and second and mw.ustring.match(first,"%d") then
    	coord[1] = parseValue(first)
    	second = mw.ustring.match(second, "^(.-%A)[NSEW]%A.-$") or mw.ustring.match(second, "^(.-%A)[NSEW]$") or second
    	coord[2] = parseValue(second)
    	if (coord[1] and coord[2]) then ok = true end
    end
    if (ok) then
         -- at this point the amounts of coord[1] (lat) and coord[2] (lon) are set, but what directions?
        local firstdir = parseDirection(text)
        local seconddir = firstdir
        if firstdir then
            frag = text
            repeat -- I just keep the first letter of the direction, not the context, so need to run forward to it
            	frag = mw.ustring.match(frag, firstdir .. "(.*)$")
            	seconddir = parseDirection(frag)
            until seconddir ~= firstdir
        end
    else
    	-- last ditch effort: take the first two numbers in the section, WHATEVER they are.  Can be signed.  No directions!
    	coord[1], coord[2] = mw.ustring.match(text,"(%-?%d+[%.,]?%d*)[%a%c%s%z!@#$%%^&%*%(%)%=|'{}:;<>~`]+(%-?%d+[%.,]?%d*)")
    	debuglog = debuglog .. "L" .. tostring(coord[1]) .. "D" .. tostring(coord[2])
    	if not (coord[1] and coord[2]) then return nil end
    	coord[1] = tidyNum(coord[1])
    	coord[2] = tidyNum(coord[2])
    	debuglog = debuglog .. ">"
    end
    -- invert signs for west, south positions
    if (firstdir == "W" or firstdir == "S") then
    	coord[1] = -1 * coord[1]
    end
    if (seconddir == "W" or seconddir == "S") then
    	coord[2] = -1 * coord[2]
    end
    -- if first is E/W, put it second
    if (firstdir == "W" or firstdir == "E") then
    	coord[1], coord[2] = coord[2], coord[1]
    end
    -- default without directions specified: first = latitude, no sign reversal
    if (not firstdir) then
    	firstdir = "N"
    end
    if (not seconddir) then
    	seconddir = "E"
    end
    debuglog = debuglog .. tostring(coord[1]) .. tostring(coord[2])
	if mw.ustring.match(seconddir,"[NS]") or mw.ustring.match(firstdir,"[EW]") then
	    return nil, "two of one direction in coordinate"
	end
	while coord[2] > 180 do
            coord[2] = coord[2] - 360
        end
        while coord[2] < -180 do
            coord[2] = coord[2] + 360
        end
	-- at this point firstdir and seconddir no longer mean anything - direction is in the + or - and first or second position
	return coord, warning
end

function getData(pagetitle, database, pRadius, eRadius)
	local pagecontent
	if pagetitle then
		pagecontent = pagetitle:getContent()
	end
	if not pagecontent then return database end
	pagecontent = mw.ustring.gsub(pagecontent, "^.-{|", "{|") -- remove all before first table
	if not pagecontent then return database end
	pagecontent = mw.ustring.gsub(pagecontent, "|}.-{|", "") or pagecontent -- remove all between tables
	pagecontent = mw.ustring.gsub(pagecontent, "|}.-$", "|}") or pagecontent -- remove all after table
	local pagerecord = mw.text.split(pagecontent,"|%-") -- separate all table rows into records
	for i = 1, #pagerecord do
		recordname = mw.ustring.match(pagerecord[i],"%[%[(.-)%]%]")
		recordcoord = parseCoord(pagerecord[i])
		if recordname and recordcoord then table.insert(database,{recordname, recordcoord}) end
	end
	return database
end

function getDatabase(pages, pRadius, eRadius)
	local database = {}
	local page = mw.text.split(pages,"|")
	for i = 1, #page do
		local pagename = mw.text.trim(page[i] or "")
		local pagetitle = mw.title.new(pagename)
		database = getData(pagetitle,database, pRadius, eRadius) -- (so far as I know this is a self assignment, actually)
	end
	return database
end

function display(dataitem)
	local recordname, recordcoord = dataitem[1], dataitem[2]
	return '"'..recordname..'", {' .. recordcoord[1] .. ", " .. recordcoord[2] .. "}"
end

function displayDatabase(database)
	local outarray = {}
	local outprefix = "<nowiki><pre>\nreturn {{"
	local delimiter = "},\n{"
	local outsuffix = "}}\n</pre></nowiki>"
	for i = 1, #database do
		table.insert(outarray,display(database[i]))
	end
	return (outprefix .. table.concat(outarray, delimiter) .. outsuffix)
end

function p._main(frame, pRadius, eRadius, defaultdata)
	 -- for now this is not really intended to be called except from other functions here - use planet name to call
     -- handle args in this module
     -- other parameters are REQUIRED.
	local args = getArgs(frame)
	data = args.data or defaultdata
	database = getDatabase(data, pRadius, eRadius)
    return displayDatabase(database) .. debuglog
end

function p.mars(frame)
     -- module name defines Mars - now set the radius accordingly
	local pRadius = 3376.2 -- km (polar)
	local eRadius = 3396.2 -- km (equatorial)
    local defaultdata = 'List of mountains on Mars|List of craters on Mars: A-G|List of craters on Mars: H-N|List of craters on Mars: O-Z|List of catenae on Mars'
    return p._main(frame, pRadius, eRadius, defaultdata)
end

function p.venus(frame)
	local pRadius = 6051.8 -- km
	local eRadius = 6051.8 -- km (doh, it doesn't spin much!)
	local defaultdata = 'List of craters on Venus|List of montes on Venus|List of coronae on Venus'
	return p._main(frame, pRadius, eRadius, defaultdata)
end

function p.moon(frame)
	-- NOTE: our articles listing craters on the Moon don't include coordinates!
	-- Also there are so many it might run out the clock on the module, we'd have to see
	local pRadius = 1735.97 -- km
	local eRadius = 1738.14 -- km
	local defaultdata = 'List of lunar features' -- (duplicates:) 'List of mountains on the Moon|List of valleys on the Moon|List of maria on the Moon'
	return p._main(frame, pRadius, eRadius, defaultdata)
end

function p.mercury(frame)
	local pRadius = 2439.7 -- km
	local eRadius = 2439.7 -- km
	local defaultdata = 'List of geological features on Mercury|List of craters on Mercury'
	-- no coord templates in these, watch for bad coordinates
	return p._main(frame, pRadius, eRadius, defaultdata)
end

function p.earthCraters(frame)
	-- a general list of all Earth features would be beyond a simple Lua module!
	local pRadius = 6356.8 -- km
	local eRadius = 6378.1 -- km
	local defaultdata = 'List of impact craters in Africa|List of impact craters in Antarctica|List of impact craters in Asia|List of impact craters in Australia|List of impact craters in Europe|List of impact craters in North America|List of impact craters in South America'
	return p._main(frame, pRadius, eRadius, defaultdata)
end

return p