// Copyright 2023 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 config provides methods for loading and querying a
// telemetry upload config file.
package config

import (
	"encoding/json"
	"os"
	"strings"

	"golang.org/x/telemetry"
)

// Config is a wrapper around telemetry.UploadConfig that provides some
// convenience methods for checking the contents of a report.
type Config struct {
	*telemetry.UploadConfig
	program         map[string]bool
	goos            map[string]bool
	goarch          map[string]bool
	goversion       map[string]bool
	pgversion       map[pgkey]bool
	pgcounter       map[pgkey]bool
	pgcounterprefix map[pgkey]bool
	pgstack         map[pgkey]bool
	rate            map[pgkey]float64
}

type pgkey struct {
	program, key string
}

func ReadConfig(file string) (*Config, error) {
	data, err := os.ReadFile(file)
	if err != nil {
		return nil, err
	}
	var cfg telemetry.UploadConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, err
	}
	return NewConfig(&cfg), nil
}

func NewConfig(cfg *telemetry.UploadConfig) *Config {
	ucfg := Config{UploadConfig: cfg}
	ucfg.goos = set(ucfg.GOOS)
	ucfg.goarch = set(ucfg.GOARCH)
	ucfg.goversion = set(ucfg.GoVersion)
	ucfg.program = make(map[string]bool, len(ucfg.Programs))
	ucfg.pgversion = make(map[pgkey]bool, len(ucfg.Programs))
	ucfg.pgcounter = make(map[pgkey]bool, len(ucfg.Programs))
	ucfg.pgcounterprefix = make(map[pgkey]bool, len(ucfg.Programs))
	ucfg.pgstack = make(map[pgkey]bool, len(ucfg.Programs))
	ucfg.rate = make(map[pgkey]float64)
	for _, p := range ucfg.Programs {
		ucfg.program[p.Name] = true
		for _, v := range p.Versions {
			ucfg.pgversion[pgkey{p.Name, v}] = true
		}
		for _, c := range p.Counters {
			for _, e := range Expand(c.Name) {
				ucfg.pgcounter[pgkey{p.Name, e}] = true
				ucfg.rate[pgkey{p.Name, e}] = c.Rate
			}
			prefix, _, found := strings.Cut(c.Name, ":")
			if found {
				ucfg.pgcounterprefix[pgkey{p.Name, prefix}] = true
			}
		}
		for _, s := range p.Stacks {
			ucfg.pgstack[pgkey{p.Name, s.Name}] = true
			ucfg.rate[pgkey{p.Name, s.Name}] = s.Rate
		}
	}
	return &ucfg
}

func (r *Config) HasProgram(s string) bool {
	return r.program[s]
}

func (r *Config) HasGOOS(s string) bool {
	return r.goos[s]
}

func (r *Config) HasGOARCH(s string) bool {
	return r.goarch[s]
}

func (r *Config) HasGoVersion(s string) bool {
	return r.goversion[s]
}

func (r *Config) HasVersion(program, version string) bool {
	return r.pgversion[pgkey{program, version}]
}

func (r *Config) HasCounter(program, counter string) bool {
	return r.pgcounter[pgkey{program, counter}]
}

func (r *Config) HasCounterPrefix(program, prefix string) bool {
	return r.pgcounterprefix[pgkey{program, prefix}]
}

func (r *Config) HasStack(program, stack string) bool {
	return r.pgstack[pgkey{program, stack}]
}

func (r *Config) Rate(program, name string) float64 {
	return r.rate[pgkey{program, name}]
}

func set(slice []string) map[string]bool {
	s := make(map[string]bool, len(slice))
	for _, v := range slice {
		s[v] = true
	}
	return s
}

// Expand takes a counter defined with buckets and expands it into distinct
// strings for each bucket
func Expand(counter string) []string {
	prefix, rest, hasBuckets := strings.Cut(counter, "{")
	var counters []string
	if hasBuckets {
		buckets := strings.Split(strings.TrimSuffix(rest, "}"), ",")
		for _, b := range buckets {
			counters = append(counters, prefix+b)
		}
	} else {
		counters = append(counters, prefix)
	}
	return counters
}
