text: # std
  import logging
  from os import fwalk, stat
  import re
  from datetime import datetime, timedelta
  from typing import Dict, cast, List, Union
  from threading import Thread
  from time import sleep
  import json
  
  # project
  from . import (
      HarvesterActivityConsumer,
      PartialConsumer,
      BlockConsumer,
      WalletAddedCoinConsumer,
      FinishedSignageConsumer,
  )
  from .stat_accumulators.eligible_plots_stats import EligiblePlotsStats
  from .stat_accumulators.wallet_added_coin_stats import WalletAddedCoinStats
  from .stat_accumulators.search_time_stats import SearchTimeStats
  from .stat_accumulators.signage_point_stats import SignagePointStats
  from .stat_accumulators.found_proof_stats import FoundProofStats
  from .stat_accumulators.number_plots_stats import NumberPlotsStats
  from .stat_accumulators.found_partial_stats import FoundPartialStats
  from .stat_accumulators.found_block_stats import FoundBlockStats
  from src.chia_log.parsers.wallet_added_coin_parser import WalletAddedCoinMessage
  from src.chia_log.parsers.harvester_activity_parser import HarvesterActivityMessage
  from src.chia_log.parsers.finished_signage_point_parser import FinishedSignagePointMessage
  from src.chia_log.parsers.partial_parser import PartialMessage
  from src.chia_log.parsers.block_parser import BlockMessage
  from src.notifier.notify_manager import NotifyManager
  from src.notifier import Event, EventType, EventPriority, EventService
  from UI_Manager import UIManager, InfoType
  
  
  class StatsManager:
      """Manage all stat accumulators and trigger daily notification to the user
      with a summary from all stats that have been collected for the past 24 hours.
      """
  
      def __init__(self, ui_manager, config: dict, notify_manager: NotifyManager):
          self._enable = config.get("enable", False)
          self.ui_manager = ui_manager
          self._notify_time = self._parse_notify_time(config.get("time_of_day", "21:00"))
          self._frequency_hours = config.get("frequency_hours", 24)
  
          if not self._enable:
              UIManager().updateErrorTextView("Disabled stats and daily notifications", InfoType.WARNING)
              return
          UIManager().updateTextView("Enabled stats for daily notifications", InfoType.INFO)
          self._notify_manager = notify_manager
          self._stat_accumulators = [
              WalletAddedCoinStats(),
              FoundProofStats(),
              FoundPartialStats(),
              FoundBlockStats(),
              SearchTimeStats(),
              NumberPlotsStats(),
              EligiblePlotsStats(),
              SignagePointStats(),
          ]
  
          UIManager().updateTextView(f"Summary notifications will be sent out every {self._frequency_hours} hours starting from {self._notify_time['hour']:02d}:{self._notify_time['minute']:02d}", InfoType.INFO)
          self._datetime_next_summary = datetime.now().replace(
              hour=self._notify_time["hour"], minute=self._notify_time["minute"], second=0, microsecond=0
          )
          while datetime.now() > self._datetime_next_summary:
              self._datetime_next_summary += timedelta(hours=self._frequency_hours)
  
          # Start thread
          self._is_running = True
          self._thread = Thread(target=self._run_loop)
          self._thread.start()
  
          # Start remote sender thread
          self.remote_data_sender_frequecy = 60
          self.hourly_ui_update_frequency = 3600
          self._is_remote_running = True
          self._datetime_next_report = datetime.now() + timedelta(seconds=self.remote_data_sender_frequecy)
          self._datetime_next_hourly_report = datetime.now() + timedelta(seconds=self.hourly_ui_update_frequency)
          
          self._remote_thread = Thread(target=self._run_remote_sender_loop)
          self._remote_thread.start()
  
      def consume_wallet_messages(self, objects: List[WalletAddedCoinMessage]):
          if not self._enable:
              return
          for stat_acc in self._stat_accumulators:
              if isinstance(stat_acc, WalletAddedCoinConsumer):
                  for obj in objects:
                      stat_acc.consume(obj)
  
      def consume_harvester_messages(self, objects: List[HarvesterActivityMessage]):
          if not self._enable:
              return
          for stat_acc in self._stat_accumulators:
              if isinstance(stat_acc, HarvesterActivityConsumer):
                  for obj in objects:
                      stat_acc.consume(obj)
  
      def consume_partial_messages(self, objects: List[PartialMessage]):
          if not self._enable:
              return
          for stat_acc in self._stat_accumulators:
              if isinstance(stat_acc, PartialConsumer):
                  for obj in objects:
                      stat_acc.consume(obj)
  
      def consume_block_messages(self, objects: List[BlockMessage]):
          if not self._enable:
              return
          for stat_acc in self._stat_accumulators:
              if isinstance(stat_acc, BlockConsumer):
                  for obj in objects:
                      stat_acc.consume(obj)
  
      def consume_signage_point_messages(self, objects: List[FinishedSignagePointMessage]):
          if not self._enable:
              return
          for stat_acc in self._stat_accumulators:
              if isinstance(stat_acc, FinishedSignageConsumer):
                  for obj in objects:
                      stat_acc.consume(obj)
  
      def _send_daily_notification(self):
          summary = f"Here's what happened in the last {self._frequency_hours} hours:\n"
          for stat_acc in self._stat_accumulators:
              summary += "\n" + stat_acc.get_summary()
              stat_acc.reset()
  
          self._notify_manager.process_events(
              [Event(type=EventType.DAILY_STATS, priority=EventPriority.LOW, service=EventService.DAILY, message=summary)]
          )
      
      def _update_past_24h_data(self):
          if not self._stat_accumulators:
              return
  
          last_elem = self._stat_accumulators[-1]
          summary = f"Here's what happened since {last_elem._last_reset_time.strftime('%m/%d/%Y %H:%M')}."
          for stat_acc in self._stat_accumulators:
              summary += "<br>" + stat_acc.get_summary()
          self.ui_manager.updateStatusDailyLabel(summary)
  
  
      def _run_loop(self):
          while self._is_running:
              if datetime.now() > self._datetime_next_summary:
                  self._send_daily_notification()
                  self._datetime_next_summary += timedelta(hours=self._frequency_hours)
              sleep(1)
  
      def stop(self):
          self._is_running = False
          self._is_remote_running = False
  
      def _parse_notify_time(self, value: Union[str, int], default: dict = {"hour": 21, "minute": 0}) -> dict:
          if type(value) == int:
              return {"hour": value, "minute": 0}
  
          value = cast(str, value)
          match = re.match(r"(?:[01]\d|2[0-3]):(?:[0-5]\d)", value)
          if match:
              return {"hour": int(value[:2]), "minute": int(value[-2:])}
  
          return default
  
      def _send_data_to_remote(self):
          data = dict()
          summary = f"Stats for {datetime.now().strftime('%m/%d/%Y %H:%M')}, view more in mobile app!"
          for stat_acc in self._stat_accumulators:
              data.update(stat_acc.get_data(isRemote=True))
              summary += "<br>" + stat_acc.get_summary(isRemote=True)
              stat_acc.reset(isRemote=True)
          self.ui_manager.updateStatusLabel(summary)
          self.ui_manager.sendFarmInfo(data)
  
  
      def _run_remote_sender_loop(self):
          while self._is_remote_running:
              if datetime.now() >= self._datetime_next_hourly_report:
                  self._update_past_24h_data()
                  self._datetime_next_hourly_report += timedelta(seconds=self.hourly_ui_update_frequency)
  
              if datetime.now() >= self._datetime_next_report:
                  self._send_data_to_remote()
                  self._datetime_next_report += timedelta(seconds=self.remote_data_sender_frequecy)
              sleep(1)