-- vim: ft=terra
local m = {
	timepoint = int64;
	scope = lib.enum {
		'public', 'private', 'local';
		'personal', 'direct', 'circle';
	};
	notiftype = lib.enum {
		'mention', 'like', 'rt', 'react'
	};
	relation = lib.enum {
		'follow', 'mute', 'block'
	};
	credset = lib.set {
		'pw', 'otp', 'challenge', 'trust'
	};
	privset = lib.set {
		'post', 'edit', 'acct', 'upload', 'censor', 'admin'
	};
	powerset = lib.set {
		-- user powers -- default on
		'login', 'visible', 'post', 'shout',
		'propagate', 'upload', 'acct', 'edit';

		-- admin powers -- default off
		'purge', 'config', 'censor', 'suspend',
		'cred', 'elevate', 'demote', 'rebrand' -- modify site's brand identity
	}
}

terra m.powerset:affect_users()
	return self.purge() or self.censor() or self.suspend() or
	       self.elevate() or self.demote() or self.rebrand() or
		   self.cred()
end

local str = rawstring
local pstr = lib.mem.ptr(int8)

struct m.source

struct m.rights {
	rank: uint16 -- lower = more powerful except 0 = regular user
	-- creating staff automatically assigns rank immediately below you
	quota: uint32 -- # of allowed tweets per day; 0 = no limit
	
	powers: m.powerset
}

terra m.rights_default()
	var pow: m.powerset pow:fill()
	(pow.purge << false)
	(pow.config << false)
	(pow.censor << false)
	(pow.suspend << false)
	(pow.elevate << false)
	(pow.demote << false)
	(pow.cred << false)
	(pow.rebrand << false)
	return m.rights { rank = 0, quota = 1000, powers = pow; }
end

struct m.actor {
	id: uint64
	nym: str
	handle: str
	origin: uint64
	bio: str
	title: str
	avatar: str
	knownsince: m.timepoint
	rights: m.rights
	key: lib.mem.ptr(uint8)

-- ephemera
	xid: str
	source: &m.source
}

struct m.actor_stats {
	posts: intptr
	follows: intptr
	followers: intptr
	mutuals: intptr
}

struct m.range {
	mode: uint8 -- 0 == I->I, 1 == T->I, 2 == I->T, 3 == T->T
	union {
		from_time: m.timepoint
		from_idx: uint64
	}
	union {
		to_time: m.timepoint
		to_idx: uint64
	}
}

terra m.range:matrix()
	if self.mode == 0 then
		return self.from_time,self.to_time,0,0
	elseif self.mode == 1 then
		return self.from_time,0,self.to_idx,0
	elseif self.mode == 2 then
		return 0,self.to_time,0,self.from_idx
	elseif self.mode == 3 then
		return 0,0,self.to_idx,self.from_idx
	else lib.bail('invalid mode on timeline range!') end
end

struct m.post {
	id: uint64
	author: uint64
	subject: str
	body: str
	acl: str
	posted: m.timepoint
	discovered: m.timepoint
	mentions: lib.mem.ptr(uint64)
	circles: lib.mem.ptr(uint64) --only meaningful if scope is set to circle
	convoheaduri: str
	parent: uint64
-- ephemera
	localpost: bool
	source: &m.source
}

local cnf = terralib.memoize(function(ty,rty)
	rty = rty or ty
	return struct {
		enum: {&opaque, uint64, rawstring} -> intptr
		get: {&opaque, uint64, rawstring} -> rty
		set: {&opaque, uint64, rawstring, ty} -> {}
		reset: {&opaque, uint64, rawstring} -> {}
	}
end)

struct m.notif {
	kind: m.notiftype.t
	when: uint64
	union {
		post: uint64
		reaction: int8[8]
	}
}

struct m.inet {
	pv: uint8 -- 0 = null, 4 = ipv4, 6 = ipv6
	union {
		v4: uint8[4]
		v6: uint8[16]
	}
	union {
		fixbits: uint8 -- for cidr
		port: uint16 -- for origin
	}
}

terra m.inet:cidr_str()
	if self.pv == 4 then
		var maxsz = 3*4 + 3 + 1
	elseif self.pv == 6 then
		var bits = 128
		var bytes = bits / 8
		var hexchs = bytes * 2
		var segs = hexchs / 4
		var seps = segs - 1
		var maxsz = hexchs + seps + 1
	else return nil end
end

struct m.auth {
	aid: uint64
	uid: uint64
	aname: str
	netmask: m.inet
	privs: m.privset
	blacklist: bool
}

-- backends only handle content on the local server
struct m.backend { id: rawstring
	open: &m.source -> &opaque
	close: &m.source -> {}

	conf_get: {&m.source, rawstring} -> lib.mem.ptr(int8)
	conf_set: {&m.source, rawstring, rawstring} -> {}
	conf_reset: {&m.source, rawstring} -> {}

	actor_save: {&m.source, &m.actor} -> bool
	actor_create: {&m.source, &m.actor} -> uint64
	actor_fetch_xid: {&m.source, lib.mem.ptr(int8)} -> lib.mem.ptr(m.actor)
	actor_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.actor)
	actor_notif_fetch_uid: {&m.source, uint64} -> lib.mem.ptr(m.notif)
	actor_enum: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_enum_local: {&m.source} -> lib.mem.ptr(&m.actor)
	actor_stats: {&m.source, uint64} -> m.actor_stats

	actor_auth_how: {&m.source, m.inet, rawstring} -> {m.credset, bool}
		-- returns a set of auth method categories that are available for a
		-- given user from a certain origin
			-- origin: inet
			-- username: rawstring
	actor_auth_otp: {&m.source, m.inet, rawstring, rawstring}
			-> {uint64, uint64, pstr}
	actor_auth_pw: {&m.source, m.inet, lib.mem.ptr(int8), lib.mem.ptr(int8) }
			-> {uint64, uint64, pstr}
		-- handles password-based logins against hashed passwords
			-- origin: inet
			-- handle: rawstring
			-- token:  rawstring
	actor_auth_tls:    {&m.source, m.inet, rawstring}
			-> {uint64, uint64, pstr}
		-- handles implicit authentication performed as part of an TLS connection
			-- origin: inet
			-- fingerprint: rawstring
	actor_auth_api:    {&m.source, m.inet, rawstring, rawstring} -> uint64
			-> {uint64, uint64, pstr}
		-- handles API authentication
			-- origin: inet
			-- handle: rawstring
			-- key:    rawstring (X-API-Key)
	actor_auth_record_fetch: {&m.source, uint64} -> lib.mem.ptr(m.auth)
	actor_powers_fetch: {&m.source, uint64} -> m.powerset
	actor_session_fetch: {&m.source, uint64, m.inet} -> {lib.stat(m.auth), lib.mem.ptr(m.actor)}
		-- retrieves an auth record + actor combo suitable by AID suitable
		-- for determining session validity & caps
			-- aid:    uint64
			-- origin: inet
	actor_auth_register_uid: {&m.source, uint64, uint64} -> {}
		-- notifies the backend module of the UID that has been assigned for
		-- an authentication ID
			-- aid: uint64
			-- uid: uint64

	actor_conf_str: cnf(rawstring, lib.mem.ptr(int8))
	actor_conf_int: cnf(intptr, lib.stat(intptr))

	post_save: {&m.source, &m.post} -> {}
	post_create: {&m.source, &m.post} -> uint64
	post_enum_author_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
	convo_fetch_xid: {&m.source,rawstring} -> lib.mem.ptr(m.post)
	convo_fetch_uid: {&m.source,uint64} -> lib.mem.ptr(m.post)

	timeline_actor_fetch_uid: {&m.source, uint64, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
	timeline_instance_fetch: {&m.source, m.range} -> lib.mem.ptr(lib.mem.ptr(m.post))
}

struct m.source {
	backend: &m.backend
	id: lib.mem.ptr(int8)
	handle: &opaque
	string: lib.mem.ptr(int8)
}
terra m.source:free()
	self.id:free()
	self.string:free()
end
m.source.metamethods.__methodmissing = macro(function(meth, obj, ...)
	local q = {...}
	-- syntax sugar to forward unrecognized calls onto the backend
	return quote var r = obj.backend.[meth](&obj, [q]) in r end
end)

return m
