// package down_time -- счётчик обратного времени в мсек
package down_time

import (
	"context"
	"fmt"

	// "log"
	"sync"
	"time"
	"wartank/pkg/alias"
	"wartank/pkg/components/product/parser_time"
	"wartank/pkg/components/safe_bool"
	"wartank/pkg/components/safe_int"
	"wartank/pkg/types"
)

const (
	спатьИнтервал = time.Millisecond * 1000 // Малый интервал сна в 100 мсек
)

// ВремОбрат -- счётчик обратного времени для игровой зоны (анга, база, битва и т.п.)
type ВремОбрат struct {
	сцена types.ИАренаКонтекст // Сцена, которой принадлежит отсчёт времени

	остатПарсер types.ИПарсерВремя // Парсер значения (мсек)

	текущ        *safe_int.БезопЦелое // Фактическое значение счётчика в мсек
	лимит        *safe_int.БезопЦелое // Целевое время срабатывания в мсек
	еслиРаботает *safe_bool.БезопБул  // Признак работы

	канВызов chan int // Канал для отправки сигналов (для верхнего уровня)

	кнт      context.Context // Контекст для счётчика времени
	фнОтмена func()          // Функция отмены контекста для счётчика времени

	блок sync.RWMutex
}

// НовВремОбрат -- возвращает новый *CountTime
func НовВремОбрат(сцена types.ИАренаКонтекст, время alias.МилСек) *ВремОбрат {
	if сцена == nil {
		panic("НовВремОбрат(): ИСцена == nil")
	}
	кнт, фнОтмена := context.WithCancel(сцена.Контекст())
	сам := &ВремОбрат{
		сцена:        сцена,
		текущ:        safe_int.НовБезопЦелое(),
		канВызов:     make(chan int, 2),
		еслиРаботает: safe_bool.НовБезопБул(),
		остатПарсер:  parser_time.НовПарсерВремя(),
		лимит:        safe_int.НовБезопЦелое(),
		кнт:          кнт,
		фнОтмена:     фнОтмена,
	}
	мСек := alias.МилСек(time.Now().UTC().UnixMilli()) + время
	сам.лимит.Уст(int(мСек))
	сам.еслиРаботает.Уст()
	go сам.пуск()
	go сам.закрыть()
	_ = types.ИВремяОстат(сам)
	return сам
}

// ПолучМилСек -- возвращает оставшееся хранимое время остатка
func (сам *ВремОбрат) ПолучМилСек() alias.МилСек {
	return сам.остатПарсер.ПолучМилСек()
}

// Запускает тикер для интервалов сна (через каждые 1000 мСек)
func (сам *ВремОбрат) пуск() {
	defer close(сам.канВызов)
	фнЖдать := func() {
		time.Sleep(спатьИнтервал)
		timeNow := time.Now().UTC().UnixMilli()
		цТекущ := сам.текущ.Получ()
		цТекущ -= 1000
		if цТекущ < 0 {
			цТекущ = 0
		}
		сам.текущ.Уст(цТекущ)
		if сам.лимит.Получ() > int(timeNow) || цТекущ > 0 {
			return
		}
		сам.канВызов <- 1
		сам.лимит.Уст(int(timeNow) + int(сам.остатПарсер.ПолучМилСек()))
	}
	for {
		select {
		case <-сам.кнт.Done(): // Отмена контекста тикера (а может и сцены, может и бота)
			return
		default:
			фнЖдать()
		}
	}
}

// Сброс -- сбрасывает оставшееся время в ноль
func (сам *ВремОбрат) Сброс() {
	сам.остатПарсер.Сброс()
	сам.текущ.Сброс()
	сам.лимит.Сброс()
}

// Стоп -- останавливает работу cчётчика
func (сам *ВремОбрат) Стоп() {
	сам.фнОтмена()
}

// Уст -- устанавливает число оставшихся сек
func (сам *ВремОбрат) Уст(время alias.Время) error {
	сам.блок.Lock()
	defer сам.блок.Unlock()
	if ош := сам.остатПарсер.Уст(время); ош != nil {
		return fmt.Errorf("ВремОбрат(): ошибка при установке времени, ош=\n\t%w", ош)
	}
	_val := сам.остатПарсер.ПолучМилСек()
	сам.текущ.Уст(int(_val))
	val := int(time.Now().UTC().UnixMilli()) + сам.текущ.Получ()
	сам.лимит.Уст(val)
	return nil
}

// String -- возвращает строковое представление оставшихся сек
func (сам *ВремОбрат) String() string {
	сам.блок.RLock()
	defer сам.блок.RUnlock()
	цОстат := сам.текущ.Получ()
	остат := time.Millisecond * time.Duration(цОстат)
	стрВрем := остат.String()
	return стрВрем
}

// КаналСиг -- возвращает канал чтения тиков
func (сам *ВремОбрат) КаналСиг() <-chan int {
	return сам.канВызов
}

func (сам *ВремОбрат) закрыть() {
	<-сам.кнт.Done()
	сам.блок.Lock()
	defer сам.блок.Unlock()
	if !сам.еслиРаботает.Получ() {
		return
	}
	сам.еслиРаботает.Сброс()
}
