/*
Copyright IBM Corp. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/

package namer

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/hyperledger/fabric-lib-go/common/metrics"
)

type Namer struct {
	namespace  string
	subsystem  string
	name       string
	nameFormat string
	labelNames map[string]struct{}
}

func NewCounterNamer(c metrics.CounterOpts) *Namer {
	return &Namer{
		namespace:  c.Namespace,
		subsystem:  c.Subsystem,
		name:       c.Name,
		nameFormat: c.StatsdFormat,
		labelNames: sliceToSet(c.LabelNames),
	}
}

func NewGaugeNamer(g metrics.GaugeOpts) *Namer {
	return &Namer{
		namespace:  g.Namespace,
		subsystem:  g.Subsystem,
		name:       g.Name,
		nameFormat: g.StatsdFormat,
		labelNames: sliceToSet(g.LabelNames),
	}
}

func NewHistogramNamer(h metrics.HistogramOpts) *Namer {
	return &Namer{
		namespace:  h.Namespace,
		subsystem:  h.Subsystem,
		name:       h.Name,
		nameFormat: h.StatsdFormat,
		labelNames: sliceToSet(h.LabelNames),
	}
}

func (n *Namer) validateKey(name string) {
	if _, ok := n.labelNames[name]; !ok {
		panic("invalid label name: " + name)
	}
}

func (n *Namer) FullyQualifiedName() string {
	switch {
	case n.namespace != "" && n.subsystem != "":
		return strings.Join([]string{n.namespace, n.subsystem, n.name}, ".")
	case n.namespace != "":
		return strings.Join([]string{n.namespace, n.name}, ".")
	case n.subsystem != "":
		return strings.Join([]string{n.subsystem, n.name}, ".")
	default:
		return n.name
	}
}

func (n *Namer) labelsToMap(labelValues []string) map[string]string {
	labels := map[string]string{}
	for i := 0; i < len(labelValues); i += 2 {
		key := labelValues[i]
		n.validateKey(key)
		if i == len(labelValues)-1 {
			labels[key] = "unknown"
		} else {
			labels[key] = labelValues[i+1]
		}
	}
	return labels
}

var (
	formatRegexp            = regexp.MustCompile(`%{([#?[:alnum:]_]+)}`)
	invalidLabelValueRegexp = regexp.MustCompile(`[.|:\s]`)
)

func (n *Namer) Format(labelValues ...string) string {
	labels := n.labelsToMap(labelValues)

	cursor := 0
	var segments []string
	// iterate over the regex groups and convert to formatters
	matches := formatRegexp.FindAllStringSubmatchIndex(n.nameFormat, -1)
	for _, m := range matches {
		start, end := m[0], m[1]
		labelStart, labelEnd := m[2], m[3]

		if start > cursor {
			segments = append(segments, n.nameFormat[cursor:start])
		}

		key := n.nameFormat[labelStart:labelEnd]
		var value string
		switch key {
		case "#namespace":
			value = n.namespace
		case "#subsystem":
			value = n.subsystem
		case "#name":
			value = n.name
		case "#fqname":
			value = n.FullyQualifiedName()
		default:
			var ok bool
			value, ok = labels[key]
			if !ok {
				panic(fmt.Sprintf("invalid label in name format: %s", key))
			}
			value = invalidLabelValueRegexp.ReplaceAllString(value, "_")
		}
		segments = append(segments, value)

		cursor = end
	}

	// handle any trailing suffix
	if cursor != len(n.nameFormat) {
		segments = append(segments, n.nameFormat[cursor:])
	}

	return strings.Join(segments, "")
}

func sliceToSet(set []string) map[string]struct{} {
	labelSet := map[string]struct{}{}
	for _, s := range set {
		labelSet[s] = struct{}{}
	}
	return labelSet
}
