You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
8.7 KiB
Go

package valueobjects
import (
"fmt"
"time"
"knowfoolery/backend/shared/types"
"knowfoolery/backend/shared/errors"
)
// SessionTimer manages the timing for a game session with warnings
type SessionTimer struct {
startTime types.Timestamp
endTime *types.Timestamp
maxDuration types.Duration
isActive bool
warningsSent []TimerWarning
}
// TimerWarning represents a warning sent during the session
type TimerWarning struct {
WarningType string `json:"warning_type"`
Message string `json:"message"`
SentAt types.Timestamp `json:"sent_at"`
TimeRemaining types.Duration `json:"time_remaining"`
}
// NewSessionTimer creates a new session timer
func NewSessionTimer(startTime types.Timestamp) *SessionTimer {
return &SessionTimer{
startTime: startTime,
endTime: nil,
maxDuration: types.NewDuration(types.MaxSessionDuration),
isActive: true,
warningsSent: make([]TimerWarning, 0),
}
}
// NewSessionTimerNow creates a new session timer starting now
func NewSessionTimerNow() *SessionTimer {
return NewSessionTimer(types.NewTimestamp())
}
// StartTime returns the session start time
func (st *SessionTimer) StartTime() types.Timestamp {
return st.startTime
}
// EndTime returns the session end time (nil if not ended)
func (st *SessionTimer) EndTime() *types.Timestamp {
return st.endTime
}
// MaxDuration returns the maximum allowed session duration
func (st *SessionTimer) MaxDuration() types.Duration {
return st.maxDuration
}
// IsActive returns true if the timer is currently active
func (st *SessionTimer) IsActive() bool {
return st.isActive
}
// WarningsSent returns all warnings that have been sent
func (st *SessionTimer) WarningsSent() []TimerWarning {
return st.warningsSent
}
// ElapsedTime returns the time elapsed since session start
func (st *SessionTimer) ElapsedTime() types.Duration {
var endPoint types.Timestamp
if st.endTime != nil {
endPoint = *st.endTime
} else {
endPoint = types.NewTimestamp()
}
elapsed := endPoint.Sub(st.startTime)
return types.NewDuration(elapsed)
}
// RemainingTime returns the time remaining in the session
func (st *SessionTimer) RemainingTime() types.Duration {
if !st.isActive {
return types.NewDuration(0)
}
elapsed := st.ElapsedTime()
remaining := st.maxDuration.Duration - elapsed.Duration
if remaining < 0 {
remaining = 0
}
return types.NewDuration(remaining)
}
// IsExpired returns true if the session has exceeded its maximum duration
func (st *SessionTimer) IsExpired() bool {
return st.ElapsedTime().Duration >= st.maxDuration.Duration
}
// TimeUntilExpiry returns the duration until session expires
func (st *SessionTimer) TimeUntilExpiry() types.Duration {
return st.RemainingTime()
}
// End marks the session timer as ended
func (st *SessionTimer) End() error {
if !st.isActive {
return errors.ErrOperationNotAllowed("end timer", "timer is not active")
}
now := types.NewTimestamp()
st.endTime = &now
st.isActive = false
return nil
}
// GetSessionDuration returns the total duration of the session
func (st *SessionTimer) GetSessionDuration() types.Duration {
if st.endTime != nil {
return types.NewDuration(st.endTime.Sub(st.startTime))
}
return st.ElapsedTime()
}
// ShouldSendWarning checks if a warning should be sent based on remaining time
func (st *SessionTimer) ShouldSendWarning() (bool, TimerWarning) {
if !st.isActive {
return false, TimerWarning{}
}
remaining := st.RemainingTime()
// Check for different warning thresholds
warnings := []struct {
threshold time.Duration
warningType string
message string
}{
{types.SessionWarningAt5Min, "5min", "5 minutes remaining!"},
{types.SessionWarningAt1Min, "1min", "1 minute remaining!"},
{types.SessionWarningAt10Sec, "10sec", "10 seconds left!"},
}
for _, warning := range warnings {
// Check if we're at or below this threshold
if remaining.Duration <= warning.threshold {
// Check if we've already sent this warning
if !st.hasWarningSent(warning.warningType) {
return true, TimerWarning{
WarningType: warning.warningType,
Message: warning.message,
SentAt: types.NewTimestamp(),
TimeRemaining: remaining,
}
}
}
}
return false, TimerWarning{}
}
// AddWarning records that a warning has been sent
func (st *SessionTimer) AddWarning(warning TimerWarning) {
st.warningsSent = append(st.warningsSent, warning)
}
// hasWarningSent checks if a specific warning type has already been sent
func (st *SessionTimer) hasWarningSent(warningType string) bool {
for _, warning := range st.warningsSent {
if warning.WarningType == warningType {
return true
}
}
return false
}
// GetFormattedRemainingTime returns the remaining time formatted for display
func (st *SessionTimer) GetFormattedRemainingTime() string {
if !st.isActive {
return "00:00"
}
remaining := st.RemainingTime()
if remaining.Duration <= 0 {
return "00:00"
}
totalSeconds := int(remaining.Seconds())
minutes := totalSeconds / 60
seconds := totalSeconds % 60
return fmt.Sprintf("%02d:%02d", minutes, seconds)
}
// GetFormattedElapsedTime returns the elapsed time formatted for display
func (st *SessionTimer) GetFormattedElapsedTime() string {
elapsed := st.ElapsedTime()
totalSeconds := int(elapsed.Seconds())
minutes := totalSeconds / 60
seconds := totalSeconds % 60
return fmt.Sprintf("%02d:%02d", minutes, seconds)
}
// IsInWarningPeriod returns true if the session is in a warning period
func (st *SessionTimer) IsInWarningPeriod() bool {
if !st.isActive {
return false
}
remaining := st.RemainingTime()
return remaining.Duration <= types.SessionWarningAt5Min
}
// IsInCriticalPeriod returns true if the session is in the critical final period
func (st *SessionTimer) IsInCriticalPeriod() bool {
if !st.isActive {
return false
}
remaining := st.RemainingTime()
return remaining.Duration <= types.SessionWarningAt1Min
}
// GetRemainingPercentage returns the percentage of time remaining (0-100)
func (st *SessionTimer) GetRemainingPercentage() float64 {
if !st.isActive {
return 0.0
}
remaining := st.RemainingTime()
if st.maxDuration.Duration == 0 {
return 0.0
}
percentage := (remaining.Duration.Seconds() / st.maxDuration.Duration.Seconds()) * 100.0
if percentage < 0 {
percentage = 0
}
if percentage > 100 {
percentage = 100
}
return percentage
}
// GetElapsedPercentage returns the percentage of time elapsed (0-100)
func (st *SessionTimer) GetElapsedPercentage() float64 {
return 100.0 - st.GetRemainingPercentage()
}
// TimerStatus represents the current status of the timer
type TimerStatus struct {
IsActive bool `json:"is_active"`
IsExpired bool `json:"is_expired"`
IsInWarningPeriod bool `json:"is_in_warning_period"`
IsInCriticalPeriod bool `json:"is_in_critical_period"`
StartTime types.Timestamp `json:"start_time"`
EndTime *types.Timestamp `json:"end_time,omitempty"`
ElapsedTime types.Duration `json:"elapsed_time"`
RemainingTime types.Duration `json:"remaining_time"`
ElapsedFormatted string `json:"elapsed_formatted"`
RemainingFormatted string `json:"remaining_formatted"`
ElapsedPercentage float64 `json:"elapsed_percentage"`
RemainingPercentage float64 `json:"remaining_percentage"`
WarningsSent []TimerWarning `json:"warnings_sent"`
}
// GetStatus returns the current comprehensive status of the timer
func (st *SessionTimer) GetStatus() *TimerStatus {
return &TimerStatus{
IsActive: st.IsActive(),
IsExpired: st.IsExpired(),
IsInWarningPeriod: st.IsInWarningPeriod(),
IsInCriticalPeriod: st.IsInCriticalPeriod(),
StartTime: st.StartTime(),
EndTime: st.EndTime(),
ElapsedTime: st.ElapsedTime(),
RemainingTime: st.RemainingTime(),
ElapsedFormatted: st.GetFormattedElapsedTime(),
RemainingFormatted: st.GetFormattedRemainingTime(),
ElapsedPercentage: st.GetElapsedPercentage(),
RemainingPercentage: st.GetRemainingPercentage(),
WarningsSent: st.WarningsSent(),
}
}
// SessionTimerFromValues creates a SessionTimer from stored values
// Used when loading from persistence
func SessionTimerFromValues(startTime types.Timestamp, endTime *types.Timestamp, warningsSent []TimerWarning) *SessionTimer {
isActive := endTime == nil
timer := &SessionTimer{
startTime: startTime,
endTime: endTime,
maxDuration: types.NewDuration(types.MaxSessionDuration),
isActive: isActive,
warningsSent: warningsSent,
}
// If timer was active but is now expired, mark it as inactive
if isActive && timer.IsExpired() {
now := types.NewTimestamp()
timer.endTime = &now
timer.isActive = false
}
return timer
}