local M = { _NAME = "uri.data" }
local Util = require "uri._util"
local URI = require "uri"
Util.subclass_of(M, URI)

-- This implements the 'data' scheme defined in RFC 2397.

local Filter = Util.attempt_require("datafilter")

local function _valid_base64 (data) return data:find("^[0-9a-zA-Z/+]*$") end

local function _split_path (path)
    local _, _, mediatype, data = path:find("^([^,]*),(.*)")
    if not mediatype then return "must have comma in path" end
    local base64 = false
    if mediatype:find(";base64$") then
        base64 = true
        mediatype = mediatype:sub(1, -8)
    end
    if base64 and not _valid_base64(data) then
        return "illegal character in base64 encoding"
    end
    return nil, mediatype, base64, data
end

function M.init (self)
    if M._SUPER.host(self) then
        return nil, "data URIs may not have authority parts"
    end
    local err, mediatype, base64, data = _split_path(M._SUPER.path(self))
    if err then return nil, "invalid data URI (" .. err .. ")" end
    return self
end

function M.data_media_type (self, ...)
    local _, old, base64, data = _split_path(M._SUPER.path(self))

    if select('#', ...) > 0 then
        local new = ... or ""
        new = Util.uri_encode(new, "^A-Za-z0-9%-._~!$&'()*+;=:@/")
        if base64 then new = new .. ";base64" end
        M._SUPER.path(self, new .. "," .. data)
    end

    if old ~= "" then
        if old:find("^;") then old = "text/plain" .. old end
        return Util.uri_decode(old)
    else
        return "text/plain;charset=US-ASCII"    -- default type
    end
end

local function _urienc_len (s)
    local num_unsafe_chars = s:gsub("[A-Za-z0-9%-._~!$&'()*+,;=:@/]", ""):len()
    local num_safe_chars = s:len() - num_unsafe_chars
    return num_safe_chars + num_unsafe_chars * 3
end

local function _base64_len (s)
    local num_blocks = (s:len() + 2) / 3
    num_blocks = num_blocks - num_blocks % 1
    return num_blocks * 4
           + 7      -- because of ";base64" marker
end

local function _do_filter (algorithm, input)
    return Filter[algorithm](input)
end

function M.data_bytes (self, ...)
    local _, mediatype, base64, old = _split_path(M._SUPER.path(self))
    if base64 then
        if not Filter then
            error("'datafilter' Lua module required to decode base64 data", 2)
        end
        old = _do_filter("base64_decode", old)
    else
        old = Util.uri_decode(old)
    end

    if select('#', ...) > 0 then
        local new = ... or ""
        local urienc_len = _urienc_len(new)
        local base64_len = _base64_len(new)
        if base64_len < urienc_len and Filter then
            mediatype = mediatype .. ";base64"
            new = _do_filter("base64_encode", new)
        else
            new = new:gsub("%%", "%%25")
        end
        M._SUPER.path(self, mediatype .. "," .. new)
    end

    return old
end

function M.path (self, ...)
    local old = M._SUPER.path(self)

    if select('#', ...) > 0 then
        local new = ...
        if not new then error("there must be a path in a data URI") end
        local err = _split_path(new)
        if err then error("invalid data URI (" .. err .. ")") end
        M._SUPER.path(self, new)
    end

    return old
end

Util.uri_part_not_allowed(M, "userinfo")
Util.uri_part_not_allowed(M, "host")
Util.uri_part_not_allowed(M, "port")

return M
-- vi:ts=4 sw=4 expandtab
