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

# Copyright (c) 2008-2009 Piotr Ożarowski
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published byp
# 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

"""
The Dejavu font family has the following license agreement
https://github.com/dejavu-fonts/dejavu-fonts/blob/master/LICENSE
"""

import glob
import os.path
import gutils
from gi.repository import Gtk
import xml.dom.minidom
import os
from gettext import gettext as _
import logging
from sqlalchemy import select, join, outerjoin, and_, or_
import sqlalchemy
import sqlalchemy.sql.selectable
import db
from sql import update_whereclause

log = logging.getLogger("Griffith")

# detect all plugins:
__all__ = [os.path.basename(x)[:-3] for x in
           glob.glob("%s/PluginExport*.py" % os.path.dirname(__file__))]


class Base(object):
    description = None
    author = None
    email = None
    version = None
    fields_to_export = ('number', 'o_title', 'title', 'director', 'year')

    def __init__(self, database, locations, parent_window, search_conditions,
                 config):
        self.db = database
        self.config = config
        self.locations = locations
        self.parent_window = parent_window
        self.search_conditions = search_conditions
        self.exported_columns = []
        self.font_name = ''
        self.font_path = ''
        self.base_font_size = 0
        self.styles = None
        self.table_style = None

    def initialize(self):
        """Initializes plugin (get all parameters from user, etc.)"""
        return True

    def cleanup(self):
        pass

    def run(self):
        raise NotImplementedError

    def get_query(self) -> sqlalchemy.sql.selectable.Select:
        t = db.metadata.tables
        tables = set()
        columns = []

        for field in self.fields_to_export:
            table = 'movies'
            column = field.split('.')
            if len(column) > 1:
                table = column[0]
                column = column[1]
                if table not in t:
                    log.warning(f"Wrong table name: {table}")
                    continue
                tables.add(table)  # will be used to generate JOIN
            else:
                column = column[0]

            if column in t[table].columns:
                columns.append(t[table].columns[column])
            else:
                log.warning(f"Wrong field name: {field}")

        joins = []
        if 'media' in tables:
            joins.append(
                (t['media'], t['movies'].c.medium_id == t['media'].c.medium_id))
        if 'collections' in tables:
            joins.append((t['collections'], t['movies'].c.collection_id == t[
                'collections'].c.collection_id))
        if 'volumes' in tables:
            joins.append((t['volumes'],
                          t['movies'].c.volume_id == t['volumes'].c.volume_id))
        if 'vcodecs' in tables:
            joins.append((t['vcodecs'],
                          t['movies'].c.vcodec_id == t['vcodecs'].c.vcodec_id))

        if joins:
            from_obj = [outerjoin(t['movies'], *(joins[0]))]
            for j in joins[1:]:
                from_obj.append(outerjoin(from_obj[-1], *j))
            query = select(*columns).select_from(from_obj[-1])
        else:
            query = select(*columns)

        query = update_whereclause(query, self.search_conditions)

        # save column names (will contain 'movies_title' or 'title' depending
        # on how many tables were requested)
        self.exported_columns = [col.key for col in query.selected_columns]

        return query


class XmlExportBase(Base):

    def __init__(self, database, locations, parent_window, search_conditions,
                 config):
        Base.__init__(self, database, locations, parent_window,
                      search_conditions, config)
        self.db = database
        self.config_section = None
        self.export_name = ''
        self.filepath = ''
        self.dirpath = ''
        self.filename = 'export.xml'
        # self.encoding = 'windows-1252'
        self.encoding = 'utf-8'
        self.exported_movies = 0
        self.true_value = 'True'
        self.false_value = 'False'

    def run(self):
        if self.show_dialog():
            # create XML document
            impl = xml.dom.minidom.getDOMImplementation()
            doc = impl.createDocument(None, None, None)
            self.export_to_document(doc, doc.documentElement)
            # write XML document to file
            self.export_document_to_file(doc, self.filepath)
            gutils.info(_(f"{self.export_name} file has been created."),
                        self.parent_window)

    def show_dialog(self):
        # shows a file dialog and sets self.filepath
        # derived classes which overwrite this method have also to set
        # self.filepath
        basedir = None
        if self.config is not None and self.config_section is not None:
            basedir = self.config.get('export_dir', None,
                                      section=self.config_section)
        if basedir is None:
            filenames = gutils.file_chooser(
                _('Export a %s document') % self.export_name,
                action=Gtk.FileChooserAction.SAVE,
                buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                         Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
                name=self.filename)
        else:
            filenames = gutils.file_chooser(
                _('Export a %s document') % self.export_name,
                action=Gtk.FileChooserAction.SAVE,
                buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                         Gtk.STOCK_SAVE, Gtk.ResponseType.OK),
                name=self.filename, folder=basedir)
        if filenames[0]:
            self.filepath = filenames[0]
            if len(filenames) > 1:
                self.dirpath = filenames[1]
            else:
                self.dirpath = os.path.dirname(self.filepath)
            self.filename = os.path.basename(self.filepath)
            if (self.config is not None and self.dirpath and
                    self.config_section is not None):
                self.config.set('export_dir', self.dirpath,
                                section=self.config_section)
                self.config.save()
            overwrite = None
            if os.path.isfile(self.filepath):
                if gutils.question(
                        _('File exists. Do you want to overwrite it?'),
                        self.parent_window):
                    overwrite = True
                else:
                    overwrite = False

            if overwrite is True or overwrite is None:
                return True
        return False

    def export_document_to_file(self, document, filename):
        # write XML to file
        xml_document = document.toprettyxml(encoding=self.encoding)
        with open(filename, 'wb') as fp:
            fp.write(xml_document)

    def export_to_document(self, document, mainelement):
        table_movies = db.metadata.tables['movies']
        # create object
        columns = [table_movies.c.movie_id, table_movies.c.number,
                   table_movies.c.title, table_movies.c.o_title,
                   table_movies.c.country, table_movies.c.year,
                   table_movies.c.runtime, table_movies.c.classification,
                   table_movies.c.genre, table_movies.c.region,
                   table_movies.c.studio, table_movies.c.cast,
                   table_movies.c.director, table_movies.c.plot,
                   table_movies.c.notes, table_movies.c.loaned,
                   table_movies.c.rating, table_movies.c.trailer,
                   table_movies.c.image, table_movies.c.seen,
                   table_movies.c.media_num, table_movies.c.poster_md5,
                   table_movies.c.screenplay, table_movies.c.cameraman,
                   table_movies.c.barcode,
                   # use outer join to media table to get the name of the media
                   db.metadata.tables['media'].c.name
                   ]
        media_join = outerjoin(db.metadata.tables['movies'],
                               db.metadata.tables['media'],
                               db.metadata.tables['movies'].c.medium_id ==
                               db.metadata.tables['media'].c.medium_id)
        # use outer join to collections table to get the name of the collection
        columns.append(db.metadata.tables['collections'].c.name)
        collection_join = media_join.outerjoin(
            db.metadata.tables['collections'],
            db.metadata.tables['movies'].c.collection_id ==
            db.metadata.tables['collections'].c.collection_id)
        # use outer join to volumes table to get the name of the volume
        columns.append(db.metadata.tables['volumes'].c.name)
        volume_join = collection_join.outerjoin(
            db.metadata.tables['volumes'],
            db.metadata.tables['movies'].c.volume_id ==
            db.metadata.tables['volumes'].c.volume_id)
        # use outer join to volumes table to get the name of the video codec
        columns.append(db.metadata.tables['vcodecs'].c.name)
        vcodec_join = volume_join.outerjoin(
            db.metadata.tables['vcodecs'],
            db.metadata.tables['movies'].c.vcodec_id ==
            db.metadata.tables['vcodecs'].c.vcodec_id)
        # use outer join to posters table to get the poster image
        columns.append(db.metadata.tables['posters'].c.data)
        posters_join = vcodec_join.outerjoin(
            db.metadata.tables['posters'],
            db.metadata.tables['movies'].c.poster_md5 ==
            db.metadata.tables['posters'].c.md5sum)
        # fetch movie data
        moviesquery = select(*columns).select_from(posters_join)
        moviesquery = update_whereclause(moviesquery, self.search_conditions)
        self.process_movies(document, mainelement, moviesquery)

    def process_movies(self, document, mainelement, moviesquery):
        # process all movies by query
        self.exported_movies = 0
        keys = [col.key for col in moviesquery.selected_columns]
        result_obj = self.db.session.execute(moviesquery)
        result = result_obj.mappings().all()
        for movie in result:
            self.process_movie(document, mainelement, movie, keys)
            self.exported_movies += 1

    def process_movie(self, document, movieelement, movie, keys):
        # process the movie itself
        self.process_movie_values(document, movieelement, movie, keys)
        # process tags
        self.process_movie_tags(document, movieelement, movie)
        # process audio codecs
        self.process_movie_acodecs(document, movieelement, movie)
        # process languages
        self.process_movie_languages(document, movieelement, movie)
        # process subtitles
        self.process_movie_subtitles(document, movieelement, movie)

    def process_movie_values(self, document, movieelement, movie, keys):
        # process movie properties by key
        for key in keys:
            if not key == 'posters_data':
                value = self.convert_value(movie[key])
                self.process_movie_value(document, movieelement, key, value)
        self.process_movie_image(document, movieelement, movie['posters_data'],
                                 movie['movies_poster_md5'])

    def process_movie_value(self, document, movieelement, key, value):
        pass

    def process_movie_image(self, document, movieelement, imagedata,
                            imagemd5sum):
        pass

    #
    # export movie tags
    #
    def process_movie_tags(self, document, movieelement, movie):
        # build query for movie tags processing
        tagsquerycolumns = [db.metadata.tables['movie_tag'].c.movie_id,
                            db.metadata.tables['movie_tag'].c.tag_id,
                            db.metadata.tables['tags'].c.name]
        tag_join = join(db.metadata.tables['movie_tag'],
                        db.metadata.tables['tags'],
                        db.metadata.tables['movie_tag'].c.tag_id ==
                        db.metadata.tables['tags'].c.tag_id)
        tagsquery = select(*tagsquerycolumns).select_from(tag_join).where(
            db.metadata.tables['movie_tag'].c.movie_id == movie[
                'movies_movie_id']
            )
        self.process_tags(document, movieelement, tagsquery)

    def process_tags(self, document, movieelement, tagsquery):
        # process movie tags by query
        pass

    #
    # export movie audio codecs
    #
    def process_movie_acodecs(self, document, movieelement, movie):
        # build query for audio codec processing
        acodecscolumns = [db.metadata.tables['movie_lang'].c.movie_id,
                          db.metadata.tables['movie_lang'].c.type,
                          db.metadata.tables['movie_lang'].c.acodec_id,
                          db.metadata.tables['acodecs'].c.name]
        acodec_join = join(db.metadata.tables['movie_lang'],
                           db.metadata.tables['acodecs'],
                           db.metadata.tables['movie_lang'].c.acodec_id ==
                           db.metadata.tables['acodecs'].c.acodec_id)
        acodecsquery = select(*acodecscolumns).select_from(acodec_join).where(
            db.metadata.tables['movie_lang'].c.movie_id == movie[
                'movies_movie_id']
            )
        self.process_acodecs(document, movieelement, acodecsquery)

    def process_acodecs(self, document, movieelement, acodecs):
        # process audio codecs by query
        pass

    #
    # export movie languages
    #
    def process_movie_languages(self, document, movieelement, movie):
        # build query for movie languages processing
        languagecolumns = [db.metadata.tables['movie_lang'].c.movie_id,
                           db.metadata.tables['movie_lang'].c.type,
                           db.metadata.tables['movie_lang'].c.lang_id,
                           db.metadata.tables['movie_lang'].c.acodec_id,
                           db.metadata.tables['languages'].c.name,
                           db.metadata.tables['acodecs'].c.name]
        language_join = join(db.metadata.tables['movie_lang'],
                             db.metadata.tables['languages'],
                             db.metadata.tables['movie_lang'].c.lang_id ==
                             db.metadata.tables['languages'].c.lang_id)
        acodec_join = language_join.join(
            db.metadata.tables['acodecs'],
            db.metadata.tables['movie_lang'].c.acodec_id == db.metadata.tables[
                'acodecs'].c.acodec_id)
        languagesquery = (
            select(*languagecolumns)
            .select_from(acodec_join)
            .where(
                and_(
                    db.metadata.tables['movie_lang'].c.movie_id
                    == movie['movies_movie_id'],
                    or_(
                        db.metadata.tables['movie_lang'].c.type == 0,
                        or_(
                            db.metadata.tables['movie_lang'].c.type == 2,
                            db.metadata.tables['movie_lang'].c.type.is_(None),
                            ),
                        ),
                    )
                )
            )
        self.process_languages(document, movieelement, languagesquery)

    def process_languages(self, document, movieelement, languages):
        # process movie languages by query
        pass

    #
    # export movie subtitles
    #
    def process_movie_subtitles(self, document, movieelement, movie):
        # build query for movie subtitles processing
        languagescolumns = [db.metadata.tables['movie_lang'].c.movie_id,
                            db.metadata.tables['movie_lang'].c.type,
                            db.metadata.tables['movie_lang'].c.lang_id,
                            db.metadata.tables['languages'].c.name]
        language_join = join(db.metadata.tables['movie_lang'],
                             db.metadata.tables['languages'],
                             db.metadata.tables['movie_lang'].c.lang_id ==
                             db.metadata.tables['languages'].c.lang_id)
        languagesquery = (
            select(*languagescolumns)
            .select_from(language_join)
            .where(
                and_(
                    db.metadata.tables['movie_lang'].c.movie_id
                    == movie['movies_movie_id'],
                    db.metadata.tables['movie_lang'].c.type == 3,
                    )
                )
            )
        self.process_subtitles(document, movieelement, languagesquery)

    def process_subtitles(self, document, movieelement, subtitlesquery):
        # process movie subtitles by query
        pass

    #
    # helper methods
    #

    def save_image_to_file(self, imagedata, imagebasename, destinationdir):
        # save image image to destination directory
        # build absolute path from current export directory if necessary
        # create picture directory if necessary
        if not os.path.isabs(destinationdir):
            dst_directory = os.path.abspath(
                os.path.join(self.dirpath, destinationdir))
        else:
            dst_directory = os.path.abspath(destinationdir)
        if not os.path.exists(dst_directory):
            try:
                os.mkdir(dst_directory)
            except Exception as ex:
                log.error(f"Can't create {dst_directory} [type(ex).__name__]: "
                          f"'{ex.args}'")
        dst_filename = os.path.join(dst_directory, imagebasename + '.jpg')
        try:
            with open(dst_filename, 'wb') as fp:
                fp.write(imagedata)
        except Exception as ex:
            log.error(f"Can't save image data to {dst_filename} "
                      f"[type(ex).__name__]: '{ex.args}'")

    def convert_value(self, value):
        if value is None:
            result = ''
        elif value is True:
            result = self.true_value
        elif value is False:
            result = self.false_value
        else:
            try:
                result = str(value).encode(self.encoding).decode(self.encoding)
            except Exception as exc:
                log.error(f"Can't convert value {value} to codepage "
                          f"{self.encoding} [type(exc).__name__]: '{exc.args}'")
                result = ''
        return result
