/*
** Zabbix
** Copyright (C) 2001-2021 Zabbix SIA
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
**/

package plugin

import (
	"fmt"
	"reflect"
	"regexp"
	"unicode"
)

type Metric struct {
	Plugin      Accessor
	Key         string
	Description string
}

var Metrics map[string]*Metric = make(map[string]*Metric)
var Plugins map[string]Accessor = make(map[string]Accessor)

func registerMetric(plugin Accessor, name string, key string, description string) {
	if ok, _ := regexp.MatchString(`^[A-Za-z0-9\._-]+$`, key); !ok {
		panic(fmt.Sprintf(`cannot register metric "%s" having invalid format`, key))
	}

	if 0 == len(description) {
		panic(fmt.Sprintf(`cannot register metric "%s" with empty description`, key))
	}

	if unicode.IsLower([]rune(description)[0]) {
		panic(fmt.Sprintf(`cannot register metric "%s" with description without capital first letter: "%s"`, key, description))
	}

	if description[len(description)-1] != '.' {
		panic(fmt.Sprintf(`cannot register metric "%s" without dot at the end of description: "%s"`, key, description))
	}

	if _, ok := Metrics[key]; ok {
		panic(fmt.Sprintf(`cannot register duplicate metric "%s"`, key))
	}

	t := reflect.TypeOf(plugin)
	for i := 0; i < t.NumMethod(); i++ {
		method := t.Method(i)
		switch method.Name {
		case "Export":
			if _, ok := plugin.(Exporter); !ok {
				panic(fmt.Sprintf(`the "%s" plugin has %s method, but does implement Exporter interface`, name, method.Name))
			}
		case "Collect", "Period":
			if _, ok := plugin.(Collector); !ok {
				panic(fmt.Sprintf(`the "%s" plugin has %s method, but does not implement Collector interface`, name, method.Name))
			}
		case "Watch":
			if _, ok := plugin.(Watcher); !ok {
				panic(fmt.Sprintf(`the "%s" plugin has %s method, but does not implement Watcher interface`, name, method.Name))
			}
		case "Configure", "Validate":
			if _, ok := plugin.(Configurator); !ok {
				panic(fmt.Sprintf(`the "%s" plugin has %s method, but does not implement Configurator interface`, name, method.Name))
			}
		case "Start", "Stop":
			if _, ok := plugin.(Runner); !ok {
				panic(fmt.Sprintf(`the "%s" plugin has %s method, but does not implement Runner interface`, name, method.Name))
			}
		}
	}
	switch plugin.(type) {
	case Exporter, Collector, Runner, Watcher, Configurator:
	default:
		panic(fmt.Sprintf(`plugin "%s" does not implement any plugin interfaces`, name))
	}

	if p, ok := Plugins[name]; ok {
		if p != plugin {
			panic(fmt.Sprintf(`plugin name "%s" has been already registered by other plugin`, name))
		}
	} else {
		Plugins[name] = plugin
		plugin.Init(name)
	}

	Metrics[key] = &Metric{Plugin: plugin, Key: key, Description: description}
}

func RegisterMetrics(impl Accessor, name string, params ...string) {
	if len(params) < 2 {
		panic("expected at least one metric and its description")
	}
	if len(params)&1 != 0 {
		panic("expected even number of metric and description parameters")
	}
	for i := 0; i < len(params); i += 2 {
		registerMetric(impl, name, params[i], params[i+1])
	}
}

func Get(key string) (acc Accessor, err error) {
	if m, ok := Metrics[key]; ok {
		return m.Plugin, nil
	}
	return nil, UnsupportedMetricError
}

func ClearRegistry() {
	Metrics = make(map[string]*Metric)
	Plugins = make(map[string]Accessor)
}
