#    copyright 2009-2011 Thomas Gideon, Jason Penney
#
#    This file is part of flashbake.
#
#    flashbake 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 3 of the License, or
#    (at your option) any later version.
#
#    flashbake 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 flashbake.  If not, see <http://www.gnu.org/licenses/>.
 
'''   scrivener.py - Scrivener flashbake plugin
by Jason Penney, jasonpenney.net'''
 
from flashbake.plugins import (
    AbstractFilePlugin, AbstractMessagePlugin, PluginError,
    PLUGIN_ERRORS)
 
import flashbake #@UnusedImport
import fnmatch
import glob
import logging
import os
import os.path
import subprocess
import string
import re
 
from flashbake.compat import relpath, pickle
 
def find_scrivener_projects(hot_files, config, flush_cache=False):
    if flush_cache:
        config.scrivener_projects = None
 
    if config.scrivener_projects == None:
        scrivener_projects = list()
        for f in hot_files.control_files:
            if fnmatch.fnmatch(f, '*.scriv'):
                scrivener_projects.append(f)
 
        config.scrivener_projects = scrivener_projects
 
    return config.scrivener_projects
 
 
def find_scrivener_project_contents(hot_files, scrivener_project):
    for path, dirs, files in os.walk(os.path.join(  # @UnusedVariable
            hot_files.project_dir, scrivener_project)):
        rpath = relpath(path, hot_files.project_dir)
        for filename in files:
            yield os.path.join(rpath, filename)
 
 
def get_logfile_name(scriv_proj_dir):
    return os.path.join(os.path.dirname(scriv_proj_dir),
                        ".%s.flashbake.wordcount" % os.path.basename(
                            scriv_proj_dir))
 
 
## TODO: deal with deleted files
class ScrivenerFile(AbstractFilePlugin):
 
    def __init__(self, plugin_spec):
        AbstractFilePlugin.__init__(self, plugin_spec)
        self.share_property('scrivener_projects')
 
    def pre_process(self, hot_files, config):
        for f in find_scrivener_projects(hot_files, config):
            logging.debug("ScrivenerFile: adding '%s'" % f)
            for hotfile in find_scrivener_project_contents(hot_files, f):
                #logging.debug(" - %s" % hotfile)
                hot_files.control_files.add(hotfile)
 
    def post_process(self, to_commit, hot_files, config):
        flashbake.commit.purge(config, hot_files)
 
 
class ScrivenerWordcountFile(AbstractFilePlugin):
    """ Record Wordcount for Scrivener Files """
 
    def __init__(self, plugin_spec):
        AbstractFilePlugin.__init__(self, plugin_spec)
 
        self.define_property('use_textutil', type=bool, default=False)
 
        self.share_property('scrivener_projects')
        self.share_property('scrivener_project_count')
 
    def init(self, config):
        self.get_count = self._get_count_python
        if self.use_textutil:
            if flashbake.executable_available('textutil'):
                self.get_count = self._get_count_textutil
            else:
                logging.warn("unable to find textutil, will use python "
                             "wordcount calculation")
 
    def pre_process(self, hot_files, config):
        config.scrivener_project_count = dict()
        for f in find_scrivener_projects(hot_files, config):
            scriv_proj_dir = os.path.join(hot_files.project_dir, f)
            hot_logfile = get_logfile_name(f)
            logfile = os.path.join(hot_files.project_dir, hot_logfile)
 
            if os.path.exists(logfile):
                logging.debug("logifile exists %s" % logfile)
                log = open(logfile, 'r')
                oldCount = pickle.load(log)
                log.close()
            else:
                oldCount = {
                    'Content': 0,
                    'Synopsis': 0,
                    'Notes': 0,
                    'All': 0}
 
            search_path = os.path.join(scriv_proj_dir, 'Files', 'Docs')
            if os.path.exists(os.path.join(search_path)):
                newCount = {
                    'Content': self.get_count(search_path, ["*[0-9].rtf"]),
                    'Synopsis': self.get_count(
                        search_path, ['*_synopsis.txt']),
                    'Notes': self.get_count(
                        search_path, ['*_notes.rtf']),
                    'All': self.get_count(
                        search_path, ['*.rtf', '*.txt'])}
 
            else:
                newCount = {
                    'Content': self.get_count(scriv_proj_dir, ["*[0-9].rtfd"]),
                    'Synopsis': self.get_count(
                        scriv_proj_dir, ['*_synopsis.txt']),
                    'Notes': self.get_count(
                        scriv_proj_dir, ['*_notes.rtfd']),
                    'All': self.get_count(
                        scriv_proj_dir, ['*.rtfd', '*.txt'])}
 
            config.scrivener_project_count[f] = {
                'old': oldCount,
                'new': newCount}
 
            if not config.context_only:
                log = open(logfile, 'w')
                pickle.dump(config.scrivener_project_count[f]['new'], log)
                log.close()
                if not hot_logfile in hot_files.control_files:
                    hot_files.control_files.add(logfile)
 
    RTF_RE = re.compile('(\{[^}]+\}|\\\\\\\\END_SCRV[^\}]+\}|'
                        '\\\\\'\d+|\\\\(\\\\|[-=A-Za-z0-9\.])*|\}$|'
                        '\W[%s]\W)' % (re.escape(string.punctuation)),
                        re.MULTILINE | re.IGNORECASE)
 
    def _get_count_python(self, file, matches):
        count = 0
        for match in matches:
            for f in glob.glob(os.path.normpath(os.path.join(file, match))):
                if f.endswith('.rtfd'):
                    new_f = os.path.join(f, 'TXT.rtf')
                    if os.path.exists(new_f):
                        f = new_f
 
                if f.endswith('.txt'):
                    count += len(open(f).read().split(None))
                elif f.endswith('.rtf'):
                    words = self.RTF_RE.sub('', open(f).read()).split(None)
                    count += len(words)
                else:
                    raise PluginError(
                        PLUGIN_ERRORS.ignorable_error,
                        self.plugin_spec,
                        'Unsupported file type: %s' % f)
        return count
 
    def _get_count_textutil(self, file, matches):
        count = 0
        args = ['textutil', '-stdout', '-cat', 'txt']
        do_count = False
        for match in matches:
            for f in glob.glob(os.path.normpath(os.path.join(file, match))):
                do_count = True
                args.append(f)
 
        if do_count:
            p = subprocess.Popen(args, stdout=subprocess.PIPE,
                             close_fds=True)
            count += len(p.stdout.read().split(None))
 
        return count
 
 
class ScrivenerWordcountMessage(AbstractMessagePlugin):
    """ Display Wordcount for Scrivener Files """
 
    def __init__(self, plugin_spec):
        AbstractMessagePlugin.__init__(self, plugin_spec, False)
        self.share_property('scrivener_project_count')
 
    def addcontext(self, message_file, config):
        to_file = ''
        if 'scrivener_project_count' in config.__dict__:
            for proj in config.scrivener_project_count:
                to_file += "Wordcount: %s\n" % proj
                for key in ['Content', 'Synopsis', 'Notes', 'All']:
                    new = config.scrivener_project_count[proj]['new'][key]
                    old = config.scrivener_project_count[proj]['old'][key]
                    diff = new - old
                    to_file += "- " + key.ljust(10, ' ') + str(new).rjust(20)
                    if diff != 0:
                        to_file += " (%+d)" % (new - old)
                    to_file += "\n"
 
            message_file.write(to_file)