// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package debug

import (
	"context"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"sort"

	tlm "golang.org/x/tools/internal/lsp/telemetry"
	"golang.org/x/tools/internal/telemetry"
	"golang.org/x/tools/internal/telemetry/metric"
)

var rpcTmpl = template.Must(template.Must(BaseTemplate.Clone()).Parse(`
{{define "title"}}RPC Information{{end}}
{{define "body"}}
	<H2>Inbound</H2>
	{{template "rpcSection" .Inbound}}
	<H2>Outbound</H2>
	{{template "rpcSection" .Outbound}}
{{end}}
{{define "rpcSection"}}
	{{range .}}<P>
		<b>{{.Method}}</b> {{.Started}} <a href="/trace/{{.Method}}">traces</a> ({{.InProgress}} in progress)
		<br>
		<i>Latency</i> {{with .Latency}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
		<i>By bucket</i> 0s {{range .Latency.Values}}<b>{{.Count}}</b> {{.Limit}} {{end}}
		<br>
		<i>Received</i> {{with .Received}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
		<i>Sent</i> {{with .Sent}}{{.Mean}} ({{.Min}}<{{.Max}}){{end}}
		<br>
		<i>Result codes</i> {{range .Codes}}{{.Key}}={{.Count}} {{end}}
		</P>
	{{end}}
{{end}}
`))

type rpcs struct {
	Inbound  []*rpcStats
	Outbound []*rpcStats
}

type rpcStats struct {
	Method     string
	Started    int64
	Completed  int64
	InProgress int64
	Latency    rpcTimeHistogram
	Received   rpcBytesHistogram
	Sent       rpcBytesHistogram
	Codes      []*rpcCodeBucket
}

type rpcTimeHistogram struct {
	Sum    timeUnits
	Count  int64
	Mean   timeUnits
	Min    timeUnits
	Max    timeUnits
	Values []rpcTimeBucket
}

type rpcTimeBucket struct {
	Limit timeUnits
	Count int64
}

type rpcBytesHistogram struct {
	Sum    byteUnits
	Count  int64
	Mean   byteUnits
	Min    byteUnits
	Max    byteUnits
	Values []rpcBytesBucket
}

type rpcBytesBucket struct {
	Limit byteUnits
	Count int64
}

type rpcCodeBucket struct {
	Key   string
	Count int64
}

func (r *rpcs) StartSpan(ctx context.Context, span *telemetry.Span)  {}
func (r *rpcs) FinishSpan(ctx context.Context, span *telemetry.Span) {}
func (r *rpcs) Log(ctx context.Context, event telemetry.Event)       {}
func (r *rpcs) Flush()                                               {}

func (r *rpcs) Metric(ctx context.Context, data telemetry.MetricData) {
	for i, group := range data.Groups() {
		set := &r.Inbound
		if group.Get(tlm.RPCDirection) == tlm.Outbound {
			set = &r.Outbound
		}
		method, ok := group.Get(tlm.Method).(string)
		if !ok {
			log.Printf("Not a method... %v", group)
			continue
		}
		index := sort.Search(len(*set), func(i int) bool {
			return (*set)[i].Method >= method
		})
		if index >= len(*set) || (*set)[index].Method != method {
			old := *set
			*set = make([]*rpcStats, len(old)+1)
			copy(*set, old[:index])
			copy((*set)[index+1:], old[index:])
			(*set)[index] = &rpcStats{Method: method}
		}
		stats := (*set)[index]
		switch data.Handle() {
		case started:
			stats.Started = data.(*metric.Int64Data).Rows[i]
		case completed:
			status, ok := group.Get(tlm.StatusCode).(string)
			if !ok {
				log.Printf("Not status... %v", group)
				continue
			}
			var b *rpcCodeBucket
			for c, entry := range stats.Codes {
				if entry.Key == status {
					b = stats.Codes[c]
					break
				}
			}
			if b == nil {
				b = &rpcCodeBucket{Key: status}
				stats.Codes = append(stats.Codes, b)
				sort.Slice(stats.Codes, func(i int, j int) bool {
					return stats.Codes[i].Key < stats.Codes[j].Key
				})
			}
			b.Count = data.(*metric.Int64Data).Rows[i]
		case latency:
			data := data.(*metric.HistogramFloat64Data)
			row := data.Rows[i]
			stats.Latency.Count = row.Count
			stats.Latency.Sum = timeUnits(row.Sum)
			stats.Latency.Min = timeUnits(row.Min)
			stats.Latency.Max = timeUnits(row.Max)
			stats.Latency.Mean = timeUnits(row.Sum) / timeUnits(row.Count)
			stats.Latency.Values = make([]rpcTimeBucket, len(data.Info.Buckets))
			last := int64(0)
			for i, b := range data.Info.Buckets {
				stats.Latency.Values[i].Limit = timeUnits(b)
				stats.Latency.Values[i].Count = row.Values[i] - last
				last = row.Values[i]
			}
		case sentBytes:
			data := data.(*metric.HistogramInt64Data)
			row := data.Rows[i]
			stats.Sent.Count = row.Count
			stats.Sent.Sum = byteUnits(row.Sum)
			stats.Sent.Min = byteUnits(row.Min)
			stats.Sent.Max = byteUnits(row.Max)
			stats.Sent.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
		case receivedBytes:
			data := data.(*metric.HistogramInt64Data)
			row := data.Rows[i]
			stats.Received.Count = row.Count
			stats.Received.Sum = byteUnits(row.Sum)
			stats.Sent.Min = byteUnits(row.Min)
			stats.Sent.Max = byteUnits(row.Max)
			stats.Received.Mean = byteUnits(row.Sum) / byteUnits(row.Count)
		}
	}

	for _, set := range [][]*rpcStats{r.Inbound, r.Outbound} {
		for _, stats := range set {
			stats.Completed = 0
			for _, b := range stats.Codes {
				stats.Completed += b.Count
			}
			stats.InProgress = stats.Started - stats.Completed
		}
	}
}

func (r *rpcs) getData(req *http.Request) interface{} {
	return r
}

func units(v float64, suffixes []string) string {
	s := ""
	for _, s = range suffixes {
		n := v / 1000
		if n < 1 {
			break
		}
		v = n
	}
	return fmt.Sprintf("%.2f%s", v, s)
}

type timeUnits float64

func (v timeUnits) String() string {
	v = v * 1000 * 1000
	return units(float64(v), []string{"ns", "μs", "ms", "s"})
}

type byteUnits float64

func (v byteUnits) String() string {
	return units(float64(v), []string{"B", "KB", "MB", "GB", "TB"})
}
