local gps = require "gps"
local nmap = require "nmap"
local os = require "os"
local shortport = require "shortport"
local stdnse = require "stdnse"

description = [[
Retrieves GPS time, coordinates and speed from the GPSD network daemon.
]]

---
-- @usage
-- nmap -p 2947 <ip> --script gpsd-info
--
-- @output
-- PORT     STATE SERVICE REASON
-- 2947/tcp open  gpsd-ng syn-ack
-- | gpsd-info: 
-- |   Time of fix: Sat Apr 14 15:54:23 2012
-- |   Coordinates: 59.321685,17.886493
-- |_  Speed: - knots
--
-- @args gpsd-info.timeout timespec defining how long to wait for data (default 10s)


author = "Patrik Karlsson"
license = "Same as Nmap--See http://nmap.org/book/man-legal.html"
categories = {"discovery", "safe"}


portrule = shortport.port_or_service(2947, "gpsd-ng", "tcp")

local arg_timeout = stdnse.parse_timespec(stdnse.get_script_args(SCRIPT_NAME .. ".timeout"))
arg_timeout = arg_timeout or 10

local function updateData(gpsinfo, entry)
	for k, v in pairs(gpsinfo) do
		if ( entry[k] and 0 < #tostring(entry[k]) ) then
			gpsinfo[k] = entry[k]
		end
	end
end

local function hasAllData(gpsinfo)
	for k, v in pairs(gpsinfo) do
		if ( k ~= "speed" and v == '-' ) then
			return false
		end
	end
	return true
end

local function fail(err) return ("\n  ERROR: %s"):format(err or "") end

action = function(host, port)
	
	local gpsinfo = {
		longitude = "-",
		latitude = "-",
		speed = "-",
		time  = "-",
		date  = "-",
	}

	local socket = nmap.new_socket()
	socket:set_timeout(1000)
	
	local status = socket:connect(host, port)
	
	if ( not(status) ) then
		return fail("Failed to connect to server")
	end

	-- get the banner
	local status, line = socket:receive_lines(1)
	socket:send('?WATCH={"enable":true,"nmea":true}\r\n')
	
	local start_time = os.time()
	
	repeat
		local entry
		status, line = socket:receive_buf("\r\n", false)
		if ( status ) then
			status, entry = gps.NMEA.parse(line)
			if ( status ) then
				updateData(gpsinfo, entry)
			end
		end
	until( os.time() - start_time > arg_timeout or hasAllData(gpsinfo) )

	socket:send('?WATCH={"enable":false}\r\n')

	if ( not(hasAllData(gpsinfo)) ) then
		return
	end

	local output = {
		("Time of fix: %s"):format(stdnse.format_timestamp(gps.Util.convertTime(gpsinfo.date, gpsinfo.time))),
		("Coordinates: %.4f,%.4f"):format(tonumber(gpsinfo.latitude), tonumber(gpsinfo.longitude)),
		("Speed: %s knots"):format(gpsinfo.speed)
	}	
	return stdnse.format_output(true, output)
end
