package bdiscord

import (
	"errors"
	"regexp"
	"strings"
	"unicode"

	"github.com/bwmarrin/discordgo"
)

func (b *Bdiscord) getNick(user *discordgo.User) string {
	b.membersMutex.RLock()
	defer b.membersMutex.RUnlock()

	if member, ok := b.userMemberMap[user.ID]; ok {
		if member.Nick != "" {
			// Only return if nick is set.
			return member.Nick
		}
		// Otherwise return username.
		return user.Username
	}

	// If we didn't find nick, search for it.
	member, err := b.c.GuildMember(b.guildID, user.ID)
	if err != nil {
		b.Log.Warnf("Failed to fetch information for member %#v: %#v", user, err)
		return user.Username
	} else if member == nil {
		b.Log.Warnf("Got no information for member %#v", user)
		return user.Username
	}
	b.userMemberMap[user.ID] = member
	b.nickMemberMap[member.User.Username] = member
	if member.Nick != "" {
		b.nickMemberMap[member.Nick] = member
		return member.Nick
	}
	return user.Username
}

func (b *Bdiscord) getGuildMemberByNick(nick string) (*discordgo.Member, error) {
	b.membersMutex.RLock()
	defer b.membersMutex.RUnlock()

	if member, ok := b.nickMemberMap[nick]; ok {
		return member, nil
	}
	return nil, errors.New("Couldn't find guild member with nick " + nick) // This will most likely get ignored by the caller
}

func (b *Bdiscord) getChannelID(name string) string {
	b.channelsMutex.RLock()
	defer b.channelsMutex.RUnlock()

	idcheck := strings.Split(name, "ID:")
	if len(idcheck) > 1 {
		return idcheck[1]
	}
	for _, channel := range b.channels {
		if channel.Name == name {
			return channel.ID
		}
	}
	return ""
}

func (b *Bdiscord) getChannelName(id string) string {
	b.channelsMutex.RLock()
	defer b.channelsMutex.RUnlock()

	for _, channel := range b.channels {
		if channel.ID == id {
			return channel.Name
		}
	}
	return ""
}

var (
	// See https://discordapp.com/developers/docs/reference#message-formatting.
	channelMentionRE = regexp.MustCompile("<#[0-9]+>")
	emojiRE          = regexp.MustCompile("<(:.*?:)[0-9]+>")
	userMentionRE    = regexp.MustCompile("@[^@\n]{1,32}")
)

func (b *Bdiscord) replaceChannelMentions(text string) string {
	replaceChannelMentionFunc := func(match string) string {
		var err error
		channelID := match[2 : len(match)-1]

		channelName := b.getChannelName(channelID)
		// If we don't have the channel refresh our list.
		if channelName == "" {
			b.channels, err = b.c.GuildChannels(b.guildID)
			if err != nil {
				return "#unknownchannel"
			}
			channelName = b.getChannelName(channelID)
		}
		return "#" + channelName
	}
	return channelMentionRE.ReplaceAllStringFunc(text, replaceChannelMentionFunc)
}

func (b *Bdiscord) replaceUserMentions(text string) string {
	replaceUserMentionFunc := func(match string) string {
		var (
			err      error
			member   *discordgo.Member
			username string
		)

		usernames := enumerateUsernames(match[1:])
		for _, username = range usernames {
			b.Log.Debugf("Testing mention: '%s'", username)
			member, err = b.getGuildMemberByNick(username)
			if err == nil {
				break
			}
		}
		if member == nil {
			return match
		}
		return strings.Replace(match, "@"+username, member.User.Mention(), 1)
	}
	return userMentionRE.ReplaceAllStringFunc(text, replaceUserMentionFunc)
}

func (b *Bdiscord) stripCustomoji(text string) string {
	return emojiRE.ReplaceAllString(text, `$1`)
}

func (b *Bdiscord) replaceAction(text string) (string, bool) {
	if strings.HasPrefix(text, "_") && strings.HasSuffix(text, "_") {
		return text[1:], true
	}
	return text, false
}

// splitURL splits a webhookURL and returns the ID and token.
func (b *Bdiscord) splitURL(url string) (string, string) {
	const (
		expectedWebhookSplitCount = 7
		webhookIdxID              = 5
		webhookIdxToken           = 6
	)
	webhookURLSplit := strings.Split(url, "/")
	if len(webhookURLSplit) != expectedWebhookSplitCount {
		b.Log.Fatalf("%s is no correct discord WebhookURL", url)
	}
	return webhookURLSplit[webhookIdxID], webhookURLSplit[webhookIdxToken]
}

func enumerateUsernames(s string) []string {
	onlySpace := true
	for _, r := range s {
		if !unicode.IsSpace(r) {
			onlySpace = false
			break
		}
	}
	if onlySpace {
		return nil
	}

	var username, endSpace string
	var usernames []string
	skippingSpace := true
	for _, r := range s {
		if unicode.IsSpace(r) {
			if !skippingSpace {
				usernames = append(usernames, username)
				skippingSpace = true
			}
			endSpace += string(r)
			username += string(r)
		} else {
			endSpace = ""
			username += string(r)
			skippingSpace = false
		}
	}
	if endSpace == "" {
		usernames = append(usernames, username)
	}
	return usernames
}