#!/usr/bin/python

#    picurl - tagging reinvented
#    Copyright (C) 2007-2008 picurl.org Team <team@picurl.org>
#
#    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 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.,
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

import os
import urllib
import os.path
import sys
import glob
try:
    import readline
# readline fallback on win32
except ImportError:
    import picurl.lib.readline
import shlex
import getpass
import inspect
import traceback

import picurl
import picurl.util as util

logger = picurl.get_logger('cli-client')

class PicurlShell(object):
    QUIT_WORDS = ('exit', 'quit', 'bye', 'q')

    def __init__(self):
        pass

    def cmd_store_copy(self, source_store, dest_store):
        """
        Copies all images from source_store to dest_store

        The copying takes place and you know it.

        For sure! It really does. Try it out.
        """
        source_store = picurl.open_store(source_store)
        dest_store = picurl.open_store(dest_store)

        for image in source_store:
            logger.info('Copying %s to %s' % (image.url, dest_store.url))
            dest_store.add_image(image)

    def cmd_store_info(self, store_url):
        """
        Lists image in the store specified by store_url

        You can list multiple images if you want, etc, etc..
        """
        store = picurl.open_store(store_url)
        print store
        logger.info('store %s has %d images' % (store_url, len(store)))

    def cmd_store_index(self, store_url, filename='feed.rss'):
        """
        Generates an RSS feed from the store at store_url

        Optionally, you can specify the file name
        """
        store = picurl.open_store(store_url)
        filename = picurl.util.picurl_file(filename)
        open(filename,'w').write(util.create_feed(store))
        print 'Feed saved: %s' % filename

    def cmd_store_add(self, alias, scheme='http'):
        """
        Configure a new alias for later use.

        The scheme is the protocol for the URLs.
        """
        config = picurl.store_config(scheme)
        for option in config:
            is_first = True
            while is_first or not option.is_valid():
                prompt = '%s: ' % option.get_prompt()
                if option.get_hide_input() == True:
                    new_value = getpass.getpass(prompt)
                else:
                    new_value = raw_input(prompt)
                if new_value.strip() != '':
                    option.set_value(new_value)
                is_first = False
                if not option.is_valid():
                    print 'Invalid input for this field.'
        picurl.open_store_config(scheme, config, alias)

    def cmd_store_list(self):
        """
        Shows you a list of all defined aliases.
        """
        for alias in picurl.list_aliases():
            try:
                url = picurl.get_url_for_alias(alias)
            except:
                url = '(unknown url)'
            (scheme, config) = picurl.get_configuration_for_alias(alias)
            if scheme == 'media':
                for c in config:
                    if c.key == 'identifier':
                        print '%s = %s (%s)' % (alias, url, c.value)
            else:
                print '%s = %s' % (alias, url)

    def cmd_store_remove(self, alias):
        """
        Removes an alias from picurl's alias list.
        """
        qr = ''
        while len(qr)==0:
            qr = raw_input('Do you really want to delete "%s"? (y|n) ' % alias)

        if qr.lower()[0] != 'y':
            sys.exit(1)

        if picurl.remove_alias(alias):
            print '"%s" has been removed.' % alias
        else:
            print 'Could not remove "%s".' % alias

    def cmd_store_volumes(self):
        """
        Lists all removable volumes attached to this computer.
        """
        picurl.lib.volumes.print_volumes()

    def cmd_store_edit(self, alias):
        """
        Edits a previously-created store alias.
        """
        qr = ''
        while len(qr)==0:
            qr = raw_input('Do you really want to edit "%s"? (y|n) ' % alias)

        if qr.lower()[0] != 'y':
            sys.exit(1)

        (scheme, config) = picurl.get_configuration_for_alias(alias)
        for option in config:
            is_first = True
            option['default'] = option.value
            while is_first or not option.is_valid():
                prompt = '%s: ' % option.get_prompt()
                if option.get_hide_input() == True:
                    new_value = getpass.getpass(prompt)
                else:
                    new_value = raw_input(prompt)
                if new_value.strip() != '':
                    option.set_value(new_value)
                is_first = False
                if not option.is_valid():
                    print 'Invalid input for this field.'
        picurl.open_store_config(scheme, config, alias)

    def cmd_thumbs_fetch(self, store_url, local_folder=os.getcwd()):
        """
        Download thumbnails and metadata from a store

        The files can then be downloaded using "download-images"
        """
        if not picurl.util.create_folder(local_folder):
            logger.warn('Cannot create folder: %s' % local_folder)
            sys.exit(1)

        store = picurl.open_store(store_url)
        local_folder = os.path.abspath(local_folder)
        feedname = ('%s.rss.xml') % (store_url.replace('://','_').replace('/','_').replace(os.sep,'_').replace(":\\",'_'))
        album = picurl.create_album (store_url,store_url,"Contents of %s" % (store_url))
        for image in store:
            print image.url
            filename = os.path.join(os.path.abspath(local_folder), os.path.basename(image.url))
            logger.info('writing %s to %s' % (os.path.basename(image.url), filename))
            if not image.write_placeholder_thumbnail(filename, album):
                logger.warn('Placeholder thumbnail not written: %s' % filename)

        album.write_to_file(os.path.join(local_folder, feedname))

        #logger.info ('Feed saved: %s' % filename)
        picurl.util.create_opml(local_folder)
        picurl.util.copy_stylesheet_dir(local_folder)
        logger.info ('Updated picurl.opml.xml')

    def cmd_thumbs_find(self,thumb_dir, *search_expr):
        """
        Search EXIF and IPTC metadata in the thumbnails.

        Search syntax: coming soon.
        """

        exiv2 = picurl.get_exiv2wrapper()
        search_result = exiv2.search_meta (thumb_dir,search_expr)
        #logger.info ('Search expression was: %s' % exiv2.eval_cond)
        print "Found %s photos" % (len(search_result))
        for match in search_result:
            print match.keys()[0]


    def cmd_thumbs_download(self, *thumb_paths):
        """
        Downloads images from a directory containing thumbnails

        The thumbnails have to be downloaded with "get-thumbnails".
        """
        thumb_list = []

        def glob_files (expr):
            thumb_files = glob.glob (expr)
            if len (thumb_files) == 0:
                     logger.warn('No files found for/in:'% expr)
            for i in range (len(thumb_files)):
                thumb_files[i] = os.path.abspath(thumb_files[i])
                if thumb_files[i] not in thumb_list:
                   thumb_list.append(thumb_files[i])

        for thumb_path in thumb_paths:
             if os.path.isdir(thumb_path):
                  thumb_files = glob_files(os.path.join(thumb_path, '*'))
             else:
                 #this also allows globbing expression inside file names, e.g. c:\temp\barcelona_??.jpg
                 thumb_files = glob_files(thumb_path)

        for filename in thumb_list:
            print 'Searching for URL in %s...' % (filename)
            try:
                metadata = picurl.get_metadata(open(filename, 'r+b'))
                for t in metadata.keys():
                    if t.lower() == 'usercomment':
                        url = metadata[t].printable
                        if 'http://' in url:
                            print 'Found: %s' % (url)
                            urllib.urlretrieve(url, filename)
                            found = True
                        else:
                            print 'Field contains no URL in %s' % (filename)
                if not found:
                    print 'No URL found for %s - SKIPPING' % (filename)
            except:
                print 'ERROR getting URL - SKIPPING %s' % (filename)

    def cmd_store_purge(self, store_url):
        """
        Removes all images from a picurl store
        """
        store = picurl.open_store(store_url)

        for image in list(store):
            logger.info('Deleting %s' % (image.url))
            store.delete_image(image)

    def cmd_store_metadata(self, *store_urls):
        """
        Print out image metadata from a list of stores
        """
        sorting = False
        sort_key = None
        logger.debug('Sorting disabled')

        images = []
        for store_url in store_urls:
            store = picurl.open_store(store_url)
            for image in store:
                images.append(image)
                logger.debug(image.url)

        def flatten_dict (md):
              flat_dict = {}
              flat_dict.update (md.get('EXIF',{}))
              flat_dict.update (md.get('IPTC',{}))
              if len(flat_dict) == 0:
                   return None
              else:
                   return flat_dict

        def compare_sort_keys(a, b):
            ma = flatten_dict(a.get_metadata())
            mb = flatten_dict(b.get_metadata())
            if ma is None:
                return -1
            elif mb is None:
                return +1
            else:
                return cmp(ma.get(sort_key, ''), mb.get(sort_key, ''))

        if sorting:
            images = sorted(images, cmp=compare_sort_keys)

        #url_length = min(40, max((len(x.url) for x in images)))

        for image in images:
            print 79*'='
            print image.url
            print 79*'-'
            meta = image.get_metadata()
            if not hasattr (meta,'keys') or len (meta) == 0:
                print "No metadata available"
            else:
                if sorting:
                    meta_flat = flatten_dict (meta) or {}
                    print '%-32s : %s' % (sort_key, meta_flat.get(sort_key,'---'))
                else:
                    for k in sorted(meta.keys()):
                       for key in sorted(meta[k].keys()):
                           keystring = '[%s] %s' % (k,key)
                           print '%-32s : %s' % (keystring, meta[k][key])

            print os.linesep

    def help(self, compact=False):
        for name, function in inspect.getmembers(self):
            if name.startswith('cmd_'):
                function_name = name[len('cmd_'):].replace('_', ' ')
                (args, varargs, varkw, locals) = inspect.getargspec(function)
                if 'self' in args:
                    args.remove('self')
                docstring = inspect.getdoc(function).splitlines()
                # The first line of the docstring is our description
                description = docstring.pop(0)
                print '%s -- %s' % (function_name, description)
                arg_str = ', '.join(args)
                if varargs is not None:
                    if varargs.endswith('s'):
                        varargs = varargs[:-1]

                    arg_str += '%s [...]' % varargs
                print '%s    Usage: %s (%s)' % (' '*len(function_name), function_name, arg_str)
                if not compact:
                    # Remove precending empty lines
                    while len(docstring) and docstring[0] == '':
                        docstring.pop(0)
                    # Output remaining lines from docstring
                    for line in docstring:
                        print '|   %s' % line
                    print ''

    def execute(self, cmd_list):
        """
        Executes a command list, e.g. ['list', 'http://localhost/']
        """
        # First item = command name
        if 'help' in cmd_list or '?' in cmd_list:
            return self.help(compact=True)
        elif len(cmd_list) < 2:
            print 'Invalid command: %s. Enter "help" for a list of commands.' % ' '.join(cmd_list)
            return False
        command = 'cmd_'+cmd_list.pop(0)+'_'+cmd_list.pop(0)
        if hasattr(self, command):
            # Try to simply call the command
            return getattr(self, command)(*cmd_list)
        else:
            logger.warn('Cannot find command: %s' % command)

    def interactive(self):
        """
        Starts an interactive shell session
        """
        print """
        welcome to the picurl shell :)

        Enter "exit" to exit or "help" for help.
        """
        input = ''
        while True:
            try:
                input = raw_input('picurl> ')
            except EOFError, e:
                print '\nEnter "exit" if you want to quit.'
                continue
            except KeyboardInterrupt, k:
                print '\nEnter "exit" if you want to quit.'
                continue

            if input.lower() in self.QUIT_WORDS:
                break

            if len(input)==0:
                continue

            try:
                self.execute(shlex.split(input))
            except:
                logger.warn('Warning: %s'% traceback.format_exc())

        print 'logging out. thanks for using picurl :)'


shell = PicurlShell()

print """
picurl-client %s (c) 2007 - 2008 The Picurl Team
""" % (picurl.__version__)

if '--help' in sys.argv or '-h' in sys.argv:
    shell.help(compact=True)
elif len(sys.argv) == 1:
    shell.interactive()
else:
    shell.execute(sys.argv[1:])
sys.exit(1)

