package dovecot

import (
	"bytes"
	"fmt"
	"io"
	"net"
	"strconv"
	"strings"
	"sync"
	"time"

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

type Dovecot struct {
	Servers []string
	Domains []string
}

func (d *Dovecot) Description() string {
	return "Read statistics from one or many dovecot servers"
}

var sampleConfig = `
  ## specify dovecot servers via an address:port list
  ##  e.g.
  ##    localhost:24242
  ##
  ## If no servers are specified, then localhost is used as the host.
  servers = ["localhost:24242"]
  ## Only collect metrics for these domains, collect all if empty
  domains = []
`

func (d *Dovecot) SampleConfig() string { return sampleConfig }

const defaultPort = "24242"

// Reads stats from all configured servers.
func (d *Dovecot) Gather(acc telegraf.Accumulator) error {

	if len(d.Servers) == 0 {
		d.Servers = append(d.Servers, "127.0.0.1:24242")
	}

	var wg sync.WaitGroup

	var outerr error

	var domains = make(map[string]bool)

	for _, dom := range d.Domains {
		domains[dom] = true
	}

	for _, serv := range d.Servers {
		wg.Add(1)
		go func(serv string) {
			defer wg.Done()
			outerr = d.gatherServer(serv, acc, domains)
		}(serv)
	}

	wg.Wait()

	return outerr
}

func (d *Dovecot) gatherServer(addr string, acc telegraf.Accumulator, doms map[string]bool) error {
	_, _, err := net.SplitHostPort(addr)
	if err != nil {
		return fmt.Errorf("Error: %s on url %s\n", err, addr)
	}

	c, err := net.Dial("tcp", addr)
	if err != nil {
		return fmt.Errorf("Unable to connect to dovecot server '%s': %s", addr, err)
	}
	defer c.Close()

	c.Write([]byte("EXPORT\tdomain\n\n"))
	var buf bytes.Buffer
	io.Copy(&buf, c)
	//	buf := bufio.NewReader(c)

	host, _, _ := net.SplitHostPort(addr)

	return gatherStats(&buf, acc, doms, host)
}

func gatherStats(buf *bytes.Buffer, acc telegraf.Accumulator, doms map[string]bool, host string) error {

	lines := strings.Split(buf.String(), "\n")
	head := strings.Split(lines[0], "\t")
	vals := lines[1:]

	for i := range vals {
		if vals[i] == "" {
			continue
		}
		val := strings.Split(vals[i], "\t")
		fields := make(map[string]interface{})
		if len(doms) > 0 && !doms[val[0]] {
			continue
		}
		tags := map[string]string{"server": host, "domain": val[0]}
		for n := range val {
			switch head[n] {
			case "domain":
				continue
				//				fields[head[n]] = val[n]
			case "user_cpu", "sys_cpu", "clock_time":
				fields[head[n]] = secParser(val[n])
			case "reset_timestamp", "last_update":
				fields[head[n]] = timeParser(val[n])
			default:
				ival, _ := splitSec(val[n])
				fields[head[n]] = ival
			}
		}

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

	return nil
}

func splitSec(tm string) (sec int64, msec int64) {
	var err error
	ss := strings.Split(tm, ".")

	sec, err = strconv.ParseInt(ss[0], 10, 64)
	if err != nil {
		sec = 0
	}
	if len(ss) > 1 {
		msec, err = strconv.ParseInt(ss[1], 10, 64)
		if err != nil {
			msec = 0
		}
	} else {
		msec = 0
	}

	return sec, msec
}

func timeParser(tm string) time.Time {

	sec, msec := splitSec(tm)
	return time.Unix(sec, msec)
}

func secParser(tm string) float64 {

	sec, msec := splitSec(tm)
	return float64(sec) + (float64(msec) / 1000000.0)
}

func init() {
	inputs.Add("dovecot", func() telegraf.Input {
		return &Dovecot{}
	})
}