package main

import (
	"fmt"
	"html"
	"regexp"
	"strings"
	"time"
	"unicode"

	"github.com/zorchenhimer/MovieNight/common"
)

var (
	regexSpoiler = regexp.MustCompile(`\|\|(.*?)\|\|`)
	spoilerStart = `<span class="spoiler" onclick='$(this).removeClass("spoiler").addClass("spoiler-active")'>`
	spoilerEnd   = `</span>`
)

type Client struct {
	name          string // Display name
	conn          *chatConnection
	belongsTo     *ChatRoom
	color         string
	CmdLevel      common.CommandLevel
	IsColorForced bool
	IsNameForced  bool
	regexName     *regexp.Regexp

	// Times since last event.  use time.Duration.Since()
	nextChat  time.Time // rate limit chat messages
	nextNick  time.Time // rate limit nickname changes
	nextColor time.Time // rate limit color changes
	nextAuth  time.Time // rate limit failed auth attempts.  Sould prolly have a backoff policy.
	authTries int       // number of failed auth attempts

	nextDuplicate time.Time
	lastMsg       string
}

func NewClient(connection *chatConnection, room *ChatRoom, name, color string) (*Client, error) {
	c := &Client{
		conn:      connection,
		belongsTo: room,
		color:     color,
	}

	if err := c.setName(name); err != nil {
		return nil, fmt.Errorf("could not set client name to %#v: %v", name, err)
	}

	// Set initial vaules to their rate limit duration in the past.
	c.nextChat = time.Now()
	c.nextNick = time.Now()
	c.nextColor = time.Now()
	c.nextAuth = time.Now()

	return c, nil
}

//Client has a new message to broadcast
func (cl *Client) NewMsg(data common.ClientData) {
	return
}

func (cl *Client) SendChatData(data common.ChatData) error {
	// Don't send chat or event data to clients that have not fully joined the
	// chatroom (ie, they have not set a name).
	if cl.name == "" && (data.Type == common.DTChat || data.Type == common.DTEvent) {
		return nil
	}

	// Colorize name on chat messages
	if data.Type == common.DTChat {
		var err error
		data = cl.replaceColorizedName(data)
		if err != nil {
			return fmt.Errorf("could not colorize name: %v", err)
		}
	}

	cd, err := data.ToJSON()
	if err != nil {
		return fmt.Errorf("could not create ChatDataJSON of type %d: %v", data.Type, err)
	}
	return cl.Send(cd)
}

func (cl *Client) Send(data common.ChatDataJSON) error {
	err := cl.conn.WriteData(data)
	if err != nil {
		return fmt.Errorf("could not send message: %v", err)
	}
	return nil
}

func (cl *Client) SendServerMessage(s string) error {
	err := cl.SendChatData(common.NewChatMessage("", ColorServerMessage, s, common.CmdlUser, common.MsgServer))
	if err != nil {
		return fmt.Errorf("could send server message to %s: message - %#v: %v", cl.name, s, err)
	}
	return nil
}

// Make links clickable
func formatLinks(input string) string {
	newMsg := []string{}
	for _, word := range strings.Split(input, " ") {
		if strings.HasPrefix(word, "http://") || strings.HasPrefix(word, "https://") {
			word = html.UnescapeString(word)
			word = fmt.Sprintf(`<a href="%s" target="_blank">%s</a>`, word, word)
		}
		newMsg = append(newMsg, word)
	}
	return strings.Join(newMsg, " ")
}

//Exiting out
func (cl *Client) Exit() {
	cl.belongsTo.Leave(cl.name, cl.color)
}

// Outgoing messages
func (cl *Client) Message(msg string) {
	msg = common.ParseEmotes(msg)
	cl.belongsTo.AddMsg(cl, false, false, msg)
}

// Outgoing /me command
func (cl *Client) Me(msg string) {
	msg = common.ParseEmotes(msg)
	cl.belongsTo.AddMsg(cl, true, false, msg)
}

func (cl *Client) Mod() {
	if cl.CmdLevel < common.CmdlMod {
		cl.CmdLevel = common.CmdlMod
	}
}

func (cl *Client) Unmod() {
	cl.CmdLevel = common.CmdlUser
}

func (cl *Client) Host() string {
	return cl.conn.Host()
}

func (cl *Client) setName(s string) error {
	cl.name = s
	if cl.conn != nil {
		cl.conn.clientName = s
	}
	return nil
}

func (cl *Client) setColor(s string) error {
	cl.color = s
	return cl.SendChatData(common.NewChatHiddenMessage(common.CdColor, cl.color))
}

func (cl *Client) replaceColorizedName(chatData common.ChatData) common.ChatData {
	data := chatData.Data.(common.DataMessage)
	words := strings.Split(data.Message, " ")
	newWords := []string{}

	for _, word := range words {
		if strings.ToLower(word) == strings.ToLower(cl.name) || strings.ToLower(word) == strings.ToLower("@"+cl.name) {
			newWords = append(newWords, `<span class="mention">`+word+`</span>`)
		} else {
			newWords = append(newWords, word)
		}
	}

	data.Message = strings.Join(newWords, " ")
	chatData.Data = data
	return chatData
}

var dumbSpaces = []string{
	"\n",
	"\t",
	"\r",
	"\u200b",
}

func removeDumbSpaces(msg string) string {
	for _, ds := range dumbSpaces {
		msg = strings.ReplaceAll(msg, ds, " ")
	}

	newMsg := ""
	for _, r := range msg {
		if unicode.IsSpace(r) {
			newMsg += " "
		} else {
			newMsg += string(r)
		}
	}
	return newMsg
}

func addSpoilerTags(msg string) string {
	return regexSpoiler.ReplaceAllString(msg, fmt.Sprintf(`%s$1%s`, spoilerStart, spoilerEnd))
}
