-----------------------------------------------------------------------------
-- Little program to download DICT word definitions
-- LuaSocket sample files
-- Author: Diego Nehab
-- RCS ID: $Id: dict.lua,v 1.13 2004/06/17 23:19:38 diego Exp $
-----------------------------------------------------------------------------

-----------------------------------------------------------------------------
-- Load required modules
-----------------------------------------------------------------------------
local socket = require("socket")
local url = require("url")
local tp = require("tp")

-----------------------------------------------------------------------------
-- Globals 
-----------------------------------------------------------------------------
HOST = "dict.org"
PORT = 2628
TIMEOUT = 10

-----------------------------------------------------------------------------
-- Low-level dict API
-----------------------------------------------------------------------------
local metat = { __index = {} }

function open(host, port)
    local tp = socket.try(tp.connect(host or HOST, port or PORT, TIMEOUT))
    return setmetatable({tp = tp}, metat)
end

function metat.__index:greet()
    return socket.try(self.tp:check(220))
end

function metat.__index:check(ok)
    local code, status = socket.try(self.tp:check(ok))
    return code, tonumber(socket.skip(2, string.find(status, "^%d%d%d (%d*)")))
end

function metat.__index:getdef()
	local line = socket.try(self.tp:receive())
	local def = {}
	while line ~= "." do
		table.insert(def, line)
		line = socket.try(self.tp:receive())
	end
	return table.concat(def, "\n")
end

function metat.__index:define(database, word)
    database = database or "!"
  	socket.try(self.tp:command("DEFINE",  database .. " " .. word))
    local code, count = self:check(150)
	local defs = {}
	for i = 1, count do
  		self:check(151)
		table.insert(defs, self:getdef())
	end
  	self:check(250)
    return defs
end

function metat.__index:match(database, strat, word)
    database = database or "!"
    strat = strat or "."
  	socket.try(self.tp:command("MATCH",  database .." ".. strat .." ".. word))
    self:check(152)
	local mat = {}
	local line = socket.try(self.tp:receive())
    while line ~= '.' do
        database, word = socket.skip(2, string.find(line, "(%S+) (.*)"))
        if not mat[database] then mat[database] = {} end
        table.insert(mat[database], word)
	    line = socket.try(self.tp:receive())
	end
  	self:check(250)
    return mat
end

function metat.__index:quit()
	self.tp:command("QUIT")
	return self:check(221)
end

function metat.__index:close()
    return self.tp:close()
end

-----------------------------------------------------------------------------
-- High-level dict API
-----------------------------------------------------------------------------
local default = {
    scheme = "dict",
    host = "dict.org"
}

local function there(f)
    if f == "" then return nil 
    else return f end
end

local function parse(u)
    local t = socket.try(url.parse(u, default))
    socket.try(t.scheme == "dict", "invalid scheme '" .. t.scheme .. "'")
    socket.try(t.path, "invalid path in url")
    local cmd, arg = socket.skip(2, string.find(t.path, "^/(.)(.*)$"))
    socket.try(cmd == "d" or cmd == "m", "<command> should be 'm' or 'd'")
    socket.try(arg and arg ~= "", "need at least <word> in URL")
    t.command, t.argument = cmd, arg
    arg = string.gsub(arg, "^:([^:]+)", function(f) t.word = f end)
    socket.try(t.word, "need at least <word> in URL")
    arg = string.gsub(arg, "^:([^:]*)", function(f) t.database = there(f) end)
    if cmd == "m" then 
        arg = string.gsub(arg, "^:([^:]*)", function(f) t.strat = there(f) end)
    end
    string.gsub(arg, ":([^:]*)$", function(f) t.n = tonumber(f) end)
    return t
end

local function tget(gett)
    local con = open(gett.host, gett.port)
    con:greet()
    if gett.command == "d" then
        local def = con:define(gett.database, gett.word)
        con:quit()
        con:close()
        if gett.n then return def[gett.n] 
        else return def end
    elseif gett.command == "m" then
        local mat = con:match(gett.database, gett.strat, gett.word)
        con:quit()
        con:close()
        return mat
    else return nil, "invalid command" end
end

local function sget(u)
    local gett = parse(u)
    return tget(gett)
end

get = socket.protect(function(gett)
    if type(gett) == "string" then return sget(gett)
    else return tget(gett) end
end)
