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
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
|
|
} |