#!/usr/bin/env python3

import json
import logging

import jinja2

from os import listdir
from os.path import join

from tornado.ioloop import IOLoop
from tornado.options import define, options
from tornado.web import Application, RequestHandler
from tornado.websocket import WebSocketHandler

from tornado_jinja2 import Jinja2Loader
from requests.exceptions import RequestException

from owltail.qrcode_reader import QRCodeReader
from owltail.api_handler import APIHandler
from owltail.wifi_handler import get_wifi_networks, connect
from owltail.player import Player, PLAY_FROM_INPUT
from owltail import settings
from owltail.configuration import Configurer
from owltail.synchro import SongsSynchroniser


define('debug', default=False, help='debug mode')
define('host', default='localhost', help='server host')
define('port', default=8888, help='server port')
define('secret', default='secret', help='secret key')


logger = logging.getLogger(__name__)


DEFAULT_PLAYLISTS = {
    'NAP': {'display': 'Nap', 'fa_icon': 'moon-o'},
    'RELAXATION': {'display': 'Relaxation', 'fa_icon': 'glass'},
    'DISCOVERY': {'display': 'Discovery', 'fa_icon': 'question'},
    'JOURNEY': {'display': 'Journey', 'fa_icon': 'globe'},
    'ENERGISING': {
        'display': 'Energising',
        'fa_icon': 'battery-three-quarters'
    }
}


class OwltailWebsocketApp(Application):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.websockets = []

        self.api = APIHandler()
        self.qr_reader = QRCodeReader()
        self.player = Player(self)

        self.config = self.parse_config()

        self.superadmin = None
        self.user = None
        self.playing = False
        self.themes = self.get_themes()

        self.read_code()
        IOLoop.current().call_later(300, self.sync)

    @property
    def qr_enabled(self):
        return self.config['qr_codes_enabled']

    def read_code(self):
        code = self.qr_reader.read()
        if code:
            self.on_qrcode(code)

        IOLoop.current().call_later(0.2, self.read_code)

    def sync(self):
        old_config = self.config.copy()
        old_themes = self.themes.copy()

        Configurer().start()
        SongsSynchroniser().start()

        self.config = self.parse_config()
        self.themes = self.get_themes()

        should_refresh = (
            (self.config != old_config or self.themes != old_themes) and
            not self.playing and
            not self.superadmin
        )
        if should_refresh:
            self.send_message({'operation': 'REFRESH'})

        IOLoop.current().call_later(300, self.sync)

    def get_themes(self):
        result = []
        dirs = listdir(settings.DEFAULT_SONGS_FOLDER)
        for directory in dirs:
            if directory in DEFAULT_PLAYLISTS:
                theme = DEFAULT_PLAYLISTS[directory]
            else:
                theme = {'display': directory, 'fa_icon': 'music'}
            theme['code'] = directory
            result.append(theme)
        return result

    def parse_config(self):
        with open(settings.CONFIG_FILE_PATH, 'r') as conf_file:
            return json.loads(conf_file.read())

    def on_qrcode(self, code):
        if self.playing:
            return
        if code == settings.ADMIN_TOKEN:
            self.superadmin = True
            self.send_message({'operation': 'REFRESH'})
            return
        if self.qr_enabled and not self.user and not self.superadmin:
            try:
                self.user = self.api.check_qrcode_validity(code)
                self.send_message({'operation': 'REFRESH'})
                return
            except RequestException:
                return

    def send_message(self, message):
        for ws in self.websockets:
            ws.write_message(message)

    def stop(self, refresh=False):
        self.playing = False
        self.user = None
        self.player.playlist_stop()
        if refresh:
            self.send_message({'operation': 'REFRESH'})

    def play(self, source, duration):
        self.playing = True
        self.player.playlist_start(source)
        IOLoop.current().call_later(
            duration * 60 - settings.FADE_OUT_DURATION, self.fade_out
        )

    def fade_out(self):
        self.player.fade_out()
        self.stop(refresh=True)

    def reset(self):
        self.stop()
        self.superadmin = None

    def next(self):
        if not self.playing:
            return
        if self.player.source == PLAY_FROM_INPUT:
            return
        self.player.play_next_song()

    def previous(self):
        if not self.playing:
            return
        if self.player.source == PLAY_FROM_INPUT:
            return
        self.player.play_previous_song()

    def change_volume(self, name, value):
        self.player.set_volume(value)


class WebSocket(WebSocketHandler):
    def check_origin(self, origin):
        return True

    def open(self):
        self.application.websockets.append(self)

    def on_close(self):
        self.application.websockets.remove(self)

    def on_message(self, message):
        if self.application.playing:
            m = json.loads(message)
            if 'name' in m and 'value' in m:
                self.application.change_volume(m['name'], int(m['value']))
        for ws in self.application.websockets:
            if ws == self:
                continue
            ws.write_message(message)


class ResetHandler(RequestHandler):
    def get(self):
        self.application.reset()
        self.redirect('/')


class StopHandler(RequestHandler):
    def get(self):
        self.application.stop()
        self.redirect('/')


class PlayHandler(RequestHandler):
    def post(self):
        self.application.play(
            self.get_argument('source'),
            int(self.get_argument('duration')),
        )
        self.redirect('/')


class PreviousHandler(RequestHandler):
    def get(self):
        self.application.previous()
        self.redirect('/')


class NextHandler(RequestHandler):
    def get(self):
        self.application.next()
        self.redirect('/')


class WifiHandler(RequestHandler):
    def post(self):
        result, message = connect(
            self.get_argument('ssid'),
            self.get_argument('password'),
        )
        if result:
            self.application.reset()
            self.redirect('/')
            return
        self.render(
            'wifi_config.html',
            networks=get_wifi_networks(),
            error=True,
            message=message,
        )


class IndexHandler(RequestHandler):
    def get(self):
        if self.application.superadmin:
            self.render('wifi_config.html', networks=get_wifi_networks())
            return

        if self.application.qr_enabled and not self.application.user:
            self.render('qrcode-wait.html')
            return

        if self.application.playing:
            track_infos = self.application.player.current_track_infos()
            artist, title, picture_data, picture_mime = track_infos
            self.render(
                'playing.html',
                artist=artist,
                title=title,
                picture_data=picture_data,
                picture_mime=picture_mime,
                source=self.application.player.source,
            )
            return
        self.render(
            'selection.html', max_time=60, themes=self.application.themes
        )


if __name__ == '__main__':
    jinja2_env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(join(
            settings.BASE_DIR, 'owltail', 'templates'
        )),
        autoescape=False
    )
    jinja2_loader = Jinja2Loader(jinja2_env)
    options.parse_command_line()
    app = OwltailWebsocketApp(
        handlers=[
            (r"/websocket", WebSocket),
            (r'/reset/', ResetHandler),
            (r'/play/', PlayHandler),
            (r'/stop/', StopHandler),
            (r'/previous/', PreviousHandler),
            (r'/next/', NextHandler),
            (r'/wifi/', WifiHandler),
            (r'^/$', IndexHandler),
        ],
        debug=options.debug,
        cookie_secret=options.secret,
        template_loader=jinja2_loader,
        static_path=join(settings.BASE_DIR, 'owltail', 'static'),
    )
    app.listen(options.port)

    IOLoop.current().start()
