# -*- coding: UTF-8 -*-

# Copyright (c) 2005-2010 Vasco Nunes
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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 Library 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
# 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

# You may use and distribute this software under the terms of the
# GNU General Public License, version 2 or later

from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.lib.styles import ParagraphStyle
from reportlab.platypus import (Paragraph, SimpleDocTemplate, PageBreak,
                                ParagraphAndImage, Image, Table, TableStyle)
from reportlab.lib.styles import getSampleStyleSheet, PropertySet
from reportlab.lib.pagesizes import A4
from xml.sax import saxutils
from gi.repository import Gtk
import os
import locale
import gutils
import version
from plugins.export import Base
from gettext import gettext as _
from dataclasses import dataclass
from typing import Any
from movie import Movie
from logging import getLogger

log = getLogger('Griffith')

MediaName = {
    1: "DVD", 2: "DVD-R", 3: "DVD-RW", 4: "DVD+R", 5: "DVD+RW", 6: "DVD-RAM",
    7: "CD", 8: "CD-RW", 9: "VCD", 10: "SVCD", 11: "VHS", 12: "BetaCAM",
    13: "LaserDisc", 14: "HD DVD", 15: "Bluray"
    }


@dataclass
class FontElements:
    filename_style: str = None
    font_name_style: str = None
    font_element_style: str = None


def determine_otitle_a_title(movie: Movie) -> tuple[str, str]:
    if movie.o_title:
        original_title = movie.o_title
    else:
        original_title = ''
    if movie.title:
        title = movie.title
    else:
        title = ''
    return original_title, title


class ExportPlugin(Base):
    name = "PDF"
    description = _("PDF export plugin (revised)")
    author = "Vasco Nunes (edited by Strit)"
    email = "<vasco.m.nunes@gmail.com> <strit@strits.dk>"
    version = "0.7"
    fields_to_export = ('number', 'o_title', 'title', 'director', 'genre',
                        'cast', 'plot', 'runtime', 'year', 'notes',
                        'poster_md5', 'medium_id')

    def initialize(self):
        self.font_name = ''
        self.determine_font()
        self.table_style = TableStyle(
            [('ALIGN', (0, 0), (-1, -1), 'LEFT'),
             ('VALIGN', (0, 0), (-1, -1), 'TOP'),
             ('FONT', (0, 0), (-1, -1), self.font_name),
             # ('GRID', (0, 0), (-1, -1), 0.25, colors.black),
             ])
        return True

    def _query_filename(self) -> tuple[bool, str]:
        create_file = False
        filename_path = None
        if self.config is not None:
            base_dir = self.config.get('export_dir', None, section='export-pdf')
        else:
            base_dir = '~'
        filename = "griffith_simple_list.pdf"
        menu_title = _("Export a PDF")
        filename_info = gutils.file_chooser(
            menu_title, action=Gtk.FileChooserAction.SAVE,
            buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                     Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
            name=filename, folder=base_dir)
        if filename_info and filename_info[0]:
            filename_path, base_dir = filename_info
            if self.config is not None and base_dir:
                section_name = 'export-pdf'
                self.config.set('export_dir', base_dir, section=section_name)
                self.config.save()
            create_file = True
            if os.path.isfile(filename_path):
                if gutils.question(
                        _("File exists. Do you want to overwrite it?"),
                        self.parent_window):
                    create_file = True
                else:
                    create_file = False
        return create_file, filename_path

    def run(self):
        """exports a simple movie list to a PDF file"""
        create_pdf_flag, pdf_filename = self._query_filename()
        if create_pdf_flag:
            doc_template = SimpleDocTemplate(
                pdf_filename, author='Griffith', title=_('Film List'),
                subject=_('Film List'), size=A4)
            fields = 'image,director,genre,cast'
            pdf_elements = self.config.get('pdf_elements', fields).split(',')
            self.create_styles()
            style = self.styles["Normal"]
            story = []

            # select sort column - FIXME
            sort_column_name = self.config.get(
                'sortby', 'number', section='mainlist')
            # TODO: check if below self.config.get statement has any effect
            self.config.get(
                'sortby_reverse', False, section='mainlist')
            do_grouping = True
            for field_name in sort_column_name.split(','):
                if field_name != 'title' and field_name != 'o_title':
                    do_grouping = False

            # build the query
            # TODO: check if the whole locale handling is still needed in Py3
            query = self.get_query()
            locale.setlocale(locale.LC_COLLATE, '')

            movies = self.db.session.execute(query).fetchall()
            movies.sort(
                key=lambda m: locale.strxfrm(
                    (m.title or m.o_title or '').lower()
                    )
                )
            self.create_pages_w_table(doc_template, do_grouping, movies,
                                      pdf_elements, story, self.table_style)
        gutils.info(_('PDF has been created.'), self.parent_window)

    @staticmethod
    def my_header_footer(canvas, doc):
        header_style1 = ParagraphStyle(
            name='HeaderStyle', fontSize=18, leading=1.2 * 18,
            fontName=doc.font_name,
            alignment=1,  # 0=Left, 1=Center, 2=Right
            )
        header_style2 = ParagraphStyle(
            name='HeaderStyle', fontSize=14, leading=1.2 * 14,
            fontName=doc.font_name,
            alignment=1,  # 0=Left, 1=Center, 2=Right
            )
        footer_style1 = ParagraphStyle(
            name='FooterStyle', fontSize=9, leading=10, alignment=1,
            fontName=doc.font_name)
        footer_style2 = ParagraphStyle(
            name='FooterStyle', fontSize=5, leading=7, alignment=1,
            fontName=doc.font_name)
        f_text = (f"Document generated by Griffith {version.pversion} - "
                  f"Copyright (C) {version.pyear}<br/>"
                  f"{saxutils.escape(version.pauthor)}<br/>"
                  f"Released Under the GNU/GPL License")

        footer_text1 = Paragraph(f"Page {doc.page}", footer_style1)
        footer_text2 = Paragraph(f_text, footer_style2)
        _w, _h = footer_text1.wrap(doc.width, doc.bottomMargin)
        footer_text1.drawOn(canvas, doc.leftMargin, doc.bottomMargin - 15)
        _w, _h = footer_text2.wrap(doc.width, doc.bottomMargin)
        footer_text2.drawOn(canvas, doc.leftMargin, doc.bottomMargin - 45)

        p1 = Paragraph(saxutils.escape(_("Film Liste")), header_style1)
        if doc.page == 1:
            p2 = Paragraph(
                saxutils.escape(
                    _(f"Total Number of Films: {str(doc.num_movies)}")),
                header_style2)
        else:
            p2 = Paragraph(saxutils.escape(_("")), header_style2)
        _width, _height = p1.wrap(doc.width, doc.topMargin)
        _width, _height = p2.wrap(doc.width, doc.topMargin - 5)
        header_margin = 15  # move above the lower header start position
        header_offset = doc.height + doc.bottomMargin + header_margin
        p1.drawOn(canvas, doc.leftMargin, header_offset + 15)
        p2.drawOn(canvas, doc.leftMargin, header_offset - 5)

    def create_pages_w_table(self, doc: SimpleDocTemplate, do_grouping: bool,
                             movies: list[Movie], pdf_elements: list[str],
                             story: list[Any], t_style):
        num_movies = len(movies)
        doc.num_movies = num_movies
        doc.font_name = self.font_name
        # p_style = ParagraphStyle(name='movie_info', vAlign='TOP', fontSize=8)
        num_rows = int(doc.height // 52)
        num_cols = 3
        movies.reverse()
        while movies:
            table_rows = []
            for row_num in range(num_rows):
                row = []
                for col_num in range(num_cols):
                    if movies:
                        movie = movies.pop()
                    else:
                        break
                    p = self.create_movie_info_w_image(movie, pdf_elements)
                    row.append(p)
                    _w, _h = p.wrap(doc.width, doc.topMargin)
                    # p.drawOn(doc, doc.leftMargin, doc.bottomMargin)
                table_rows.append(row)
            table = Table(table_rows, colWidths=(doc.width / num_cols),
                          rowHeights=52, style=t_style)
            story.append(table)
            story.append(PageBreak())
        doc.build(story, onFirstPage=self.my_header_footer,
                  onLaterPages=self.my_header_footer)

    def create_movie_info_w_image(self, movie: Movie, pdf_elements: list[str]) \
            -> ParagraphAndImage:
        paragraph_text = self.create_movie_information(movie)
        p = Paragraph(paragraph_text, self.styles['Normal'])
        p = self.combine_paragraph_a_poster(movie, p, pdf_elements)
        return p

    def combine_paragraph_a_poster(self, movie: Movie, p: Paragraph,
                                   pdf_elements: list[
                                       str]) -> ParagraphAndImage:
        if 'image' in pdf_elements:
            image_filename = ''
            if movie.poster_md5:
                image_filename = gutils.get_image_fname(
                    movie.poster_md5, self.db, 'm')
            if image_filename:
                p = ParagraphAndImage(
                    p, Image(image_filename, width=30, height=40), side='left')
                _width, _height = p.wrap(30, 40)
        return p

    @staticmethod
    def create_movie_information(movie: Movie) -> str:
        """add title, original title, year, runtime and medium information of
        the movie"""
        original_title, title = determine_otitle_a_title(movie)
        escaped_o_title = saxutils.escape(original_title)
        if movie.medium_id is None:
            media_name = ''
        else:
            media_name = MediaName[movie.medium_id]
        paragraph_text = (
            f"<b>{_('Title')}: </b>{saxutils.escape(title)}<br/>"
            f"<b>{_('Original Title')}: </b>{escaped_o_title}<br/>"
            f"<b>{_('Year')}: </b>{str(movie.year)}<br/>"
            f"<b>{_('Runtime')}: </b>{str(movie.runtime)}<br/>"
            f"<b>{_('Media')}: </b>{media_name}<br/>")
        return paragraph_text

    def handle_special_grouping_title(
            self, do_grouping: bool, first_letter: str, grouping_title: str,
            story: list[Any]):
        """AF: it is not yet clear which case is here handled

        Original comment is:
        group by first letter
        use movie.title/grouping_title for grouping because of encoding problems
        """
        if do_grouping and grouping_title[0] != first_letter:
            if grouping_title[0] in '0123456789':
                # Group Numbers
                if first_letter != '0-9':
                    first_letter = '0-9'
                    paragraph_text = saxutils.escape(first_letter)
                    p = Paragraph(paragraph_text, self.styles['Heading2'])
                    story.append(p)
            else:
                first_letter = grouping_title[0]
                paragraph_text = saxutils.escape(first_letter)
                p = Paragraph(paragraph_text, self.styles['Heading2'])
                story.append(p)

    def create_styles(self):
        self.styles = getSampleStyleSheet()
        self.determine_font()

        if self.config.get('font_size', '') != '':
            self.base_font_size = int(self.config.get('font_size'))
        else:
            self.base_font_size = 18
        title_font_size = self.base_font_size
        heading1_font_size = self.base_font_size - 8
        heading2_font_size = self.base_font_size - 3
        heading3_font_size = self.base_font_size - 11
        normal_font_size = self.base_font_size - 13
        if heading1_font_size < 4:
            heading1_font_size = 4
        if heading2_font_size < 4:
            heading2_font_size = 4
        if heading3_font_size < 4:
            heading3_font_size = 4
        if normal_font_size < 4:
            normal_font_size = 4

        # adjust font
        for (name, style) in list(self.styles.byName.items()):
            style.fontName = self.font_name

        # adjust font sizes
        self.styles['Title'].fontSize = title_font_size
        self.styles['Title'].leading = title_font_size + 2
        self.styles['Normal'].fontSize = normal_font_size
        self.styles['Normal'].leading = normal_font_size + 2
        self.styles['Heading1'].fontSize = heading1_font_size
        self.styles['Heading1'].leading = heading1_font_size + 2
        self.styles['Heading2'].fontSize = heading2_font_size
        self.styles['Heading2'].leading = heading2_font_size + 2
        self.styles['Heading3'].fontSize = heading3_font_size
        self.styles['Heading3'].leading = heading3_font_size + 2

    def determine_font(self):
        if self.config.get('font', '') != '':
            self.font_path = self.config.get('font', '')
            self.process_ttf_font()
        else:
            self.font_path = f"{self.config.top_path}/fonts/DejaVuSans.ttf"
            if os.path.isfile(self.font_path):
                self.process_ttf_font()
            else:
                self.font_name = "Helvetica"
                log.warning(f"Font not found at {self.font_name}, falling "
                            f"back to default font {self.font_name}")

    def process_ttf_font(self):
        """For custom fonts match italic, bolditalic and bold to the
        corresponding tags of Platypus"""
        font_styles = [
            FontElements('bold', 'bd', 'bold'),
            FontElements('bolditalic', 'bi', 'boldItalic'),
            FontElements('italic', 'i', 'italic'),
            FontElements('Bold', 'bd', 'bold'),
            FontElements('Italic', 'i', 'italic'),
            FontElements('Oblique', 'i', 'italic'),
            FontElements('BoldOblique', 'bi', 'boldItalic'),
            ]
        font_family_elements = {
            'normal': '', 'bold': '', 'boldItalic': '', 'italic': ''}
        fontmap = []

        font_path_base, font_filename_extension = (
            os.path.splitext(self.font_path))
        font_obj = TTFont('temp_name', self.font_path)
        self.font_name = font_obj.face.name
        font_to_add = TTFont(self.font_name, self.font_path)
        pdfmetrics.registerFont(font_to_add)
        fontmap.append(font_to_add)
        font_family_elements['normal'] = self.font_name

        # build font family if available to support <b> and <i>
        for font_style in font_styles:
            ttf_filename = (f"{font_path_base}-{font_style.filename_style}"
                            f"{font_filename_extension}")
            if os.path.isfile(ttf_filename):
                font_obj = TTFont('temp_name', ttf_filename)
                font_to_add = TTFont(font_obj.face.name, ttf_filename)
                pdfmetrics.registerFont(font_to_add)
                fontmap.append(font_to_add)
                font_family_elements[font_style.font_element_style] = (
                    font_obj.face.name)
        for key, value in font_family_elements.items():
            if not value:
                font_family_elements[key] = self.font_name
        pdfmetrics.registerFontFamily(self.font_name, **font_family_elements)
