''' Classes that represent audio clips. '''

from .base import Stim
from moviepy.audio.io.AudioFileClip import AudioFileClip

import os
import re
import subprocess


class AudioStim(Stim):

    ''' Represents an audio clip.

    Args:
        filename (str): Path to audio file.
        onset (float): Optional onset of the audio file (in seconds) with
            respect to some more general context or timeline the user wishes
            to keep track of.
        sampling_rate (int): Sampling rate of clip, in hertz.
        url (str): Optional url to read contents from.
        clip (AudioFileClip): Optional moviepy AudioFileClip to initialize
            from.
        order (int): Optional sequential index of the AudioStim within some
            containing context.

    '''

    _default_file_extension = '.wav'

    def __init__(self, filename=None, onset=None, sampling_rate=None, url=None,
                 clip=None, order=None):
        if url is not None:
            filename = url
        self.filename = filename

        self.sampling_rate = sampling_rate
        if not self.sampling_rate:
            self.sampling_rate = self.get_sampling_rate(self.filename)

        if clip:
            self.clip = clip
        else:
            self._load_clip()

        # Small default buffer isn't ideal, but moviepy has persistent issues
        # with some files otherwise; see
        # https://github.com/Zulko/moviepy/issues/246
        self.data = self.clip.to_soundarray(buffersize=1000)
        duration = self.clip.duration

        if self.data.ndim > 1:
            # Average channels to make data mono
            self.data = self.data.mean(axis=1)

        super(AudioStim, self).__init__(
            filename, onset=onset, duration=duration, order=order, url=url)

    @staticmethod
    def get_sampling_rate(filename):
        ''' Use FFMPEG to get the sampling rate, most of this code was
        adapted from the moviepy codebase '''
        cmd = ['ffmpeg', '-i', filename]

        with open(os.devnull, 'rb') as devnull:
            creationflags = 0x08000000 if os.name == 'nt' else 0
            p = subprocess.Popen(cmd,
                                 stdin=devnull,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE,
                                 creationflags=creationflags)

        _, p_err = p.communicate()
        del p

        lines = p_err.decode('utf8').splitlines()
        if 'No such file or directory' in lines[-1]:
            raise IOError(('Error: the file %s could not be found.\n'
                           'Please check that you entered the correct '
                           'path.') % filename)

        lines_audio = [l for l in lines if ' Audio: ' in l]

        if lines_audio:
            line = lines_audio[0]
            try:
                match = re.search(' [0-9]* Hz', line)
                return int(line[match.start() + 1:match.end() - 3])
            except Exception as e:
                pass

        # Return a sensible default
        return 44100

    def _load_clip(self):
        self.clip = AudioFileClip(self.filename, fps=self.sampling_rate)

    def __getstate__(self):
        d = self.__dict__.copy()
        d['clip'] = None
        return d

    def __setstate__(self, d):
        self.__dict__ = d
        self._load_clip()

    def save(self, path):
        ''' Save clip data to file.

        Args:
            path (str): Filename to save audio data to.
        '''
        self.clip.write_audiofile(path, fps=self.sampling_rate)
