---
-- Commonly used POP3 implementation. 
-- These functions need a <B>stat(pstate)</B> 
-- that checks if it called more than once.
-- </p><p>
-- The callback factory for retr and top:
-- </p><p>
-- A callback factory is a function that generates other functions. both retr
-- and top need a callback. the callback is called when there is some data 
-- to send to the client. this is done with popserver_callback(s,data) 
-- where s is the data and data is the opaque data that is passed to 
-- the retr/top function and is used internally by the popserve callbak. 
-- no need to know what it is, but we have to pass it. 
-- </p><p>
-- The callback function must accept 2 args: the data to send and an optional 
-- error message. if the data is nil it means the err contains the 
-- relative error message. If s is "" it means that the trasmission 
-- ended sucesfully (read: the socket has benn closed correclty). 
-- </p><p>
-- Here in the implementation <tt>a</tt> is an opaque data structure used by the
-- stringhack module. the stringhack module implements some useful string 
-- manipulation tasks. 
-- tophack keeps track of how many lines have been 
-- processed. If more than lines (we talk of lines of mail body) have 
-- been processed the returned string will be trucated to the 
-- correct line number. 
-- dothack simply does a 'sed s/^\.$/../' but is really hard if the data 
-- is not divided in lines as in our case (ip packets are not line oriented),
-- so it is implemented in C for you. check_stop checks if the lines 
-- amount of lines have already been processed.
--


MODULE_VERSION = "0.0.1"
MODULE_NAME = "common"
MODULE_REQUIRE_VERSION = "0.2.0"
MODULE_LICENSE = "GNU/GPL"
MODULE_URL = "http://www.freepops.org/download.php?module=common.lua"
MODULE_HOMEPAGE = "http://www.freepops.org/"

module("common",package.seeall)

--============================================================================--
-- This is part of FreePOPs (http://www.freepops.org) released under GNU/GPL  
--============================================================================--

---
-- Checks if a message number is in range
--
function check_range(pstate,msg)
	local n = get_popstate_nummesg(pstate)
	return msg >= 1 and msg <= n
end

---
-- Fill msg uidl field
function uidl(pstate,msg)
	return stat(pstate)
end

---
-- Fill all messages uidl field
function uidl_all(pstate)
	return stat(pstate)
end

---
-- Fill msg size
function list(pstate,msg)
	return stat(pstate)
end

---
-- Fill all messages size
function list_all(pstate,msg)
	return stat(pstate)
end

---
-- Do nothing
function noop(pstate)
	return POPSERVER_ERR_OK
end

---
-- Unflag each message merked for deletion
function rset(pstate)
	local st = stat(pstate)
	if st ~= POPSERVER_ERR_OK then return st end
	
	for i=1,get_popstate_nummesg(pstate) do
		unset_mailmessage_flag(pstate,i,MAILMESSAGE_DELETE)
	end
	return POPSERVER_ERR_OK
end

---
-- Mark msg for deletion
function dele(pstate,msg)
	local st = stat(pstate)
	if st ~= POPSERVER_ERR_OK then return st end

	if not check_range(pstate,msg) then
		return POPSERVER_ERR_NOMSG
	end
	set_mailmessage_flag(pstate,msg,MAILMESSAGE_DELETE)
	return POPSERVER_ERR_OK
end

---
-- A common implementation of the retr_cb used in the retr() function
-- @param data userdata the data passed to the retr function.
-- @return function the callback to use with b:pipe_uri().
function retr_cb(data)
	local a = stringhack.new()
	
	return function(s,len)
		s = a:dothack(s).."\0"
			
		popserver_callback(s,data)
			
		return len,nil
	end
end

---
-- The callback for top is really similar to the retr, but checks for purging
-- unwanted data and sets globals.lines to -1 if no more lines are needed
-- @param global table you should read common.lua to understand all the fields.
-- @param data userdata the data passed to top().
-- @param truncate bool if we should truncate the commection when done or not.
-- @return function the callback for b:pipe_uri().
--
function top_cb(global,data,truncate)
	local purge = false
	
	return function(s,len)
		if purge == true then
			--print("purging: "..string.len(s))
			return len,nil
		end
			
		s=global.a:tophack(s,global.lines_requested)
		s =  global.a:dothack(s).."\0"
			
		popserver_callback(s,data)
	
		global.bytes = global.bytes + len

		-- check if we need to stop (in top only)
		if global.a:check_stop(global.lines_requested) then
			--print("TOP more than needed")
			purge = true
			global.lines = -1
			if(string.sub(s,-2,-1) ~= "\r\n") then
				popserver_callback("\r\n",data)
			end
			if truncate then
				return 0,nil
			else
				return len,nil
			end
		else
			global.lines = global.lines_requested - 
				global.a:current_lines()
			return len,nil
		end
	end
end

---
-- the TOP function used in raw-message webmails
-- @param b table the browser.
-- @param uri string the uri to fetch.
-- @param key string the key used with session module.
-- @param tot_bytes number the size of the mailmessage.
-- @param data userdata the data passed to top(pstate,msg,lines,data).
-- @param truncate bool if the TOP should be implemented with a Range header
-- 	field or should be implemented dropping the connection.
-- @return number POPSERVER_ERR_*.
--
function top(b,uri,key,tot_bytes,lines,data,truncate)
	-- build the callbacks --
	
	-- this data structure is shared between callbacks
	local global = {
		-- the current amount of lines to go!
		lines = lines, 
		-- the original amount of lines requested
		lines_requested = lines, 
		-- how many bytes we have received
		bytes = 0,
		total_bytes = tot_bytes,
		-- the stringhack (must survive the callback, since the 
		-- callback doesn't know when it must be destroyed)
		a = stringhack.new(),
		-- the first byte
		from = 0,
		-- the last byte
		to = 0,
		-- the minimum amount of bytes we receive 
		-- (compensates the mail header usually)
		base = 2048,
	}
	-- the callback for http stram
	local cb = top_cb(global,data,truncate)
	-- retrive must retrive from-to bytes, stores from and to in globals.
	local retrive_f = function()
		global.to = global.base + global.from + (global.lines + 1) * 100
		global.base = 0
		local extra_header = {
			"Range: bytes="..global.from.."-"..global.to
		}
		local f,err = b:pipe_uri(uri,cb,extra_header)
		global.from = global.to + 1
		--if f == nil --and rc.error == "EOF" 
		--	then
		--	return "",nil
		--end
		return f,err
	end
	-- global.lines = -1 means we are done!
	local check_f = function(_)
		return global.lines < 0 or global.bytes >= global.total_bytes
	end
	-- nothing to do
	local action_f = function(_)
		return true
	end

	-- go! 
	if not support.do_until(retrive_f,check_f,action_f) and 
	   not truncate then
		log.error_print("Top failed\n")
		-- don't remember if this should be done
		--session.remove(key())
		return POPSERVER_ERR_UNKNOWN
	end

	return POPSERVER_ERR_OK
end

---
--This is to create the global argument for top_cb.
--Remember that if size is nil the truncate parameter to top_cb must be true.
--@param lines number the number of line we want.
--@param size number the size in byte of the mail, can be nil.
--@return table the global argument from common.top_cb.
function new_global_for_top(lines,size)
	if size == nil then
		return {lines=math.max(lines-1,0),
			lines_requested=math.max(lines-1,0),
			bytes = 0,
			a = stringhack.new()}
	else
		return {lines=math.max(lines-1,0),
			lines_requested=math.max(lines-1,0),
			bytes = 0,
			from=0,
			to=0,
			total_bytes=size,
			a = stringhack.new()}
	end
end

