#! /usr/bin/env python3

"""Monitor sound files; For each program, check for:

- new files;
- deleted files;
- differences between files and sound;
- quality of the files;

It tries to parse the file name to get the date of the diffusion of an
episode and associate the file with it; We use the following format:
    yyyymmdd[_n][_][name]

Where:
    'yyyy' the year of the episode's diffusion;
    'mm' the month of the episode's diffusion;
    'dd' the day of the episode's diffusion;
    'n' the number of the episode (if multiple episodes);
    'name' the title of the sound;


To check quality of files, call the command sound_quality_check using the
parameters given by the setting SOUND_QUALITY. This script requires
Sox (and soxi).
"""
import atexit
import concurrent.futures as futures
import logging
import os
import time
from argparse import RawTextHelpFormatter

from django.core.management.base import BaseCommand
from watchdog.observers import Observer

from aircox.conf import settings
from aircox.management.sound_file import SoundFile
from aircox.management.sound_monitor import MonitorHandler
from aircox.models import Program, Sound

logger = logging.getLogger("aircox.commands")


class Command(BaseCommand):
    help = __doc__

    def report(self, program=None, component=None, *content):
        if not component:
            logger.info(
                "%s: %s", str(program), " ".join([str(c) for c in content])
            )
        else:
            logger.info(
                "%s, %s: %s",
                str(program),
                str(component),
                " ".join([str(c) for c in content]),
            )

    def scan(self):
        """For all programs, scan dirs."""
        logger.info("scan all programs...")
        programs = Program.objects.filter()

        dirs = []
        for program in programs:
            logger.info("#%d %s", program.id, program.title)
            self.scan_for_program(
                program,
                settings.SOUND_ARCHIVES_SUBDIR,
                type=Sound.TYPE_ARCHIVE,
            )
            self.scan_for_program(
                program,
                settings.SOUND_EXCERPTS_SUBDIR,
                type=Sound.TYPE_EXCERPT,
            )
            dirs.append(os.path.join(program.abspath))
        return dirs

    def scan_for_program(self, program, subdir, **sound_kwargs):
        """Scan a given directory that is associated to the given program, and
        update sounds information."""
        logger.info("- %s/", subdir)
        if not program.ensure_dir(subdir):
            return

        subdir = os.path.join(program.abspath, subdir)
        sounds = []

        # sounds in directory
        for path in os.listdir(subdir):
            path = os.path.join(subdir, path)
            if not path.endswith(settings.SOUND_FILE_EXT):
                continue

            sound_file = SoundFile(path)
            sound_file.sync(program=program, **sound_kwargs)
            sounds.append(sound_file.sound.pk)

        # sounds in db & unchecked
        sounds = Sound.objects.filter(file__startswith=subdir).exclude(
            pk__in=sounds
        )
        self.check_sounds(sounds, program=program)

    def check_sounds(self, qs, **sync_kwargs):
        """Only check for the sound existence or update."""
        # check files
        for sound in qs:
            if sound.check_on_file():
                SoundFile(sound.file.path).sync(sound=sound, **sync_kwargs)

    def monitor(self):
        """Run in monitor mode."""
        with futures.ThreadPoolExecutor() as pool:
            archives_handler = MonitorHandler(
                settings.SOUND_ARCHIVES_SUBDIR,
                pool,
                type=Sound.TYPE_ARCHIVE,
            )
            excerpts_handler = MonitorHandler(
                settings.SOUND_EXCERPTS_SUBDIR,
                pool,
                type=Sound.TYPE_EXCERPT,
            )

            observer = Observer()
            observer.schedule(
                archives_handler,
                settings.PROGRAMS_DIR_ABS,
                recursive=True,
            )
            observer.schedule(
                excerpts_handler,
                settings.PROGRAMS_DIR_ABS,
                recursive=True,
            )
            observer.start()

            def leave():
                observer.stop()
                observer.join()

            atexit.register(leave)

            while True:
                time.sleep(1)

    def add_arguments(self, parser):
        parser.formatter_class = RawTextHelpFormatter
        parser.add_argument(
            "-q",
            "--quality_check",
            action="store_true",
            help="Enable quality check using sound_quality_check on all "
            "sounds marqued as not good",
        )
        parser.add_argument(
            "-s",
            "--scan",
            action="store_true",
            help="Scan programs directories for changes, plus check for a "
            " matching diffusion on sounds that have not been yet assigned",
        )
        parser.add_argument(
            "-m",
            "--monitor",
            action="store_true",
            help="Run in monitor mode, watch for modification in the "
            "filesystem and react in consequence",
        )

    def handle(self, *args, **options):
        if options.get("scan"):
            self.scan()
        # if options.get('quality_check'):
        #    self.check_quality(check=(not options.get('scan')))
        if options.get("monitor"):
            self.monitor()
