# -*- coding: ascii -*-

###########################################################################
# clive, video extraction utility
# Copyright (C) 2007-2008 Toni Gundogdu
#
# clive 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., 59 Temple Place, Suite 330, Boston, MA 0.1.2-1307 USA
###########################################################################

## The classes for parsing runtime configuration file

import os
import sys
import time
import errno

from clive.path import ConfigDir, Autodetect

__all__ = ['ConfigParser']


## The class that wraps config file parsing
class ConfigParser:
    _config = {}
    _line_no = 0

    _lookup = {
        'enable_extract' : {
            'default':'YES',
            'accepts':['YES','NO'],
            'opts_key':'enable_extract',
            'desc':'## Whether clive should actually extract the videos',
        },
        'enable_paste' : {
            'default':'NO',
            'accepts':['YES','NO'],
            'opts_key':'enable_xclip_paste',
            'desc':'## Whether clive should paste URLs from X clipboard ' \
                'using ``xclip``\n## rather than reading them from command ' \
                'line or stdin',
        },
        'enable_cache' : {
            'default':'YES',
            'accepts':['YES','NO'],
            'opts_key':'enable_cache',
            'desc':'## Whether clive should read/write cache',
        },
        'enable_recall' : {
            'default':'YES',
            'accepts':['YES','NO'],
            'opts_key':'enable_recall',
            'desc':'## Whether clive should write to ~/.clive/recall file',
        },
        'enable_verbose' : {
            'default':'YES',
            'accepts':['YES','NO'],
            'opts_key':'enable_verbose',
            'desc':'## Whether clive should be verbose',
        },
        'enable_low_quality' : {
            'default':'NO',
            'accepts':['YES','NO'],
            'opts_key':'enable_low_quality',
            'desc':'## Explicitly extract low quality videos even if better ' \
                'quality\n## are available (hosts: ytube,dmotion)',
        },
        'play_format' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'play_format',
            'desc':'## Uncomment to play FORMAT (e.g. "src", "mpg", ' \
                '"avi", ..)\n## Requires also path_player'
        },
        'reencode_format' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'reencode_format',
            'desc':'## Uncomment to re-encode to FORMAT ("mpg", "avi", ..)\n' \
                '## Requires also path_ffmpeg',
        },
        'path_player' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'player',
            'desc':'## Path to a player program, e.g. "/usr/local/bin/vlc %i"',
        },
        'path_ffmpeg' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'ffmpeg',
            'desc':'## Path to the ffmpeg program (' \
                'e.g. "/usr/local/bin/ffmpeg -y -i %i %o")',
        },
        'path_xclip' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'xclip',
            'desc':'## Path to the xclip program (' \
                'e.g. "/usr/local/bin/xclip -o")',
        },
        'output_savedir' : {
            'default':os.getcwd(),
            'accepts':['__any__'],
            'opts_key':'output_savedir',
            'desc':'## Uncomment to override default (workdir)',
        },
        'output_if_file_exists' : {
            'default':'RENAME',
            'accepts':['RENAME','OVERWRITE'],
            'opts_key':'output_exists',
            'desc':'## Action if local_file > remote_file OR if host ' \
                'does not support resuming\n## partially downloaded files; ' \
                '"OVERWRITE/RENAME" (default:RENAME)',
        },
        'output_title_mask' : {
            'default':'A-Za-z0-9',
            'accepts':['__any__'],
            'opts_key':'output_mask',
            'desc':'## Uncomment to override default ("A-Za-z0-9"), ' \
                'see also clive(1)',
        },
        'output_filename_format' : {
            'default':'%t.%e',
            'accepts':['__any__'],
            'opts_key':'output_format',
            'desc':'## Uncomment to override default ("%t.%e"), ' \
                'see also clive(1)',
        },
        'http_proxy' : {
            'default':None,
            'accepts':['__any_or_empty__'],
            'opts_key':'http_proxy',
            'desc':'## Default: use http_proxy environment variable IF found.\n'
                '## Uncomment to override the default behaviour.',
        },
        'http_agent' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'http_agent',
            'desc':'## Uncomment to override default (random string)',
        },
        'http_throttle' : {
            'default':0,
            'accepts':['__any__'],
            'opts_key':'http_throttle',
            'desc':'## Uncomment to override default (0=unlimited) [KB/s]',
        },
        'youtube_id' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'youtube_username',
            'desc':'## Youtube username',
        },
        'youtube_password' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'youtube_password',
            'desc':'## Youtube password',
        },
        'dmotion_id' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'dmotion_username',
            'desc':'## Dailymotion username',
        },
        'dmotion_password' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'dmotion_password',
            'desc':'## Dailymotion password',
        },
        'url_feed' : {
            'default':[],
            'accepts':['__any__'],
            'opts_key':'url_feed',
            'desc':'## RSS/Atom feed, duplicate commands as needed.',
        },            
    }

    ## Constructor
    def __init__(self):
        self._set_defaults()
        self._filter_yesno()

    ## Load and parse config file
    #
    # Sets default values for unused config file variables.
    def load(self):
        try:
            f = open(ConfigDir().rcfile(), 'r')
        except IOError, e:
            if e.errno == errno.ENOENT: return
            else: raise e

        self._config = {}
        found_vars = []
        self._line_no = 0
        for ln in f.readlines():
            self._line_no += 1
            ln = ln.rstrip('\n')
            if len(ln) == 0: continue
            if ln.startswith('#'): continue
            try:
                (var,val) = ln.split('=', 1)
                if not val.startswith('"') or not val.endswith('"'):
                    self._err('expected quoted value (e.g. cmd="value")')
                    continue
                val = val.lstrip().rstrip()
                val = val.strip('"')
                try:
                    d = self._lookup[var]
                    if val not in d['accepts']:
                        if len(val) == 0:
                            if d['accepts'][0] != '__any_or_empty__':
                                self._err('cannot be an empty value')
                        elif d['accepts'][0] not \
                            in ['__any__','__any_or_empty__']:
                                self._err('invalid value (%s), expected %s' % \
                                    (val, str(d['accepts'])))
                except KeyError:
                    self._warn('unrecognized command (%s)' % var)
                    continue
                if var != 'url_feed':
                    self._config[var] = val
                    found_vars.append(var)
                else: # RSS/Atom feed URL
                    try:
                        self._config[var].append(val)
                    except KeyError:
                        self._config[var] = [val,]
                    if var not in found_vars: # Append only once
                        found_vars.append(var)
            except ValueError:
                self._err('expected \'command="value"\'')
                continue

        self._set_defaults(found_vars)
        self._filter_yesno()

        return self._config

    ## Return default value for the requested option key
    def get_default(self, opts_key):
        for var in self._lookup:
            d = self._lookup[var]
            if d['opts_key'] == opts_key:
                return self._config[var]
        raise SystemExit('error: invalid key (%s), report this bug' % opts_key)

    ## (Re)writes config file with default values
    #
    # Will prompt the user if the file exists already.
    def rewrite_config(self, say):
        if os.path.exists(ConfigDir().rcfile()):
            if raw_input('rewrite existing config file? (y/N): ').lower() \
                == 'y':
                os.remove(ConfigDir().rcfile())
            else:
                return

        self._lookup['path_player']['default'] = \
            Autodetect().locate('vlc', '%i', say)

        self._lookup['path_ffmpeg']['default'] = \
            Autodetect().locate('ffmpeg', '-y -i %i %o', say)

        self._lookup['path_xclip']['default'] = \
            Autodetect().locate('xclip', '-o', say)

        f = open(ConfigDir().rcfile(), 'w')
        f.write('## Created: %s\n\n' % time.asctime())
        f.write('## Lines that begin with "##" try to explain what is ' \
            'going on. Lines\n## that begin with just "#" are disabled ' \
            'commands: you can enable them\n## by removing the "#" symbol.\n\n')
        for (var, data) in sorted(self._lookup.items()):
            v = self._lookup[var]['default']
            c = self._lookup[var]['desc']
            if v == None: v = ''
            if var == 'output_savedir': v='' # don't save with os.getcwd()
            if var == 'url_feed': v=''
            if len(c) > 0: f.write('%s\n' % c)
            f.write('#%s="%s"\n\n' % (var,v))
        f.close()
        say('done.')
            
    def _set_defaults(self, found_vars=None):
        for var in self._lookup:
            set = 0
            if found_vars:
                if var not in found_vars:
                    set = 1
            else:
                set = 1
            if set:                
                self._config[var] = self._lookup[var]['default']

    def _filter_yesno(self):
        for var in self._config:
            if self._config[var] == 'YES': self._config[var] = True
            elif self._config[var] == 'NO': self._config[var] = False

    def _warn(self, warning):
        self._err(warning, 'warn')

    def _err(self, error, _type='error'):
        print >>sys.stderr, '%s:config:%d: %s' % (_type, self._line_no, error)
        if _type == 'error': raise SystemExit

if __name__ == '__main__':
    conf = ConfigParser()
    conf.load()
    conf.generate_file()

