# -*- coding: utf-8 -*-
# vim: autoindent shiftwidth=4 expandtab textwidth=80 tabstop=4 softtabstop=4

###############################################################################
# OpenLP - Open Source Lyrics Projection                                      #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2011 Raoul Snyman                                        #
# Portions copyright (c) 2008-2011 Tim Bentley, Gerald Britton, Jonathan      #
# Corwin, Michael Gorven, Scott Guerrieri, Matthias Hub, Meinert Jordan,      #
# Armin Köhler, Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias     #
# Põldaru, Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,    #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Frode Woldsund             #
# --------------------------------------------------------------------------- #
# This program is free software; you can redistribute it and/or modify it     #
# under the terms of the GNU General Public License as published by the Free  #
# Software Foundation; version 2 of the License.                              #
#                                                                             #
# This program is distributed in the hope that it will be useful, but WITHOUT #
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or       #
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for    #
# more details.                                                               #
#                                                                             #
# You should have received a copy of the GNU General Public License along     #
# with this program; if not, write to the Free Software Foundation, Inc., 59  #
# Temple Place, Suite 330, Boston, MA 02111-1307 USA                          #
###############################################################################

import logging

from PyQt4 import QtWebKit

from openlp.core.lib.theme import BackgroundType, BackgroundGradientType, \
    VerticalType, HorizontalType

log = logging.getLogger(__name__)

# FIXME: Add html5 doctype. However, do not break theme gradients.
HTMLSRC = u"""
<html>
<head>
<title>OpenLP Display</title>
<style>
*{
    margin: 0;
    padding: 0;
    border: 0;
    overflow: hidden;
}
body {
    %s;
}
.size {
    position: absolute;
    left: 0px;
    top: 0px;
    width: %spx;
    height: %spx;
}
#black {
    z-index: 8;
    background-color: black;
    display: none;
}
#bgimage {
    z-index: 1;
}
#image {
    z-index: 2;
}
#video1 {
    z-index: 3;
}
#video2 {
    z-index: 3;
}
#alert {
    position: absolute;
    left: 0px;
    top: 0px;
    z-index: 10;
    %s
}
#footer {
    position: absolute;
    z-index: 6;
    %s
}
/* lyric css */
%s
sup {
    font-size: 0.6em;
    vertical-align: top;
    position: relative;
    top: -0.3em;
}
</style>
<script>
    var timer = null;
    var video_timer = null;
    var current_video = '1';
    var transition = %s;

    function show_video(state, path, volume, loop){
        // Note, the preferred method for looping would be to use the
        // video tag loop attribute.
        // But QtWebKit doesn't support this. Neither does it support the
        // onended event, hence the setInterval()
        // In addition, setting the currentTime attribute to zero to restart
        // the video raises an INDEX_SIZE_ERROR: DOM Exception 1
        // To complicate it further, sometimes vid.currentTime stops
        // slightly short of vid.duration and vid.ended is intermittent!
        //
        // Note, currently the background may go black between loops. Not
        // desirable. Need to investigate using two <video>'s, and hiding/
        // preloading one, and toggle between the two when looping.

        if(current_video=='1'){
            var vid = document.getElementById('video1');
            var vid2 = document.getElementById('video2');
        } else {
            var vid = document.getElementById('video2');
            var vid2 = document.getElementById('video1');
        }
        if(volume != null){
            vid.volume = volume;
            vid2.volume = volume;
        }
        switch(state){
            case 'init':
                vid.src = path;
                vid2.src = path;
                if(loop == null) loop = false;
                vid.looping = loop;
                vid2.looping = loop;
                vid.load();
                break;
            case 'load':
                vid2.style.visibility = 'hidden';
                vid2.load();
                break;
            case 'play':
                vid.play();
                vid.style.visibility = 'visible';
                if(vid.looping){
                    video_timer = setInterval(
                        function() {
                            show_video('poll');
                        }, 200);
                }
                break;
            case 'pause':
                if(video_timer!=null){
                    clearInterval(video_timer);
                    video_timer = null;
                }
                vid.pause();
                break;
            case 'stop':
                show_video('pause');
                vid.style.visibility = 'hidden';
                break;
            case 'poll':
                if(vid.ended||vid.currentTime+0.2>vid.duration)
                    show_video('swap');
                break;
            case 'swap':
                show_video('pause');
                if(current_video=='1')
                    current_video = '2';
                else
                    current_video = '1';
                show_video('play');
                show_video('load');
                break;
            case 'close':
                show_video('stop');
                vid.src = '';
                vid2.src = '';
                break;
        }
    }

    function show_image(src){
        var img = document.getElementById('image');
        img.src = src;
        if(src == '')
            img.style.display = 'none';
        else
            img.style.display = 'block';
    }

    function show_blank(state){
        var black = 'none';
        var lyrics = '';
        var pause = false;
        switch(state){
            case 'theme':
                lyrics = 'hidden';
                pause = true;
                break;
            case 'black':
                black = 'block';
                pause = true;
                break;
            case 'desktop':
                pause = true;
                break;
        }
        document.getElementById('black').style.display = black;
        document.getElementById('lyricsmain').style.visibility = lyrics;
        document.getElementById('image').style.visibility = lyrics;
        outline = document.getElementById('lyricsoutline')
        if(outline!=null)
            outline.style.visibility = lyrics;
        shadow = document.getElementById('lyricsshadow')
        if(shadow!=null)
            shadow.style.visibility = lyrics;
        document.getElementById('footer').style.visibility = lyrics;
        var vid = document.getElementById('video');
        if(vid.src != ''){
            if(pause)
                vid.pause();
            else
                vid.play();
        }
    }

    function show_alert(alerttext, position){
        var text = document.getElementById('alert');
        text.innerHTML = alerttext;
        if(alerttext == '') {
            text.style.visibility = 'hidden';
            return 0;
        }
        if(position == ''){
            position = getComputedStyle(text, '').verticalAlign;
        }
        switch(position)
        {
            case 'top':
                text.style.top = '0px';
                break;
            case 'middle':
                text.style.top = ((window.innerHeight - text.clientHeight) / 2)
                    + 'px';
                break;
            case 'bottom':
                text.style.top = (window.innerHeight - text.clientHeight)
                    + 'px';
                break;
        }
        text.style.visibility = 'visible';
        return text.clientHeight;
    }

    function show_footer(footertext){
        document.getElementById('footer').innerHTML = footertext;
    }

    function show_text(newtext){
        var match = /-webkit-text-fill-color:[^;\"]+/gi;
        if(timer != null)
            clearTimeout(timer);
        text_fade('lyricsmain', newtext);
        text_fade('lyricsoutline', newtext);
        text_fade('lyricsshadow', newtext.replace(match, ""));
        if(text_opacity()==1) return;
        timer = setTimeout(function(){
            show_text(newtext);
        }, 100);
    }

    function text_fade(id, newtext){
        /*
        Using -webkit-transition: opacity 1s linear; would have been preferred
        but it isn't currently quick enough when animating multiple layers of
        large areas of large text. Therefore do it manually as best we can.
        Hopefully in the future we can revisit and do more interesting
        transitions using -webkit-transition and -webkit-transform.
        However we need to ensure interrupted transitions (quickly change 2
        slides) still looks pretty and is zippy.
        */
        var text = document.getElementById(id);
        if(text==null) return;
        if(!transition){
            text.innerHTML = newtext;
            return;
        }
        if(newtext==text.innerHTML){
            text.style.opacity = parseFloat(text.style.opacity) + 0.3;
            if(text.style.opacity>0.7)
                text.style.opacity = 1;
        } else {
            text.style.opacity = parseFloat(text.style.opacity) - 0.3;
            if(text.style.opacity<=0.1){
                text.innerHTML = newtext;
            }
        }
    }

    function text_opacity(){
        var text = document.getElementById('lyricsmain');
        return getComputedStyle(text, '').opacity;
    }

    function show_text_complete(){
        return (text_opacity()==1);
    }
</script>
</head>
<body>
<img id="bgimage" class="size" %s />
<img id="image" class="size" %s />
<video id="video1" class="size" style="visibility:hidden" autobuffer preload>
</video>
<video id="video2" class="size" style="visibility:hidden" autobuffer preload>
</video>
%s
<div id="footer" class="footer"></div>
<div id="black" class="size"></div>
<div id="alert" style="visibility:hidden"></div>
</body>
</html>
"""

def build_html(item, screen, alert, islive, background, image=None):
    """
    Build the full web paged structure for display

    ``item``
        Service Item to be displayed

    ``screen``
        Current display information

    ``alert``
        Alert display display information

    ``islive``
        Item is going live, rather than preview/theme building

    ``background``
        Theme background image - bytes

    ``image``
        Image media item - bytes
    """
    width = screen[u'size'].width()
    height = screen[u'size'].height()
    theme = item.themedata
    webkitvers = webkit_version()
    # Image generated and poked in
    if background:
        bgimage_src = u'src="data:image/png;base64,%s"' % background
    elif item.bg_image_bytes:
        bgimage_src = u'src="data:image/png;base64,%s"' % item.bg_image_bytes
    else:
        bgimage_src = u'style="display:none;"'
    if image:
        image_src = u'src="data:image/png;base64,%s"' % image
    else:
        image_src = u'style="display:none;"'
    html = HTMLSRC % (build_background_css(item, width, height),
        width, height,
        build_alert_css(alert, width),
        build_footer_css(item, height),
        build_lyrics_css(item, webkitvers),
        u'true' if theme and theme.display_slide_transition and islive \
            else u'false',
        bgimage_src, image_src,
        build_lyrics_html(item, webkitvers))
    return html

def webkit_version():
    """
    Return the Webkit version in use.
    Note method added relatively recently, so return 0 if prior to this
    """
    try:
        webkitvers = float(QtWebKit.qWebKitVersion())
        log.debug(u'Webkit version = %s' % webkitvers)
    except AttributeError:
        webkitvers = 0
    return webkitvers

def build_background_css(item, width, height):
    """
    Build the background css

    ``item``
        Service Item containing theme and location information

    """
    width = int(width) / 2
    theme = item.themedata
    background = u'background-color: black'
    if theme:
        if theme.background_type == \
            BackgroundType.to_string(BackgroundType.Solid):
            background = u'background-color: %s' % theme.background_color
        else:
            if theme.background_direction == BackgroundGradientType.to_string \
                (BackgroundGradientType.Horizontal):
                background = \
                    u'background: ' \
                    u'-webkit-gradient(linear, left top, left bottom, ' \
                    'from(%s), to(%s))' % (theme.background_start_color,
                    theme.background_end_color)
            elif theme.background_direction == \
                BackgroundGradientType.to_string( \
                BackgroundGradientType.LeftTop):
                background = \
                    u'background: ' \
                    u'-webkit-gradient(linear, left top, right bottom, ' \
                    'from(%s), to(%s))' % (theme.background_start_color,
                    theme.background_end_color)
            elif theme.background_direction == \
                BackgroundGradientType.to_string \
                (BackgroundGradientType.LeftBottom):
                background = \
                    u'background: ' \
                    u'-webkit-gradient(linear, left bottom, right top, ' \
                    'from(%s), to(%s))' % (theme.background_start_color,
                    theme.background_end_color)
            elif theme.background_direction == \
                BackgroundGradientType.to_string \
                (BackgroundGradientType.Vertical):
                background = \
                    u'background: -webkit-gradient(linear, left top, ' \
                    u'right top, from(%s), to(%s))' % \
                    (theme.background_start_color, theme.background_end_color)
            else:
                background = \
                    u'background: -webkit-gradient(radial, %s 50%%, 100, %s ' \
                    u'50%%, %s, from(%s), to(%s))' % (width, width, width,
                    theme.background_start_color, theme.background_end_color)
    return background

def build_lyrics_css(item, webkitvers):
    """
    Build the lyrics display css

    ``item``
        Service Item containing theme and location information

    ``webkitvers``
        The version of qtwebkit we're using

    """
    style = u"""
.lyricstable {
    z-index: 5;
    position: absolute;
    display: table;
    %s
}
.lyricscell {
    display: table-cell;
    word-wrap: break-word;
    %s
}
.lyricsmain {
%s
}
.lyricsoutline {
%s
}
.lyricsshadow {
%s
}
    """
    theme = item.themedata
    lyricstable = u''
    lyrics = u''
    lyricsmain = u''
    outline = u''
    shadow = u''
    if theme and item.main:
        lyricstable = u'left: %spx; top: %spx;' % (item.main.x(), item.main.y())
        lyrics = build_lyrics_format_css(theme, item.main.width(),
            item.main.height())
        # For performance reasons we want to show as few DIV's as possible,
        # especially when animating/transitions.
        # However some bugs in older versions of qtwebkit mean we need to
        # perform workarounds and add extra divs. Only do these when needed.
        #
        # Before 533.3 the webkit-text-fill colour wasn't displayed, only the
        # stroke (outline) color. So put stroke layer underneath the main text.
        #
        # Up to 534.3 the webkit-text-stroke was sometimes out of alignment
        # with the fill, or normal text. letter-spacing=1 is workaround
        # https://bugs.webkit.org/show_bug.cgi?id=44403
        #
        # Up to 534.3 the text-shadow didn't get displayed when
        # webkit-text-stroke was used. So use an offset text layer underneath.
        # https://bugs.webkit.org/show_bug.cgi?id=19728
        if webkitvers >= 533.3:
            lyricsmain += build_lyrics_outline_css(theme)
        else:
            outline = build_lyrics_outline_css(theme)
        if theme.font_main_shadow:
            if theme.font_main_outline and webkitvers <= 534.3:
                shadow = u'padding-left: %spx; padding-top: %spx;' % \
                    (int(theme.font_main_shadow_size) +
                    (int(theme.font_main_outline_size) * 2),
                    theme.font_main_shadow_size)
                shadow += build_lyrics_outline_css(theme, True)
            else:
                lyricsmain += u' text-shadow: %s %spx %spx;' % \
                    (theme.font_main_shadow_color, theme.font_main_shadow_size,
                    theme.font_main_shadow_size)
    lyrics_css = style % (lyricstable, lyrics, lyricsmain, outline, shadow)
    return lyrics_css

def build_lyrics_outline_css(theme, is_shadow=False):
    """
    Build the css which controls the theme outline
    Also used by renderer for splitting verses

    ``theme``
        Object containing theme information

    ``is_shadow``
        If true, use the shadow colors instead
    """
    if theme.font_main_outline:
        size = float(theme.font_main_outline_size) / 16
        if is_shadow:
            fill_color = theme.font_main_shadow_color
            outline_color = theme.font_main_shadow_color
        else:
            fill_color = theme.font_main_color
            outline_color = theme.font_main_outline_color
        return u' -webkit-text-stroke: %sem %s; ' \
            u'-webkit-text-fill-color: %s; ' % (size, outline_color, fill_color)
    else:
        return u''

def build_lyrics_format_css(theme, width, height):
    """
    Build the css which controls the theme format
    Also used by renderer for splitting verses

    ``theme``
        Object containing theme information

    ``width``
        Width of the lyrics block

    ``height``
        Height of the lyrics block

    """
    align = HorizontalType.Names[theme.display_horizontal_align]
    valign = VerticalType.Names[theme.display_vertical_align]
    if theme.font_main_outline:
        left_margin = int(theme.font_main_outline_size) * 2
    else:
        left_margin = 0
    lyrics = u'white-space:pre-wrap; word-wrap: break-word; ' \
        'text-align: %s; vertical-align: %s; font-family: %s; ' \
        'font-size: %spt; color: %s; line-height: %d%%; margin: 0;' \
        'padding: 0; padding-left: %spx; width: %spx; height: %spx; ' % \
        (align, valign, theme.font_main_name, theme.font_main_size,
        theme.font_main_color, 100 + int(theme.font_main_line_adjustment),
        left_margin, width, height)
    if theme.font_main_outline:
        if webkit_version() <= 534.3:
            lyrics += u' letter-spacing: 1px;'
    if theme.font_main_italics:
        lyrics += u' font-style:italic; '
    if theme.font_main_bold:
        lyrics += u' font-weight:bold; '
    return lyrics

def build_lyrics_html(item, webkitvers):
    """
    Build the HTML required to show the lyrics

    ``item``
        Service Item containing theme and location information

    ``webkitvers``
        The version of qtwebkit we're using
    """
    # Bugs in some versions of QtWebKit mean we sometimes need additional
    # divs for outline and shadow, since the CSS doesn't work.
    # To support vertical alignment middle and bottom, nested div's using
    # display:table/display:table-cell are required for each lyric block.
    lyrics = u''
    theme = item.themedata
    if webkitvers <= 534.3 and theme and theme.font_main_outline:
        lyrics += u'<div class="lyricstable">' \
            u'<div id="lyricsshadow" style="opacity:1" ' \
            u'class="lyricscell lyricsshadow"></div></div>'
        if webkitvers < 533.3:
            lyrics += u'<div class="lyricstable">' \
                u'<div id="lyricsoutline" style="opacity:1" ' \
                u'class="lyricscell lyricsoutline"></div></div>'
    lyrics += u'<div class="lyricstable">' \
        u'<div id="lyricsmain" style="opacity:1" ' \
        u'class="lyricscell lyricsmain"></div></div>'
    return lyrics

def build_footer_css(item, height):
    """
    Build the display of the item footer

    ``item``
        Service Item to be processed.
    """
    style = u"""
    left: %spx;
    bottom: %spx;
    width: %spx;
    font-family: %s;
    font-size: %spt;
    color: %s;
    text-align: left;
    white-space: nowrap;
    """
    theme = item.themedata
    if not theme or not item.footer:
        return u''
    bottom = height - int(item.footer.y()) - int(item.footer.height())
    lyrics_html = style % (item.footer.x(), bottom,
        item.footer.width(), theme.font_footer_name,
        theme.font_footer_size, theme.font_footer_color)
    return lyrics_html

def build_alert_css(alertTab, width):
    """
    Build the display of the footer

    ``alertTab``
        Details from the Alert tab for fonts etc
    """
    style = u"""
    width: %spx;
    vertical-align: %s;
    font-family: %s;
    font-size: %spt;
    color: %s;
    background-color: %s;
    """
    if not alertTab:
        return u''
    align = VerticalType.Names[alertTab.location]
    alert = style % (width, align, alertTab.font_face, alertTab.font_size,
        alertTab.font_color, alertTab.bg_color)
    return alert
