#!/usr/bin/env python

import logging
import time

import gi
import stagger

from os import listdir
from os.path import join
from random import shuffle
from base64 import b64encode

from tornado.process import Subprocess

from owltail import settings


PLAY_FROM_INPUT = 'WIRE'

logger = logging.getLogger(__name__)


def call(l):
    return Subprocess(['sudo'] + l)


def import_gst_gobject():
    gi.require_version('Gst', '1.0')
    from gi.repository import Gst, GObject
    Gst.init(None)
    return Gst, GObject


Gst, GObject = import_gst_gobject()


class Player:
    def __init__(self, app):
        super().__init__()

        self.app = app

        self.playlist = []
        self.index = None
        self.source = None
        self.loopback_module_loaded = False
        self.started = False

        self.volume = None
        self.filter = None
        self.low = None

    def get_playlist(self):
        source_path = join(settings.DEFAULT_SONGS_FOLDER, self.source)
        files = listdir(source_path)
        if not files:
            return None
        shuffle(files)
        return [join(source_path, filename) for filename in files]

    def play_previous_song(self, *args, **kwargs):
        if self.index is None:
            self.index = 0
        if not self.playlist:
            self.app.stop(refresh=True)
            return

        self.index -= 1
        if self.index < 0:
            self.index = len(self.playlist) - 1
        self.play()

    def play_next_song(self, *args, **kwargs):
        if self.index is None:
            self.index = 0
        if not self.playlist:
            self.app.stop(refresh=True)
            return

        self.index += 1
        if self.index >= len(self.playlist):
            self.index = 0
        self.play()

    def play(self):
        track_path = self.playlist[self.index]
        if getattr(self, 'player', None):
            self.player.set_state(Gst.State.NULL)

        self.player = Gst.ElementFactory.make("playbin", "player")
        fakesink = Gst.ElementFactory.make("fakesink", "fakesink")
        self.player.set_property("video-sink", fakesink)
        self.player.connect("about-to-finish", self.play_next_song)

        self.player.set_property("uri", "file://" + track_path)
        self.player.set_property("volume", 1)
        self.player.set_state(Gst.State.PLAYING)

        artist, title, picture_data, picture_mime = self.current_track_infos()
        self.app.send_message({
            'operation': 'TRACK_CHANGE',
            'artist': artist,
            'title': title,
            'picture_data': picture_data,
            'picture_mime': picture_mime,
        })

    def playlist_stop(self):
        self.set_volume(0)
        self.set_low(0)

        if hasattr(self, 'player'):
            self.player.set_state(Gst.State.NULL)
            self.player.set_property("volume", 0)

        self.source = None
        self.index = None
        self.playlist = []

        self.volume = None
        self.low = None
        self.filter = None

    def playlist_start(self, source):
        self.source = source

        for i in range(settings.PLAYER_VOLUME_START + 1):
            self.set_volume(i)
            self.set_low(i)
            time.sleep(0.1)

        self.set_filter(settings.PLAYER_FILTER_START)

        if self.source == PLAY_FROM_INPUT:
            self.play_from_audio_input()
        else:
            self.play_from_playlist()

    def play_from_playlist(self):
        call([settings.PLAYER_WRITE_MUX_CMD, str(settings.PLAYER_RPI_IDX)])

        self.playlist = self.get_playlist()
        self.play_next_song()

    def play_from_audio_input(self):
        call([settings.PLAYER_WRITE_MUX_CMD, str(settings.PLAYER_LINEIN_IDX)])

    def fade_out(self):
        volume = self.volume
        low = self.low
        for i in range(settings.FADE_OUT_STEP):
            self.set_volume(int(volume * (1 - i / settings.FADE_OUT_STEP)))
            self.set_low(int(low * (1 - i / settings.FADE_OUT_STEP)))
            self.app.send_message({
                'operation': 'VOLUME',
                'volume': volume * (1 - i / settings.FADE_OUT_STEP)
            })
            self.app.send_message({
                'operation': 'LOW',
                'volume': low * (1 - i / settings.FADE_OUT_STEP)
            })
            time.sleep(settings.FADE_OUT_DURATION / settings.FADE_OUT_STEP)
        self.app.stop(refresh=True)

    def current_track_infos(self):
        if self.index is None or not self.playlist:
            return ('', '', '', '')
        track_path = self.playlist[self.index]
        try:
            tag = stagger.read_tag(track_path)

            picture_data = ''
            picture_mime = ''
            if stagger.id3.APIC in tag:
                picture_data = (
                    b64encode(tag[stagger.id3.APIC][0].data).decode()
                )
                picture_mime = tag[stagger.id3.APIC][0].mime
            elif stagger.id3.PIC in tag:
                picture_data = b64encode(tag[stagger.id3.PIC][0].data).decode()
                picture_mime = tag[stagger.id3.PIC][0].mime
            return (tag.artist, tag.title, picture_data, picture_mime)
        except:
            logger.warning('Could not read tag of file ' + track_path)
            return ('', '', '', '')

    def set_volume(self, value):
        self.volume = value
        for idx in settings.VOLUME_INDEXES:
            call([settings.PLAYER_SET_GAIN_CMD, str(idx), str(value)])

    def set_low(self, value):
        self.low = value
        call([
            settings.PLAYER_SET_GAIN_CMD, str(settings.LOW_INDEX), str(value)
        ])

    def set_filter(self, value):
        self.filter = value
        call([settings.PLAYER_SET_FILTER_CMD, str(int(
            (settings.FILTER_MAX - settings.FILTER_MIN) * value / 100 +
            settings.FILTER_MIN
        ))])
