# -*- coding: utf-8 -*-

# core.py
# This file is part of qarte-4
#    
# Author: Vincent Vande Vyvre <vincent.vandevyvre@oqapy.eu>
# Copyright: 2016-2023 Vincent Vande Vyvre
# Licence: GPL3
# Home page: https://launchpad.net/qarte
#
# Daemon used for deferred downloading

import sys
import os
import time
import json
import contextlib
import urllib.request
import logging
lgg = logging.getLogger(__name__)

from urllib.error import URLError
from html.parser import HTMLParser

from data import ARTE_TV


class Daemon:
    def __init__(self, index):
        lgg.info('Qarte daemon run task %s' % index)
        self.paths = self.set_environment()
        self.cfg = self.get_config()
        self.load_tasks()
        self.configure(index)

    def set_environment(self):
        """Define the workspace.

        Returns:
        list of paths
        """
        user = os.path.expanduser("~")
        paths = {}
        paths['user'] = os.path.join(user, ".Qarte")
        paths['config'] = os.path.join(paths['user'], "user_config")
        paths['tasks'] = os.path.join(paths['user'], 'tasks.json')
        paths['tv_summaries'] = os.path.join(paths['user'], "tv_summaries")
        paths['tv_index'] = os.path.join(paths['user'], "tv_index")
        paths['tv_videos_data'] = os.path.join(paths['user'], "tv_videos_data")
        return paths

    def get_config(self):
        """Return the user's configuration.

        """
        path = self.paths['config']
        lgg.info('Load config from: %s' % path)
        try:
            with open(path, 'r') as objfile:
                cnt = objfile.read()
                return json.loads(cnt)
        except Exception as why:
            lgg.warning("Read error from: {0},".format(path))
            lgg.warning("reason: {0}.".format(why))
            sys.exit()

    def load_tasks(self):
        """Read the task file.

        """
        path = self.paths['tasks']
        if os.path.isfile(path):
            lgg.info('Load tasks file: %s' % path)
            try:
                with open(path, 'r') as objfile:
                    cnt = objfile.read()
                    self.tasks = json.loads(cnt)
            except Exception as why:
                lgg.warning("Read error from: {0},".format(path))
                lgg.warning("reason: {0}.".format(why))
                sys.exit()

        else:
            lgg.warning("Tasks file %s not found." % path)
            sys.exit()

    def configure(self, idx):
        """Prepare the current task.

        """
        if not idx in self.tasks:
            lgg.info("Task %s not found in tasks file." % idx)
            sys.exit()

        urls = False
        task = self.tasks[idx]
        type_ = task['type']
        if type_ in [0, 3]:
            tmp = self.download(task['stream'], type_)
            if tmp:
                dest = self.get_filename(task['title'], task['fname'], type_)
                os.rename(tmp, dest)
                lgg.info('File %s saved, exit.' % dest)

        elif task['type'] == 1:
            videos = self.search_with_serial_name(task)
            if videos:
                urls = self.find_urls(videos)

        elif task['type'] == 2:
            self.titles = []
            videos = self.search_with_kwords(task)
            if videos:
                urls = self.find_urls(videos)

        if urls:
            lgg.info('Found %s streams' % len(urls))
            if not task['all']:
                urls = [urls[0]]

            for url in urls:
                tmp = self.download(url)
                if tmp:
                    if task['type'] == 2:
                        name = self.titles.pop(0)

                    else:
                        name = task['title']

                    dest = self.get_filename(name)
                    os.rename(tmp, dest)
                    lgg.info('File %s saved.' % dest)
        sys.exit()

    def search_with_serial_name(self, item):
        """Try to find one or more video with the title.

        Returns:
        list of videos
        """
        lgg.info('Search for %s' % item['title'])
        title = item['title'].lower()
        all_ = item['all']
        parser = Parser()
        videos = []
        for url in self.get_site_pages()[:8]:
            content = self.fetch_page(url)
            if content:
                parser.feed(content)

            if parser.videos:
                for v in parser.videos:
                    if v['title'].lower() == title:
                        videos.append(v)

                if videos and not all_:
                    break
            parser.videos = []

        return videos

    def search_with_kwords(self, item):
        """Try to find one or more video with the key words.

        Returns:
        list of videos
        """
        lgg.info('Search for %s' % item['kwords'])
        kwrd = item['kwords']
        all_ = item['all']
        parser = Parser()
        videos = []
        for url in self.get_site_pages()[:8]:
            content = self.fetch_page(url)
            if content:
                parser.feed(content)

            if parser.videos:
                for v in parser.videos:
                    t = v['title'].lower()
                    valid = True
                    for k in item['kwords']:
                        if k not in t:
                            valid = False
                            break

                    if valid:
                        videos.append(v)
                        self.titles.append(v['title'])

                if videos and not all_:
                    break
            parser.videos = []

        return videos

    def find_urls(self, items):
        """Return the list of video's urls for the desired quality.

        """
        urls = []
        quality = self.cfg['tv_quality']
        for item in items:
            streams = self.get_stream_urls(item)
            if streams and quality in streams:
                urls.append(streams[quality]['url'])

        return urls

    def get_stream_urls(self, item):
        """Get the list of the stream's urls of a video.

        Args:
        item -- TVItem instance
        """ 
        lgg.info('Get stream URLs for: {0}'.format(item['url']))
        items = item['url'].split('/')
        idx = items.index(self.cfg['lang'])
        base = 'https://api.arte.tv/api/player/v1/config'
        plat = '?platform=ARTEPLUS7'
        lng = '&lang=%s_%s' %(self.cfg['lang'], self.cfg['lang'].upper())
        lnk = '%s/%s/%s%s%s&config=arte_tvguide' %(base, self.cfg['lang'], 
                                                   items[idx+1], plat, lng)
        links = self.fetch_page(lnk)
        if links:
            return self.read_streams(links)

    def read_streams(self, content):
        """Un-json the list of video's streams.

        Args:
        content -- str(web page)
        """
        try:
            dct = json.loads(content)
            return dct["videoJsonPlayer"]['VSR']
        except Exception as why:
            lgg.info("Can't read the stream urls: %s" % why)
            return False

    def fetch_page(self, url):
        """Fetch a html or xml page.

        Args:
        url -- page URL

        Returns:
        page as text or False if fail
        """
        lgg.info('Load page: %s' % url)
        try:
            content = urllib.request.urlopen(url).read()
            return str(content.decode('utf-8', 'replace'))
        except Exception as why:
            lgg.info('urllib2 error: %s, %s' % (url, why))
            return False

    def download(self, url, concert=0):
        path = self.get_temp_filename(concert)
        try:
            with contextlib.closing(urllib.request.urlopen(url, None)) as fp:
                bs = 1024*16
                with open(path, 'wb') as target:
                    while True:
                        block = fp.read(bs)
                        if not block:
                            break

                        target.write(block)
        except URLError as why:
            lgg.info('Download error: %s ' % why)
            return False

        return path

    def get_temp_filename(self, music):
        fld = self.cfg['videos_folder']
        if music:
            fld = self.cfg['music_folder']
        d = str(time.time()).replace(".", "")
        t = "".join(["tv", d])
        count = 1
        while 1:
            target = os.path.join(fld, t)
            if os.path.isfile(target):
                t = "".join(["tv", d, str(count)])
                count += 1

            else:
                return target

    def get_filename(self, title, fname=None, music=None):
        fld = self.cfg['videos_folder']
        if music:
            fld = self.cfg['music_folder']

        if fname is None:
            fname = title

        count = 1
        target = os.path.join(fld, fname) + '.mp4'
        while 1: 
            if os.path.isfile(target):
                target = os.path.join(fld, fname) + '(%s)' % count + '.mp4'
                count += 1            
 
            else:
                return target

    def get_site_pages(self):
        """Returns the list of the arte TV Guide web pages.

        """
        if self.cfg['lang'] == 'fr':
            return ARTE_TV

        return [l.replace('/fr/', '/de/') for l in ARTE_TV]   

class Parser(HTMLParser):
    """Define the parser of arte TV Guide's pages.

    """
    def __init__(self):
        super().__init__()
        self.videos = []
        self.inscript = False

    def handle_starttag(self, tag, attrs):
        if tag == "script":
            self.inscript = True

    def handle_endtag(self, tag):
        if tag == "script":
            self.inscript = False

    def handle_data(self, data):
        if self.inscript:
            if "videoSet:" in data:
                jsn = data.split("videoSet:")[1].split("clusters")[0]
                jsn = jsn.strip().rstrip(',')
                self.read_videos_set(jsn)

    def read_videos_set(self, txt):
        try:
            dct = json.loads(txt)
            self.videos = dct["videos"]
        except Exception as why:
            lgg.info('Error reading json: %s' % why)


if __name__ == '__main__':
    d = Daemon("002")

