"""
    This file is part of yacron.

    Copyright (C) 2016 Vadim Kuznetsov <vimusov@gmail.com>

    yacron is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    yacron is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with yacron.  If not, see <http://www.gnu.org/licenses/>.
"""

import os
import select
import logging
import contextlib
from datetime import datetime
from yacron.task import load_tasks
from yacron.privileges import sudo
from yacron.notify import create_notify_socket, read_notify


log = logging.getLogger(__name__)


class Scheduler(object):
    """
    Schedule, run and reap tasks.
    """
    def __init__(self):
        self._tasks = None
        self._is_running = True

    def _reload_tasks(self):
        """
        Reload tasks from all config files (system-wide and user's).
        """
        with sudo():
            tasks = list(load_tasks())

        if tasks:
            self._tasks = tasks
            log.info('%d tasks loaded', len(tasks))
        else:
            log.info('no tasks found, idle state')

    def _reap_tasks(self):
        """
        Reap completed tasks to avoid zombies.
        """
        log.debug('reaping completed tasks')
        while True:
            try:
                pid, status = os.waitpid(-1, os.WNOHANG | os.WUNTRACED)

            except ChildProcessError:
                log.debug('no completed tasks left')
                break

            except Exception as error:
                log.exception(error)
                break

            if (pid == 0) and (status == 0):  # no completed tasks yet
                break

            if os.WIFEXITED(status):
                exit_code = os.WEXITSTATUS(status)
                log_func = log.error if exit_code else log.info
                log_func('task pid=%d finished, exit_code=%d', pid, exit_code)
            else:
                log.error('task pid=%d finished, status=%d', pid, status)

    def _loop(self, sock):
        """
        Main loop.
        :param sock: Socket object for processing reload notifications.
        """
        while self._is_running:
            try:
                cur_time = datetime.now()
                timeout = 60 - cur_time.second
                log.debug('waiting %d seconds to next iteration', timeout)

                for unused in range(timeout):
                    fds, unused1, unused2 = select.select([sock], [], [], 1.0)

                    if not self._is_running:
                        return

                    if fds:
                        read_notify(sock)
                        log.info('a notify message has been caught, reloading tasks')
                        self._reload_tasks()
                        break
                else:
                    if not self._is_running:
                        return

                    if not self._tasks:
                        log.debug('no tasks, continue sleeping')
                        continue

                    log.debug('processing tasks')

                    cur_time = datetime.now()
                    for task in self._tasks:
                        task.check_time_and_run(cur_time)

                    self._reap_tasks()

            except Exception as error:
                log.exception(error)

    def start(self):
        """
        Start main loop.
        """
        self._reload_tasks()

        with contextlib.closing(create_notify_socket()) as sock:
            self._loop(sock)

        self._reap_tasks()

    def stop(self):
        """
        Stop main loop.
        """
        self._is_running = False
