--[=[
@c GuildChannel x Channel
@t abc
@d Defines the base methods and properties for all Discord guild channels.
]=]

local json = require('json')
local enums = require('enums')
local class = require('class')
local Channel = require('containers/abstract/Channel')
local PermissionOverwrite = require('containers/PermissionOverwrite')
local Invite = require('containers/Invite')
local Cache = require('iterables/Cache')
local Resolver = require('client/Resolver')

local isInstance = class.isInstance
local classes = class.classes
local channelType = assert(enums.channelType)

local insert, sort = table.insert, table.sort
local min, max, floor = math.min, math.max, math.floor
local huge = math.huge

local GuildChannel, get = class('GuildChannel', Channel)

function GuildChannel:__init(data, parent)
	Channel.__init(self, data, parent)
	self.client._channel_map[self._id] = parent
	self._permission_overwrites = Cache({}, PermissionOverwrite, self)
	return self:_loadMore(data)
end

function GuildChannel:_load(data)
	Channel._load(self, data)
	return self:_loadMore(data)
end

function GuildChannel:_loadMore(data)
	return self._permission_overwrites:_load(data.permission_overwrites, true)
end

--[=[
@m setName
@t http
@p name string
@r boolean
@d Sets the channel's name. This must be between 2 and 100 characters in length.
]=]
function GuildChannel:setName(name)
	return self:_modify({name = name or json.null})
end

--[=[
@m setCategory
@t http
@p id Channel-ID-Resolvable
@r boolean
@d Sets the channel's parent category.
]=]
function GuildChannel:setCategory(id)
	id = Resolver.channelId(id)
	return self:_modify({parent_id = id or json.null})
end

local function sorter(a, b)
	if a.position == b.position then
		return tonumber(a.id) < tonumber(b.id)
	else
		return a.position < b.position
	end
end

local function getSortedChannels(self)

	local channels
	local t = self._type
	if t == channelType.text or t == channelType.news then
		channels = self._parent._text_channels
	elseif t == channelType.voice then
		channels = self._parent._voice_channels
	elseif t == channelType.category then
		channels = self._parent._categories
	end

	local ret = {}
	for channel in channels:iter() do
		insert(ret, {id = channel._id, position = channel._position})
	end
	sort(ret, sorter)

	return ret

end

local function setSortedChannels(self, channels)
	local data, err = self.client._api:modifyGuildChannelPositions(self._parent._id, channels)
	if data then
		return true
	else
		return false, err
	end
end

--[=[
@m moveUp
@t http
@p n number
@r boolean
@d Moves a channel up its list. The parameter `n` indicates how many spaces the
channel should be moved, clamped to the highest position, with a default of 1 if
it is omitted. This will also normalize the positions of all channels.
]=]
function GuildChannel:moveUp(n)

	n = tonumber(n) or 1
	if n < 0 then
		return self:moveDown(-n)
	end

	local channels = getSortedChannels(self)

	local new = huge
	for i = #channels - 1, 0, -1 do
		local v = channels[i + 1]
		if v.id == self._id then
			new = max(0, i - floor(n))
			v.position = new
		elseif i >= new then
			v.position = i + 1
		else
			v.position = i
		end
	end

	return setSortedChannels(self, channels)

end

--[=[
@m moveDown
@t http
@p n number
@r boolean
@d Moves a channel down its list. The parameter `n` indicates how many spaces the
channel should be moved, clamped to the lowest position, with a default of 1 if
it is omitted. This will also normalize the positions of all channels.
]=]
function GuildChannel:moveDown(n)

	n = tonumber(n) or 1
	if n < 0 then
		return self:moveUp(-n)
	end

	local channels = getSortedChannels(self)

	local new = -huge
	for i = 0, #channels - 1 do
		local v = channels[i + 1]
		if v.id == self._id then
			new = min(i + floor(n), #channels - 1)
			v.position = new
		elseif i <= new then
			v.position = i - 1
		else
			v.position = i
		end
	end

	return setSortedChannels(self, channels)

end

--[=[
@m createInvite
@t http
@op payload table
@r Invite
@d Creates an invite to the channel. Optional payload fields are: max_age: number
time in seconds until expiration, default = 86400 (24 hours), max_uses: number
total number of uses allowed, default = 0 (unlimited), temporary: boolean whether
the invite grants temporary membership, default = false, unique: boolean whether
a unique code should be guaranteed, default = false
]=]
function GuildChannel:createInvite(payload)
	local data, err = self.client._api:createChannelInvite(self._id, payload)
	if data then
		return Invite(data, self.client)
	else
		return nil, err
	end
end

--[=[
@m getInvites
@t http
@r Cache
@d Returns a newly constructed cache of all invite objects for the channel. The
cache and its objects are not automatically updated via gateway events. You must
call this method again to get the updated objects.
]=]
function GuildChannel:getInvites()
	local data, err = self.client._api:getChannelInvites(self._id)
	if data then
		return Cache(data, Invite, self.client)
	else
		return nil, err
	end
end

--[=[
@m getPermissionOverwriteFor
@t mem
@p obj Role/Member
@r PermissionOverwrite
@d Returns a permission overwrite object corresponding to the provided member or
role object. If a cached overwrite is not found, an empty overwrite with
zero-permissions is returned instead. Therefore, this can be used to create a
new overwrite when one does not exist. Note that the member or role must exist
in the same guild as the channel does.
]=]
function GuildChannel:getPermissionOverwriteFor(obj)
	local id, type
	if isInstance(obj, classes.Role) and self._parent == obj._parent then
		id, type = obj._id, 'role'
	elseif isInstance(obj, classes.Member) and self._parent == obj._parent then
		id, type = obj._user._id, 'member'
	else
		return nil, 'Invalid Role or Member: ' .. tostring(obj)
	end
	local overwrites = self._permission_overwrites
	return overwrites:get(id) or overwrites:_insert(setmetatable({
		id = id, type = type, allow = 0, deny = 0
	}, {__jsontype = 'object'}))
end

--[=[
@m delete
@t http
@r boolean
@d Permanently deletes the channel. This cannot be undone!
]=]
function GuildChannel:delete()
	return self:_delete()
end

--[=[@p permissionOverwrites Cache An iterable cache of all overwrites that exist in this channel. To access an
overwrite that may exist, but is not cached, use `GuildChannel:getPermissionOverwriteFor`.]=]
function get.permissionOverwrites(self)
	return self._permission_overwrites
end

--[=[@p name string The name of the channel. This should be between 2 and 100 characters in length.]=]
function get.name(self)
	return self._name
end

--[=[@p position number The position of the channel, where 0 is the highest.]=]
function get.position(self)
	return self._position
end

--[=[@p guild Guild The guild in which this channel exists.]=]
function get.guild(self)
	return self._parent
end

--[=[@p category GuildCategoryChannel/nil The parent channel category that may contain this channel.]=]
function get.category(self)
	return self._parent._categories:get(self._parent_id)
end

--[=[@p private boolean Whether the "everyone" role has permission to view this
channel. In the Discord channel, private text channels are indicated with a lock
icon and private voice channels are not visible.]=]
function get.private(self)
	local overwrite = self._permission_overwrites:get(self._parent._id)
	return overwrite and overwrite:getDeniedPermissions():has('readMessages')
end

return GuildChannel
