/*
Real-time Online/Offline Charging System (OCS) for Telecom & ISP environments
Copyright (C) ITsysCOM GmbH

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>
*/

package utils

import (
	"slices"
	"strconv"
	"strings"
)

type StringMap map[string]bool

func NewStringMap(s ...string) StringMap {
	result := make(StringMap)
	for _, v := range s {
		v = strings.TrimSpace(v)
		if v != EmptyString {
			if strings.HasPrefix(v, NegativePrefix) {
				result[v[1:]] = false
			} else {
				result[v] = true
			}
		}
	}
	return result
}

func ParseStringMap(s string) StringMap {
	if s == MetaZero {
		return make(StringMap)
	}
	return StringMapFromSlice(strings.Split(s, InfieldSep))
}

func (sm StringMap) Equal(om StringMap) bool {
	if sm == nil && om != nil {
		return false
	}
	if len(sm) != len(om) {
		return false
	}
	for key := range sm {
		if !om[key] {
			return false
		}
	}
	return true
}

func (sm StringMap) Includes(om StringMap) bool {
	if len(sm) < len(om) {
		return false
	}
	for key := range om {
		if !sm[key] {
			return false
		}
	}
	return true
}

func (sm StringMap) Slice() []string {
	result := make([]string, len(sm))
	i := 0
	for k := range sm {
		result[i] = k
		i++
	}
	return result
}

func (sm StringMap) IsEmpty() bool {
	return len(sm) == 0 || sm[MetaAny]
}

func StringMapFromSlice(s []string) StringMap {
	result := make(StringMap, len(s))
	for _, v := range s {
		v = strings.TrimSpace(v)
		if v != EmptyString {
			if strings.HasPrefix(v, NegativePrefix) {
				result[v[1:]] = false
			} else {
				result[v] = true
			}
		}
	}
	return result
}

func (sm StringMap) Copy(o StringMap) {
	for k, v := range o {
		sm[k] = v
	}
}

func (sm StringMap) Clone() StringMap {
	if sm == nil {
		return nil
	}
	result := make(StringMap, len(sm))
	result.Copy(sm)
	return result
}

func (sm StringMap) String() string {
	return strings.Join(sm.Slice(), InfieldSep)
}

func (sm StringMap) GetOne() string {
	for key := range sm {
		return key
	}
	return EmptyString
}

func (sm StringMap) HasKey(key string) (has bool) {
	_, has = sm[key]
	return
}

func MapStringToInt64(in map[string]string) (out map[string]int64, err error) {
	mapout := make(map[string]int64, len(in))
	for key, val := range in {
		x, err := strconv.Atoi(val)
		if err != nil {
			return nil, err
		}
		mapout[key] = int64(x)
	}
	return mapout, nil
}

// FlagsWithParamsFromSlice construct a  FlagsWithParams from the given slice
func FlagsWithParamsFromSlice(s []string) (flags FlagsWithParams) {
	flags = make(FlagsWithParams)
	for _, v := range s {
		flag := strings.SplitN(v, InInFieldSep, 3)
		if !flags.Has(flag[0]) {
			flags[flag[0]] = make(FlagParams)
		}
		flags[flag[0]].Add(flag[1:])
	}
	return
}

// FlagParams stores the parameters for a flag
type FlagParams map[string][]string

// Has returns if the key was mentioned in flags
func (fWp FlagParams) Has(opt string) (has bool) {
	_, has = fWp[opt]
	return
}

// ParamValue returns the value of the flag
func (fWp FlagParams) ParamValue(opt string) (ps string) {
	for _, ps = range fWp[opt] {
		if ps != EmptyString {
			return
		}
	}
	return
}

// Add adds the options to the flag
func (fWp FlagParams) Add(opts []string) {
	switch len(opts) {
	default: // just in case we call this function with more elements than needed
		fallthrough
	case 2:
		fWp[opts[0]] = strings.Split(opts[1], ANDSep)
	case 0:
	case 1:
		fWp[opts[0]] = []string{}
	}
}

// ParamsSlice returns the list of profiles for the subsystem
func (fWp FlagParams) ParamsSlice(opt string) (ps []string) {
	return fWp[opt] // if it doesn't have the option it will return an empty slice
}

// SliceFlags converts from FlagsParams to []string
func (fWp FlagParams) SliceFlags() (sls []string) {
	for key, sub := range fWp {
		if len(sub) == 0 { // no option for these subsystem
			sls = append(sls, key)
			continue
		}
		sls = append(sls, ConcatenatedKey(key, strings.Join(sub, InfieldSep)))
	}
	return
}

// Clone returns a deep copy of FlagParams
func (fWp FlagParams) Clone() (cln FlagParams) {
	if fWp == nil {
		return
	}
	cln = make(FlagParams)
	for flg, params := range fWp {
		var cprm []string
		if params != nil {
			cprm = slices.Clone(params)
		}
		cln[flg] = cprm
	}
	return
}

// FlagsWithParams should store a list of flags for each subsystem
type FlagsWithParams map[string]FlagParams

// Has returns if the key was mentioned in flags
func (fWp FlagsWithParams) Has(flag string) (has bool) {
	_, has = fWp[flag]
	return
}

// ParamsSlice returns the list of profiles for the subsystem
func (fWp FlagsWithParams) ParamsSlice(subs, opt string) (ps []string) {
	if psIfc, has := fWp[subs]; has {
		ps = psIfc.ParamsSlice(opt)
	}
	return
}

// ParamValue returns the value of the flag
func (fWp FlagsWithParams) ParamValue(subs string) (ps string) {
	for ps = range fWp[subs] {
		return
	}
	return
}

// SliceFlags converts from FlagsWithParams back to []string
func (fWp FlagsWithParams) SliceFlags() (sls []string) {
	for key, sub := range fWp {
		if len(sub) == 0 { // no option for these subsystem
			sls = append(sls, key)
			continue
		}
		for opt, values := range sub {
			if len(values) == 0 { // it's an option without values(e.g *derived_reply)
				sls = append(sls, ConcatenatedKey(key, opt))
				continue
			}
			sls = append(sls, ConcatenatedKey(key, opt, strings.Join(values, ANDSep)))
		}
	}
	return
}

// GetBool returns the flag as boolean
func (fWp FlagsWithParams) GetBool(key string) (b bool) {
	var v FlagParams
	if v, b = fWp[key]; !b {
		return // not present means false
	}
	if len(v) == 0 {
		return true // empty map
	}
	return v.Has(TrueStr) || !v.Has(FalseStr)
}

// Clone returns a deep copy of FlagsWithParams
func (fWp FlagsWithParams) Clone() (cln FlagsWithParams) {
	if fWp == nil {
		return
	}
	cln = make(FlagsWithParams)
	for flg, p := range fWp {
		cln[flg] = p.Clone()
	}
	return
}

func (sm StringMap) FieldAsInterface(fldPath []string) (val any, err error) {
	if sm == nil || len(fldPath) != 1 {
		return nil, ErrNotFound
	}
	bl, has := sm[fldPath[0]]
	if !has {
		return nil, ErrNotFound
	}
	return bl, nil
}

func (sm StringMap) FieldAsString(fldPath []string) (val string, err error) {
	var iface any
	iface, err = sm.FieldAsInterface(fldPath)
	if err != nil {
		return
	}
	return IfaceAsString(iface), nil
}
