package minecraft

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/influxdata/telegraf"
	"github.com/influxdata/telegraf/plugins/inputs"
)

const sampleConfig = `
  ## server address for minecraft
  # server = "localhost"
  ## port for RCON
  # port = "25575"
  ## password RCON for mincraft server
  # password = ""
`

var (
	playerNameRegex = regexp.MustCompile(`for\s([^:]+):-`)
	scoreboardRegex = regexp.MustCompile(`(?U):\s(\d+)\s\((.*)\)`)
)

// Client is an interface for a client which gathers data from a minecraft server
type Client interface {
	Gather(producer RCONClientProducer) ([]string, error)
}

// Minecraft represents a connection to a minecraft server
type Minecraft struct {
	Server    string
	Port      string
	Password  string
	client    Client
	clientSet bool
}

// Description gives a brief description.
func (s *Minecraft) Description() string {
	return "Collects scores from a minecraft server's scoreboard using the RCON protocol"
}

// SampleConfig returns our sampleConfig.
func (s *Minecraft) SampleConfig() string {
	return sampleConfig
}

// Gather uses the RCON protocal to collect player and
// scoreboard stats from a minecraft server.
//var hasClient bool = false
func (s *Minecraft) Gather(acc telegraf.Accumulator) error {
	// can't simply compare s.client to nil, because comparing an interface
	// to nil often does not produce the desired result
	if !s.clientSet {
		var err error
		s.client, err = NewRCON(s.Server, s.Port, s.Password)
		if err != nil {
			return err
		}
		s.clientSet = true
	}

	// (*RCON).Gather() takes an RCONClientProducer for testing purposes
	d := defaultClientProducer{
		Server: s.Server,
		Port:   s.Port,
	}

	scores, err := s.client.Gather(d)
	if err != nil {
		return err
	}

	for _, score := range scores {
		player, err := ParsePlayerName(score)
		if err != nil {
			return err
		}
		tags := map[string]string{
			"player": player,
			"server": s.Server + ":" + s.Port,
		}

		stats, err := ParseScoreboard(score)
		if err != nil {
			return err
		}
		var fields = make(map[string]interface{}, len(stats))
		for _, stat := range stats {
			fields[stat.Name] = stat.Value
		}

		acc.AddFields("minecraft", fields, tags)
	}

	return nil
}

// ParsePlayerName takes an input string from rcon, to parse
// the player.
func ParsePlayerName(input string) (string, error) {
	playerMatches := playerNameRegex.FindAllStringSubmatch(input, -1)
	if playerMatches == nil {
		return "", fmt.Errorf("no player was matched")
	}
	return playerMatches[0][1], nil
}

// Score is an individual tracked scoreboard stat.
type Score struct {
	Name  string
	Value int
}

// ParseScoreboard takes an input string from rcon, to parse
// scoreboard stats.
func ParseScoreboard(input string) ([]Score, error) {
	scoreMatches := scoreboardRegex.FindAllStringSubmatch(input, -1)
	if scoreMatches == nil {
		return nil, fmt.Errorf("No scores found")
	}

	var scores []Score

	for _, match := range scoreMatches {
		number := match[1]
		name := match[2]
		n, err := strconv.Atoi(number)
		// Not necessary in current state, because regex can only match integers,
		// maybe become necessary if regex is modified to match more types of
		// numbers
		if err != nil {
			return nil, fmt.Errorf("Failed to parse score")
		}
		s := Score{
			Name:  name,
			Value: n,
		}
		scores = append(scores, s)
	}
	return scores, nil
}

func init() {
	inputs.Add("minecraft", func() telegraf.Input {
		return &Minecraft{
			Server: "localhost",
			Port:   "25575",
		}
	})
}
