# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""Scheduling implementation -- task objects and their XML serialization."""

import os
import sys
import time
import logging
import datetime
from io import StringIO

from sigma.util.xmlparse import ElementClass
from sigma.util.xmlparse import String, Integer, Boolean, ISODate, Weekday
from sigma.sched import timezones

logger = logging.getLogger("sched.tasks")

# Classes defining a task schedule; used by the scheduler and by Directive

class Command(ElementClass):
    __element__ = "command"
    __attributes__ = {"exe": String}

class Date(ElementClass):
    __element__ = "date"
    __attributes__ = {"year": Integer,
                      "month": Integer,
                      "day": Integer,
                      "hour": Integer,
                      "min": Integer,
                      "sec": Integer,
                      "usec": Integer,
                      "utc": Boolean}

    def toDatetime(self):
        year = self.year or 0
        month = self.month or 0
        day = self.day or 0
        hour = self.hour or 0
        min = self.min or 0
        sec = self.sec or 0
        usec = self.usec or 0
        if self.utc:
            return datetime.datetime(year, month, day, hour, min, sec, usec,
                                     timezones.utc)
        else:
            return datetime.datetime(year, month, day, hour, min, sec, usec,
                                     timezones.Local)

class Start(ElementClass):
    __element__ = "start"
    __attributes__ = {"date": ISODate}

    def toDatetime(self):
        "Returns a datetime instance in the local zone with time set to zero."
        if not self.date:
            return None
        return datetime.datetime(self.date.year, self.date.month, self.date.day,
                                 tzinfo=timezones.Local)

class End(Start):
    __element__ = "end"

class Schedule(ElementClass):
    __element__ = "schedule"
    __attributes__ = {"day": Weekday,
                      "start": Integer,
                      "end": Integer}

class Freq(ElementClass):
    __element__ = "freq"
    __attributes__ = {"secs": Integer}

class Detail(Date):
    # Used to indicate when a task was last run, in listing response
    __element__ = "detail"

class Task(ElementClass):
    __element__ = "task"
    __attributes__ = {"id": String}
    __children__ = {Freq: "_freq",
                    Command: "_command",
                    Date: "_date",
                    Detail: "_detail",
                    Start: "start",
                    End: "end",
                    Schedule: "schedule[]",
                    }

    # Properties for backwards compatibility

    # 'when' is special -- it merges the XML subelements start and _date.
    # Modifying 'when' sets either start or _date and clears the other,
    # depending on what form the argument takes.

    def getWhen(self):
        """Returns the earliest of self._date and self.start, if both exist.

        If only one exists, return that one.

        If neither exists, return None.

        """
        when1 = self._date and self._date.toDatetime()
        when2 = self.start and self.start.toDatetime()
        return (when1 and when2) and min(when1, when2) or when1 or when2
    def setWhen(self, when):
        self._date = self.start = None
        if when is None:
            return
        if (when.tzinfo is timezones.Local and
            0 == when.hour == when.minute == when.second == when.microsecond):
            when = datetime.date(when.year, when.month, when.day)
            self.start = Start(date=when)
        else:
            self.start = newdate(when)
    when = property(getWhen, setWhen)

    def getFreq(self):
        return self._freq and self._freq.secs
    def setFreq(self, freq):
        self._freq = freq and Freq(secs=freq)
    freq = property(getFreq, setFreq)

    def getCommand(self):
        return self._command and self._command.exe
    def setCommand(self, command):
        self._command = command and Command(exe=command)
    command = property(getCommand, setCommand)

    def getDetail(self):
        return self._detail and self._detail.toDatetime()
    def setDetail(self, detail):
        if detail is None:
            self._detail = None
        else:
            self._detail = newdate(detail, Detail)
    detail = property(getDetail, setDetail)

class Tasks(ElementClass):
    __element__ = "tasks"
    __children__ = {Task: "tasks[]"}

def newdate(d, cls=Date):
    "Turns a datetime into a Date or Detail element."
    return cls(year=d.year, month=d.month, day=d.day,
               hour=d.hour, min=d.minute, sec=d.second or None,
               usec=d.microsecond or None,
               utc=d.tzinfo is timezones.utc)

def parse_tasks(xmltext):
    """Function to parse XML into a list of tasks."""
    return Tasks.__parseText__(xmltext).tasks

def newtask(id=None, command=None, when=None, freq=None, detail=None):
    return Task(id=id, command=command, when=when, freq=freq, detail=detail)
