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

###############################################################################
# OpenLP - Open Source Lyrics Projection                                      #
# --------------------------------------------------------------------------- #
# Copyright (c) 2008-2014 Raoul Snyman                                        #
# Portions copyright (c) 2008-2014 Tim Bentley, Gerald Britton, Jonathan      #
# Corwin, Samuel Findlay, Michael Gorven, Scott Guerrieri, Matthias Hub,      #
# Meinert Jordan, Armin Köhler, Erik Lundin, Edwin Lunando, Brian T. Meyer.   #
# Joshua Miller, Stevan Pettit, Andreas Preikschat, Mattias Põldaru,          #
# Christian Richter, Philip Ridout, Simon Scudder, Jeffrey Smith,             #
# Maikel Stuivenberg, Martin Thompson, Jon Tibble, Dave Warnock,              #
# Frode Woldsund, Martin Zibricky                                             #
# --------------------------------------------------------------------------- #
# 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
import re
import shutil
import os

from PyQt4 import QtCore

from openlp.core.lib import Receiver, translate, check_directory_exists
from openlp.core.ui.wizard import WizardStrings
from openlp.core.utils import AppLocation
from openlp.plugins.songs.lib import clean_song, VerseType
from openlp.plugins.songs.lib.db import Song, Author, Topic, Book, MediaFile
from openlp.plugins.songs.lib.ui import SongStrings
from openlp.plugins.songs.lib.xml import SongXML

log = logging.getLogger(__name__)

class SongImport(QtCore.QObject):
    """
    Helper class for import a song from a third party source into OpenLP

    This class just takes the raw strings, and will work out for itself
    whether the authors etc already exist and add them or refer to them
    as necessary
    """
    @staticmethod
    def isValidSource(import_source):
        """
        Override this method to validate the source prior to import.
        """
        return True

    def __init__(self, manager, **kwargs):
        """
        Initialise and create defaults for properties

        ``manager``
            An instance of a SongManager, through which all database access is
            performed.

        """
        self.manager = manager
        QtCore.QObject.__init__(self)
        if u'filename' in kwargs:
            self.importSource = kwargs[u'filename']
        elif u'filenames' in kwargs:
            self.importSource = kwargs[u'filenames']
        elif u'folder' in kwargs:
            self.importSource = kwargs[u'folder']
        else:
            raise KeyError(
                u'Keyword arguments "filename[s]" or "folder" not supplied.')
        log.debug(self.importSource)
        self.importWizard = None
        self.song = None
        self.stopImportFlag = False
        self.setDefaults()
        QtCore.QObject.connect(Receiver.get_receiver(),
            QtCore.SIGNAL(u'openlp_stop_wizard'), self.stopImport)

    def setDefaults(self):
        """
        Create defaults for properties - call this before each song
        if importing many songs at once to ensure a clean beginning
        """
        self.title = u''
        self.songNumber = u''
        self.alternateTitle = u''
        self.copyright = u''
        self.comments = u''
        self.themeName = u''
        self.ccliNumber = u''
        self.authors = []
        self.topics = []
        self.mediaFiles = []
        self.songBookName = u''
        self.songBookPub = u''
        self.verseOrderListGeneratedUseful = False
        self.verseOrderListGenerated = []
        self.verseOrderList = []
        self.verses = []
        self.verseCounts = {}
        self.copyrightString = unicode(translate(
            'SongsPlugin.SongImport', 'copyright'))

    def logError(self, filepath, reason=SongStrings.SongIncomplete):
        """
        This should be called, when a song could not be imported.

        ``filepath``
            This should be the file path if ``self.importSource`` is a list
            with different files. If it is not a list, but a single file (for
            instance a database), then this should be the song's title.

        ``reason``
            The reason why the import failed. The string should be as
            informative as possible.
        """
        self.setDefaults()
        if self.importWizard is None:
            return
        if self.importWizard.errorReportTextEdit.isHidden():
            self.importWizard.errorReportTextEdit.setText(
                translate('SongsPlugin.SongImport',
                'The following songs could not be imported:'))
            self.importWizard.errorReportTextEdit.setVisible(True)
            self.importWizard.errorCopyToButton.setVisible(True)
            self.importWizard.errorSaveToButton.setVisible(True)
        self.importWizard.errorReportTextEdit.append(
            u'- %s (%s)' % (filepath, reason))

    def stopImport(self):
        """
        Sets the flag for importers to stop their import
        """
        log.debug(u'Stopping songs import')
        self.stopImportFlag = True

    def register(self, import_wizard):
        self.importWizard = import_wizard

    def tidyText(self, text):
        """
        Get rid of some dodgy unicode and formatting characters we're not
        interested in. Some can be converted to ascii.
        """
        text = text.replace(u'\u2018', u'\'')
        text = text.replace(u'\u2019', u'\'')
        text = text.replace(u'\u201c', u'"')
        text = text.replace(u'\u201d', u'"')
        text = text.replace(u'\u2026', u'...')
        text = text.replace(u'\u2013', u'-')
        text = text.replace(u'\u2014', u'-')
        # Remove surplus blank lines, spaces, trailing/leading spaces
        text = re.sub(r'[ \t\v]+', u' ', text)
        text = re.sub(r' ?(\r\n?|\n) ?', u'\n', text)
        text = re.sub(r' ?(\n{5}|\f)+ ?', u'\f', text)
        return text

    def processSongText(self, text):
        verse_texts = text.split(u'\n\n')
        for verse_text in verse_texts:
            if verse_text.strip() != u'':
                self.processVerseText(verse_text.strip())

    def processVerseText(self, text):
        lines = text.split(u'\n')
        if text.lower().find(self.copyrightString) >= 0 \
            or text.find(unicode(SongStrings.CopyrightSymbol)) >= 0:
            copyright_found = False
            for line in lines:
                if (copyright_found or
                    line.lower().find(self.copyrightString) >= 0 or
                    line.find(unicode(SongStrings.CopyrightSymbol)) >= 0):
                    copyright_found = True
                    self.addCopyright(line)
                else:
                    self.parseAuthor(line)
            return
        if len(lines) == 1:
            self.parseAuthor(lines[0])
            return
        if not self.title:
            self.title = lines[0]
        self.addVerse(text)

    def addCopyright(self, copyright):
        """
        Build the copyright field
        """
        if self.copyright.find(copyright) >= 0:
            return
        if self.copyright != u'':
            self.copyright += ' '
        self.copyright += copyright

    def parseAuthor(self, text):
        """
        Add the author. OpenLP stores them individually so split by 'and', '&'
        and comma. However need to check for 'Mr and Mrs Smith' and turn it to
        'Mr Smith' and 'Mrs Smith'.
        """
        for author in text.split(u','):
            authors = author.split(u'&')
            for i in range(len(authors)):
                author2 = authors[i].strip()
                if author2.find(u' ') == -1 and i < len(authors) - 1:
                    author2 = author2 + u' ' \
                        + authors[i + 1].strip().split(u' ')[-1]
                if author2.endswith(u'.'):
                    author2 = author2[:-1]
                if author2:
                    self.addAuthor(author2)

    def addAuthor(self, author):
        """
        Add an author to the list
        """
        if author in self.authors:
            return
        self.authors.append(author)

    def addMediaFile(self, filename, weight=0):
        """
        Add a media file to the list
        """
        if filename in map(lambda x: x[0], self.mediaFiles):
            return
        self.mediaFiles.append((filename, weight))

    def addVerse(self, verse_text, verse_def=u'v', lang=None):
        """
        Add a verse. This is the whole verse, lines split by \\n. It will also
        attempt to detect duplicates. In this case it will just add to the verse
        order.

        ``verse_text``
            The text of the verse.

        ``verse_def``
            The verse tag can be v1/c1/b etc, or 'v' and 'c' (will count the
            verses/choruses itself) or None, where it will assume verse.

        ``lang``
            The language code (ISO-639) of the verse, for example *en* or *de*.

        """
        for (old_verse_def, old_verse, old_lang) in self.verses:
            if old_verse.strip() == verse_text.strip():
                self.verseOrderListGenerated.append(old_verse_def)
                self.verseOrderListGeneratedUseful = True
                return
        if verse_def[0] in self.verseCounts:
            self.verseCounts[verse_def[0]] += 1
        else:
            self.verseCounts[verse_def[0]] = 1
        if len(verse_def) == 1:
            verse_def += unicode(self.verseCounts[verse_def[0]])
        elif int(verse_def[1:]) > self.verseCounts[verse_def[0]]:
            self.verseCounts[verse_def[0]] = int(verse_def[1:])
        self.verses.append([verse_def, verse_text.rstrip(), lang])
        if verse_def not in self.verseOrderListGenerated:
            self.verseOrderListGenerated.append(verse_def)

    def repeatVerse(self):
        """
        Repeat the previous verse in the verse order
        """
        if self.verseOrderListGenerated:
            self.verseOrderListGenerated.append(
                self.verseOrderListGenerated[-1])
            self.verseOrderListGeneratedUseful = True

    def checkComplete(self):
        """
        Check the mandatory fields are entered (i.e. title and a verse)
        Author not checked here, if no author then "Author unknown" is
        automatically added
        """
        if not self.title or not self.verses:
            return False
        else:
            return True

    def finish(self):
        """
        All fields have been set to this song. Write the song to disk.
        """
        if not self.checkComplete():
            self.setDefaults()
            return False
        log.info(u'committing song %s to database', self.title)
        song = Song()
        song.title = self.title
        if self.importWizard is not None:
            self.importWizard.incrementProgressBar(
                WizardStrings.ImportingType % song.title)
        song.alternate_title = self.alternateTitle
        # Values will be set when cleaning the song.
        song.search_title = u''
        song.search_lyrics = u''
        song.verse_order = u''
        song.song_number = self.songNumber
        verses_changed_to_other = {}
        sxml = SongXML()
        other_count = 1
        for (verse_def, verse_text, lang) in self.verses:
            if verse_def[0].lower() in VerseType.Tags:
                verse_tag = verse_def[0].lower()
            else:
                new_verse_def = u'%s%d' % (VerseType.Tags[VerseType.Other],
                    other_count)
                verses_changed_to_other[verse_def] = new_verse_def
                other_count += 1
                verse_tag = VerseType.Tags[VerseType.Other]
                log.info(u'Versetype %s changing to %s', verse_def,
                    new_verse_def)
                verse_def = new_verse_def
            sxml.add_verse_to_lyrics(verse_tag, verse_def[1:], verse_text, lang)
        song.lyrics = unicode(sxml.extract_xml(), u'utf-8')
        if not self.verseOrderList and self.verseOrderListGeneratedUseful:
            self.verseOrderList = self.verseOrderListGenerated
        self.verseOrderList = map(lambda v: verses_changed_to_other.get(v, v),
            self.verseOrderList)
        song.verse_order = u' '.join(self.verseOrderList)
        song.copyright = self.copyright
        song.comments = self.comments
        song.theme_name = self.themeName
        song.ccli_number = self.ccliNumber
        for authortext in self.authors:
            author = self.manager.get_object_filtered(Author,
                Author.display_name == authortext)
            if not author:
                author = Author.populate(display_name=authortext,
                    last_name=authortext.split(u' ')[-1],
                    first_name=u' '.join(authortext.split(u' ')[:-1]))
            song.authors.append(author)
        if self.songBookName:
            song_book = self.manager.get_object_filtered(Book,
                Book.name == self.songBookName)
            if song_book is None:
                song_book = Book.populate(name=self.songBookName,
                    publisher=self.songBookPub)
            song.book = song_book
        for topictext in self.topics:
            if not topictext:
                continue
            topic = self.manager.get_object_filtered(Topic,
                Topic.name == topictext)
            if topic is None:
                topic = Topic.populate(name=topictext)
            song.topics.append(topic)
        # We need to save the song now, before adding the media files, so that
        # we know where to save the media files to.
        clean_song(self.manager, song)
        self.manager.save_object(song)
        # Now loop through the media files, copy them to the correct location,
        # and save the song again.
        for filename, weight in self.mediaFiles:
            media_file = self.manager.get_object_filtered(MediaFile,
                MediaFile.file_name == filename)
            if not media_file:
                if os.path.dirname(filename):
                    filename = self.copyMediaFile(song.id, filename)
                song.media_files.append(
                    MediaFile.populate(file_name=filename, weight=weight)
                )
        self.manager.save_object(song)
        self.setDefaults()
        return True

    def copyMediaFile(self, song_id, filename):
        """
        This method copies the media file to the correct location and returns
        the new file location.

        ``filename``
            The file to copy.
        """
        if not hasattr(self, u'save_path'):
            self.save_path = os.path.join(
                AppLocation.get_section_data_path(
                    self.importWizard.plugin.name),
                'audio', str(song_id))
        check_directory_exists(self.save_path)
        if not filename.startswith(self.save_path):
            oldfile, filename = filename, os.path.join(self.save_path,
                os.path.split(filename)[1])
            shutil.copyfile(oldfile, filename)
        return filename
