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

-- This implements RFC 2141, and attempts to change the class of the URI object
-- to one of its subclasses for further validation and normalization of the
-- namespace-specific string.

-- Check NID syntax matches RFC 2141 section 2.1.
local function _valid_nid (nid)
    if nid == "" then return nil, "missing completely" end
    if nid:len() > 32 then return nil, "too long" end
    if not nid:find("^[A-Za-z0-9][-A-Za-z0-9]*$") then
        return nil, "contains illegal character"
    end
    if nid:lower() == "urn" then return nil, "'urn' is reserved" end
    return true
end

-- Check NSS syntax matches RFC 2141 section 2.2.
local function _valid_nss (nss)
    if nss == "" then return nil, "can't be empty" end
    if nss:find("[^A-Za-z0-9()+,%-.:=@;$_!*'/%%]") then
        return nil, "contains illegal character"
    end
    return true
end

local function _validate_and_normalize_path (path)
    local _, _, nid, nss = path:find("^([^:]+):(.*)$")
    if not nid then return nil, "illegal path syntax for URN" end

    local ok, msg = _valid_nid(nid)
    if not ok then
        return nil, "invalid namespace identifier (" .. msg .. ")"
    end
    ok, msg = _valid_nss(nss)
    if not ok then
        return nil, "invalid namespace specific string (" .. msg .. ")"
    end

    return nid:lower() .. ":" .. nss
end

-- TODO - this should check that percent-encoded bytes are valid UTF-8
function M.init (self)
    if M._SUPER.query(self) then
        return nil, "URNs may not have query parts"
    end
    if M._SUPER.host(self) then
        return nil, "URNs may not have authority parts"
    end

    local path, msg = _validate_and_normalize_path(self:path())
    if not path then return nil, msg end
    M._SUPER.path(self, path)

    local nid_class
        = Util.attempt_require("uri.urn." .. self:nid():gsub("%-", "_"))
    if nid_class then
        setmetatable(self, nid_class)
        if self.init ~= M.init then return self:init() end
    end

    return self
end

function M.nid (self, new)
    local _, _, old = self:path():find("^([^:]+)")

    if new then
        new = new:lower()
        if new ~= old then
            local ok, msg = _valid_nid(new)
            if not ok then
                error("invalid namespace identifier (" .. msg .. ")", 2)
            end
        end
        Util.do_class_changing_change(self, M, "NID", new, function (uri, new)
            M._SUPER.path(uri, new .. ":" .. uri:nss())
        end)
    end

    return old
end

function M.nss (self, new)
    local _, _, old = self:path():find(":(.*)")

    if new and new ~= old then
        local ok, msg = _valid_nss(new)
        if not ok then
            error("invalid namespace specific string (" .. msg .. ")", 2)
        end
        M._SUPER.path(self, self:nid() .. ":" .. new)
    end

    return old
end

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

    if new and new ~= old then
        local path, msg = _validate_and_normalize_path(new)
        if not path then
            error("invalid path for URN '" .. new .. "' (" ..msg .. ")", 2)
        end
        local _, _, newnid, newnss = path:find("^([^:]+):(.*)")
        if not newnid then error("bad path for URN, no NID part found", 2) end
        local ok, msg = _valid_nid(newnid)
        if not ok then
            error("invalid namespace identifier (" .. msg .. ")", 2)
        end
        if newnid:lower() == self:nid() then
            self:nss(newnss)
        else
            Util.do_class_changing_change(self, M, "path", path,
                function (uri, new) M._SUPER.path(uri, new) end)
        end
    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")
Util.uri_part_not_allowed(M, "query")

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