from owrx.config.core import CoreConfig
from owrx.audio import AudioChopperProfile
from owrx.audio.queue import DecoderQueue
import threading
import wave
import os
from datetime import datetime, timedelta
from queue import Full
from typing import List

import logging

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


class WaveFile(object):
    def __init__(self, writer_id):
        self.timestamp = datetime.utcnow()
        self.writer_id = writer_id
        tmp_dir = CoreConfig().get_temporary_directory()
        self.filename = "{tmp_dir}/openwebrx-audiochopper-master-{id}-{timestamp}.wav".format(
            tmp_dir=tmp_dir,
            id=self.writer_id,
            timestamp=self.timestamp.strftime("%y%m%d_%H%M%S"),
        )
        self.waveFile = wave.open(self.filename, "wb")
        self.waveFile.setnchannels(1)
        self.waveFile.setsampwidth(2)
        self.waveFile.setframerate(12000)

    def close(self):
        self.waveFile.close()

    def getFileName(self):
        return self.filename

    def getTimestamp(self):
        return self.timestamp

    def writeframes(self, data):
        return self.waveFile.writeframes(data)

    def unlink(self):
        os.unlink(self.filename)
        self.waveFile = None


class AudioWriter(object):
    def __init__(self, chopper, interval, profiles: List[AudioChopperProfile]):
        self.chopper = chopper
        self.interval = interval
        self.profiles = profiles
        self.wavefile = None
        self.switchingLock = threading.Lock()
        self.timer = None

    def getWaveFile(self):
        return WaveFile(id(self))

    def getNextDecodingTime(self):
        # add one second to have the intervals tick over one second earlier
        # this avoids filename collisions, but also avoids decoding wave files with less than one second of audio
        t = datetime.utcnow() + timedelta(seconds=1)
        zeroed = t.replace(minute=0, second=0, microsecond=0)
        delta = t - zeroed
        seconds = (int(delta.total_seconds() / self.interval) + 1) * self.interval
        t = zeroed + timedelta(seconds=seconds)
        logger.debug("scheduling: {0}".format(t))
        return t

    def cancelTimer(self):
        if self.timer:
            self.timer.cancel()
            self.timer = None

    def _scheduleNextSwitch(self):
        self.cancelTimer()
        delta = self.getNextDecodingTime() - datetime.utcnow()
        self.timer = threading.Timer(delta.total_seconds(), self.switchFiles)
        self.timer.start()

    def switchFiles(self):
        with self.switchingLock:
            file = self.wavefile
            self.wavefile = self.getWaveFile()

        # if previously open file exists...
        if file is not None:
            file.close()
            tmp_dir = CoreConfig().get_temporary_directory()

            for profile in self.profiles:
                # create hardlinks for the individual profiles
                filename = "{tmp_dir}/openwebrx-audiochopper-{pid}-{timestamp}.wav".format(
                    tmp_dir=tmp_dir,
                    pid=id(profile),
                    timestamp=file.getTimestamp().strftime(profile.getFileTimestampFormat()),
                )
                try:
                    os.link(file.getFileName(), filename)
                except OSError:
                    logger.exception("Error while linking job files")
                    continue

                job = self.chopper.createJob(profile, filename)
                try:
                    DecoderQueue.getSharedInstance().put(job)
                except Full:
                    logger.warning("decoding queue overflow; dropping one file")
                    job.unlink()

            try:
                # our master can be deleted now, the profiles will delete their hardlinked copies after processing
                file.unlink()
            except OSError:
                logger.exception("Error while unlinking job files")

            self._scheduleNextSwitch()

    def start(self):
        self.wavefile = self.getWaveFile()
        self._scheduleNextSwitch()

    def write(self, data):
        with self.switchingLock:
            self.wavefile.writeframes(data)

    def stop(self):
        self.cancelTimer()
        try:
            self.wavefile.close()
        except Exception:
            logger.exception("error closing wave file")
        try:
            with self.switchingLock:
                self.wavefile.unlink()
        except Exception:
            logger.exception("error removing undecoded file")
        self.wavefile = None
