/*
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 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 General Public License for more details.

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

package engine

import (
	"sync"
	"time"

	"github.com/cgrates/cgrates/config"
	"github.com/cgrates/cgrates/utils"
)

func NewSafEvent(mp map[string]any) *SafEvent {
	return &SafEvent{Me: NewMapEvent(mp)}
}

// SafEvent is a generic event which is safe to read/write from multiple goroutines
type SafEvent struct {
	sync.RWMutex
	Me MapEvent // need it exportable so we can pass it on network
}

func (se *SafEvent) Clone() (cln *SafEvent) {
	se.RLock()
	cln = &SafEvent{Me: se.Me.Clone()}
	se.RUnlock()
	return
}

// MapEvent offers access to MapEvent methods, avoiding locks
func (se *SafEvent) MapEvent() (mp MapEvent) {
	return se.Me
}

func (se *SafEvent) String() (out string) {
	se.RLock()
	out = se.Me.String()
	se.RUnlock()
	return
}

func (se *SafEvent) FieldAsInterface(fldPath []string) (out any, err error) {
	se.RLock()
	out, err = se.Me.FieldAsInterface(fldPath)
	se.RUnlock()
	return
}

func (se *SafEvent) FieldAsString(fldPath []string) (out string, err error) {
	se.RLock()
	out, err = se.Me.FieldAsString(fldPath)
	se.RUnlock()
	return
}

func (se *SafEvent) HasField(fldName string) (has bool) {
	se.RLock()
	has = se.Me.HasField(fldName)
	se.RUnlock()
	return
}

func (se *SafEvent) Get(fldName string) (out any, has bool) {
	se.RLock()
	out, has = se.Me[fldName]
	se.RUnlock()
	return
}

func (se *SafEvent) GetIgnoreErrors(fldName string) (out any) {
	out, _ = se.Get(fldName)
	return
}

// Set will set a field's value
func (se *SafEvent) Set(fldName string, val any) {
	se.Lock()
	se.Me[fldName] = val
	se.Unlock()
}

// Remove will remove a field from map
func (se *SafEvent) Remove(fldName string) {
	se.Lock()
	delete(se.Me, fldName)
	se.Unlock()
}

func (se *SafEvent) GetString(fldName string) (out string, err error) {
	se.RLock()
	out, err = se.Me.GetString(fldName)
	se.RUnlock()
	return
}

func (se *SafEvent) GetTInt64(fldName string) (out int64, err error) {
	se.RLock()
	out, err = se.Me.GetTInt64(fldName)
	se.RUnlock()
	return
}

func (se *SafEvent) GetStringIgnoreErrors(fldName string) (out string) {
	out, _ = se.GetString(fldName)
	return
}

// GetDuration returns a field as Duration
func (se *SafEvent) GetDuration(fldName string) (d time.Duration, err error) {
	se.RLock()
	d, err = se.Me.GetDuration(fldName)
	se.RUnlock()
	return
}

// GetDurationPointer returns pointer towards duration, useful to detect presence of duration
func (se *SafEvent) GetDurationOrDefault(fldName string, dflt time.Duration) (d time.Duration, err error) {
	_, has := se.Get(fldName)
	if !has {
		return dflt, nil
	}
	return se.GetDuration(fldName)
}

// GetDuration returns a field as Duration, ignoring errors
func (se *SafEvent) GetDurationIgnoreErrors(fldName string) (d time.Duration) {
	d, _ = se.GetDuration(fldName)
	return
}

// GetDurationPointer returns pointer towards duration, useful to detect presence of duration
func (se *SafEvent) GetDurationPtr(fldName string) (d *time.Duration, err error) {
	fldIface, has := se.Get(fldName)
	if !has {
		return nil, utils.ErrNotFound
	}
	var dReal time.Duration
	if dReal, err = utils.IfaceAsDuration(fldIface); err != nil {
		return
	}
	return &dReal, nil
}

// GetDurationPointer returns pointer towards duration, useful to detect presence of duration
func (se *SafEvent) GetDurationPtrIgnoreErrors(fldName string) (d *time.Duration) {
	d, _ = se.GetDurationPtr(fldName)
	return
}

// GetDurationPointer returns pointer towards duration, useful to detect presence of duration
func (se *SafEvent) GetDurationPtrOrDefault(fldName string, dflt *time.Duration) (d *time.Duration, err error) {
	fldIface, has := se.Get(fldName)
	if !has {
		return dflt, nil
	}
	var dReal time.Duration
	if dReal, err = utils.IfaceAsDuration(fldIface); err != nil {
		return
	}
	return &dReal, nil
}

// GetTime returns a field as Time
func (se *SafEvent) GetTime(fldName string, tmz string) (t time.Time, err error) {
	se.RLock()
	t, err = se.Me.GetTime(fldName, tmz)
	se.RUnlock()
	return
}

// GetTimeIgnoreErrors returns a field as Time instance, ignoring errors
func (se *SafEvent) GetTimeIgnoreErrors(fldName string, tmz string) (t time.Time) {
	t, _ = se.GetTime(fldName, tmz)
	return
}

// GetSet will attempt to get a field value
// if field not present set it to the value received as parameter
func (se *SafEvent) GetSetString(fldName string, setVal string) (out string, err error) {
	se.Lock()
	defer se.Unlock()
	outIface, has := se.Me[fldName]
	if !has {
		se.Me[fldName] = setVal
		out = setVal
		return
	}
	// should be present, return it as string
	return utils.IfaceAsString(outIface), nil
}

// GetMapInterface returns the map stored internally without cloning it
func (se *SafEvent) GetMapInterface() (mp map[string]any) {
	se.RLock()
	mp = se.Me
	se.RUnlock()
	return
}

// AsMapInterface returns the cloned map stored internally
func (se *SafEvent) AsMapInterface() (mp map[string]any) {
	se.RLock()
	mp = se.Me.Clone()
	se.RUnlock()
	return
}

// AsMapString returns a map[string]string out of mp, ignoring specific fields if needed
// most used when needing to export extraFields
func (se *SafEvent) AsMapString(ignoredFlds utils.StringSet) (mp map[string]string) {
	se.RLock()
	mp = se.Me.AsMapString(ignoredFlds)
	se.RUnlock()
	return
}

// AsCDR exports the SafEvent as CDR
func (se *SafEvent) AsCDR(cfg *config.CGRConfig, tnt, tmz string) (cdr *CDR, err error) {
	se.RLock()
	cdr, err = se.Me.AsCDR(cfg, tnt, tmz)
	se.RUnlock()
	return
}

// AsCGREvent exports the SafEvent as CGREvent
func (se *SafEvent) AsCGREvent(tnt string) (cgrEv *utils.CGREvent) {
	se.RLock()
	cgrEv = &utils.CGREvent{
		Tenant: tnt,
		ID:     utils.UUIDSha1Prefix(),
		Time:   utils.TimePointer(time.Now()),
		Event:  se.Me.Data(),
	}
	se.RUnlock()
	return
}
