-------------------------------------------------
--
-- class_MultipartFormData.lua
-- Author: Brill Pappin, Sixgreen Labs Inc.
--
-- Generates multipart form-data for http POST calles that require it.
--
-- Caution: 
-- In Corona SDK you have to set a string as the body, which means that the 
-- entire POST needs to be encoded as a string, including any files you attach.
-- Needless to say, if the file you are sending is large, it''s going to use 
-- up all your available memory!
--
-- Example:
--[[
local MultipartFormData = require("class_MultipartFormData")
 
local multipart = MultipartFormData.new()
multipart:addHeader("Customer-Header", "Custom Header Value")
multipart:addField("myFieldName","myFieldValue")
multipart:addField("banana","yellow")
multipart:addFile("myfile", system.pathForFile( "myfile.jpg", system.DocumentsDirectory ), "image/jpeg", "myfile.jpg")
 
local params = {}
params.body = multipart:getBody() -- Must call getBody() first!
params.headers = multipart:getHeaders() -- Headers not valid until getBody() is called.
 
local function networkListener( event )
        if ( event.isError ) then
                print( "Network error!")
        else
                print ( "RESPONSE: " .. event.response )
        end
end
 
network.request( "http://www.example.com", "POST", networkListener, params)
 
]]

-------------------------------------------------
local crypto = require("crypto")
local ltn12 = require("ltn12")
local mime = require("mime")
 
MultipartFormData = {}
local MultipartFormData_mt = { __index = MultipartFormData }
 
function MultipartFormData.new()  -- The constructor
        local newBoundary = "MPFD-"..crypto.evp.digest( "sha1", "MultipartFormData"..tostring(object)..tostring(os.time())..tostring(os.clock()), false )
        local object = { 
                isClass = true,
                boundary = newBoundary,
                headers = {},
                elements = {},
        }
  
        object.headers["MIME-Version"] = "1.0" 
  
  return setmetatable( object, MultipartFormData_mt )
end
 
function MultipartFormData:getBody()
        local src = {}
        
        -- always need two CRLF's as the beginning
        table.insert(src, ltn12.source.chain(ltn12.source.string("\n\n"), mime.normalize()))
        
        for i = 1, #self.elements do
                local el = self.elements[i]
                if el then
                        if el.intent == "field" then
                                local elData = {
                                        "--"..self.boundary.."\n",
                                        "content-disposition: form-data; name=\"",
                                        el.name,
                                        "\"\n\n",
                                        el.value,
                                        "\n"
                                }
                                
                                local elBody = table.concat(elData)
                                table.insert(src, ltn12.source.chain(ltn12.source.string(elBody), mime.normalize()))
                        elseif el.intent == "file" then
                                local elData = {
                                        "--"..self.boundary.."\n",
                                        "content-disposition: form-data; name=\"",
                                        el.name,
                                        "\"; filename=\"",
                                        el.filename,
                                        "\"\n",
                                        "Content-Type: ",
                                        el.mimetype,
                                        "\n",
                                        "Content-Transfer-Encoding: ",
                                        el.encoding,
                                        "\n\n",
                                }
                                local elHeader = table.concat(elData)
                                
                                local elFile = io.open( el.path, "rb" )
                                assert(elFile)
                                local fileSource = ltn12.source.cat(
                                                        ltn12.source.chain(ltn12.source.string(elHeader), mime.normalize()),
                                                        ltn12.source.chain(
                                                                        ltn12.source.file(elFile), 
                                                                        ltn12.filter.chain(
                                                                                mime.encode(el.encoding), 
                                                                                mime.wrap()
                                                                        )
                                                                ),
                                                        ltn12.source.chain(ltn12.source.string("\n"), mime.normalize())
                                                )
                                
                                table.insert(src, fileSource)
                        end
                end
        end
        
        -- always need to end the body
        table.insert(src, ltn12.source.chain(ltn12.source.string("\n--"..self.boundary.."--\n"), mime.normalize()))
        
        local source = ltn12.source.empty()
        for i = 1, #src do
                source = ltn12.source.cat(source, src[i])
        end
        
        local sink, data = ltn12.sink.table()
        ltn12.pump.all(source,sink)     
        local body = table.concat(data)
        
        -- update the headers we now know how to add based on the multipart data we just generated.
        self.headers["Content-Type"] = "multipart/form-data; boundary="..self.boundary
        self.headers["Content-Length"] = string.len(body) -- must be total length of body
        
        return body
end
 
function MultipartFormData:getHeaders()
        assert(self.headers["Content-Type"])
        assert(self.headers["Content-Length"])
        return self.headers
end
 
function MultipartFormData:addHeader(name, value)
        self.headers[name] = value
end
 
function MultipartFormData:setBoundry(string)
        self.boundary = string
end
 
function MultipartFormData:addField(name, value)
        self:add("field", name, value)
end
 
function MultipartFormData:addFile(name, path, mimeType, remoteFileName)
        -- For Corona, we can really only use base64 as a simple binary 
        -- won't work with their network.request method.
        local element = {intent="file", name=name, path=path, 
                mimetype = mimeType, filename = remoteFileName, encoding = "base64"}
        self:addElement(element)
end
 
function MultipartFormData:add(intent, name, value)
        local element = {intent=intent, name=name, value=value}
        self:addElement(element)
end
 
function MultipartFormData:addElement(element)
        table.insert(self.elements, element)
end
 
function MultipartFormData:toString()
        return "MultipartFormData [elementCount:"..tostring(#self.elements)..", headerCount:"..tostring(#self.headers).."]" 
end
 
return MultipartFormData