// Package configmap provides an abstraction for reading and writing config
package configmap

import (
	"encoding/base64"
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"unicode"
)

// Priority of getters
type Priority int8

// Priority levels for AddGetter
const (
	PriorityNormal  Priority = iota
	PriorityConfig           // use for reading from the config
	PriorityDefault          // use for default values
	PriorityMax
)

// Getter provides an interface to get config items
type Getter interface {
	// Get should get an item with the key passed in and return
	// the value. If the item is found then it should return true,
	// otherwise false.
	Get(key string) (value string, ok bool)
}

// Setter provides an interface to set config items
type Setter interface {
	// Set should set an item into persistent config store.
	Set(key, value string)
}

// Mapper provides an interface to read and write config
type Mapper interface {
	Getter
	Setter
}

// Map provides a wrapper around multiple Setter and
// Getter interfaces.
type Map struct {
	setters []Setter
	getters []getprio
}

type getprio struct {
	getter   Getter
	priority Priority
}

// New returns an empty Map
func New() *Map {
	return &Map{}
}

// AddGetter appends a getter onto the end of the getters in priority order
func (c *Map) AddGetter(getter Getter, priority Priority) *Map {
	c.getters = append(c.getters, getprio{getter, priority})
	sort.SliceStable(c.getters, func(i, j int) bool {
		return c.getters[i].priority < c.getters[j].priority
	})
	return c
}

// AddSetter appends a setter onto the end of the setters
func (c *Map) AddSetter(setter Setter) *Map {
	c.setters = append(c.setters, setter)
	return c
}

// ClearSetters removes all the setters set so far
func (c *Map) ClearSetters() *Map {
	c.setters = nil
	return c
}

// ClearGetters removes all the getters with the priority given
func (c *Map) ClearGetters(priority Priority) *Map {
	getters := c.getters[:0]
	for _, item := range c.getters {
		if item.priority != priority {
			getters = append(getters, item)
		}
	}
	c.getters = getters
	return c
}

// GetPriority gets an item with the key passed in and return the
// value from the first getter to return a result with priority <=
// maxPriority. If the item is found then it returns true, otherwise
// false.
func (c *Map) GetPriority(key string, maxPriority Priority) (value string, ok bool) {
	for _, item := range c.getters {
		if item.priority > maxPriority {
			break
		}
		value, ok = item.getter.Get(key)
		if ok {
			return value, ok
		}
	}
	return "", false
}

// Get gets an item with the key passed in and return the value from
// the first getter. If the item is found then it returns true,
// otherwise false.
func (c *Map) Get(key string) (value string, ok bool) {
	return c.GetPriority(key, PriorityMax)
}

// Set sets an item into all the stored setters.
func (c *Map) Set(key, value string) {
	for _, do := range c.setters {
		do.Set(key, value)
	}
}

// Simple is a simple Mapper for testing
type Simple map[string]string

// Get the value
func (c Simple) Get(key string) (value string, ok bool) {
	value, ok = c[key]
	return value, ok
}

// Set the value
func (c Simple) Set(key, value string) {
	c[key] = value
}

// String the map value the same way the config parser does, but with
// sorted keys for reproducibility.
func (c Simple) String() string {
	var ks = make([]string, 0, len(c))
	for k := range c {
		ks = append(ks, k)
	}
	sort.Strings(ks)
	var out strings.Builder
	for _, k := range ks {
		if out.Len() > 0 {
			out.WriteRune(',')
		}
		out.WriteString(k)
		out.WriteRune('=')
		out.WriteRune('\'')
		for _, ch := range c[k] {
			out.WriteRune(ch)
			// Escape ' as ''
			if ch == '\'' {
				out.WriteRune(ch)
			}
		}
		out.WriteRune('\'')
	}
	return out.String()
}

// Encode from c into a string suitable for putting on the command line
func (c Simple) Encode() (string, error) {
	if len(c) == 0 {
		return "", nil
	}
	buf, err := json.Marshal(c)
	if err != nil {
		return "", fmt.Errorf("encode simple map: %w", err)
	}
	return base64.RawStdEncoding.EncodeToString(buf), nil
}

// Decode an Encode~d string in into c
func (c Simple) Decode(in string) error {
	// Remove all whitespace from the input string
	in = strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			return -1
		}
		return r
	}, in)
	if len(in) == 0 {
		return nil
	}
	decodedM, err := base64.RawStdEncoding.DecodeString(in)
	if err != nil {
		return fmt.Errorf("decode simple map: %w", err)
	}
	err = json.Unmarshal(decodedM, &c)
	if err != nil {
		return fmt.Errorf("parse simple map: %w", err)
	}
	return nil
}
