# -*- coding: ascii -*-

###########################################################################
# clive, video extraction utility
# Copyright (C) 2007-2008 Toni Gundogdu
#
# This file is part of clive.
#
# 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 3 of the License, or
# (at your option) any later version.
#
# clive 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 clive.  If not, see <http://www.gnu.org/licenses/>.
###########################################################################

## The classes for parsing runtime configuration file

__all__ = ['ConfigParser']

from clive.path import ConfigDir, Autodetect
from clive.modules import Modules

## The class that wraps config file parsing
class ConfigParser:
    _config = {}
    _line = 0
    _os = Modules().getinst('os')
    _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`` rather\n## 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 URL cache',
        },
        'enable_recall' : {
            'default':'YES',
            'accepts':['YES','NO'],
            'opts_key':'enable_recall',
            'desc':'## Whether clive should write last URL batch 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 are available',
        },
        'play_format' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'play_format',
            'desc':'## Uncomment to play format (e.g. "src", "mpg", ..) ' \
                'after extraction',
        },
        'reencode_format' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'reencode_format',
            'desc':'## Uncomment to re-encode to format ("mpg", "avi", ..) ' \
                'after extraction;\n## see 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':'## Save directory for downloaded videos, use absolute ' \
                'a path. Defaults to\n## current work directory',
        },
        '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 continuing\n## partially downloaded files; ' \
                '"OVERWRITE/RENAME" (default:RENAME)',
        },
        'output_title_filter' : {
            'default':'A-Za-z0-9',
            'accepts':['__any__'],
            'opts_key':'output_filter',
            'desc':'## Either regexp for re.sub (e.g. "A-Za-z0-9") or ' \
                '"custom" for user-defined\n## ~/.clive/custom.py ' \
                'otherwise "NO" if this feature should be disabled',
        },
        'output_filename_format' : {
            'default':'%t.%e',
            'accepts':['__any__'],
            'opts_key':'output_format',
            'desc':'## Uncomment to override the default "%t.%e", ' \
                'see also the clive manual page',
        },
        'http_proxy' : {
            'default':None,
            'accepts':['__any_or_empty__'],
            'opts_key':'http_proxy',
            'desc':'## HTTP proxy. Read from http_proxy env. ' \
                'variable if unused',
        },
        'http_agent' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'http_agent',
            'desc':'## HTTP user-agent (string), random string if unused',
        },
        'http_throttle' : {
            'default':0,
            'accepts':['__any__'],
            'opts_key':'http_throttle',
            'desc':'## Uncomment to override the default 0 (unlimited, KB/s)',
        },
        'youtube_login' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'youtube_login',
            'desc':'## Youtube login info in format "username:password"',
        },
        'dmotion_login' : {
            'default':None,
            'accepts':['__any__'],
            'opts_key':'dmotion_login',
            'desc':'## Dailymotion login info in format "username:password"',
        },
        'feed_url' : {
            'default':[],
            'accepts':['__any__'],
            'opts_key':'feed_url',
            'desc':'## RSS/Atom feed, duplicate this command 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:
            errno = Modules().getinst('errno')
            if e.errno == errno.ENOENT: return
            else: raise e
        self._config = {}
        found_cmds = []
        self._line = 0
        # Parse file line by line
        for ln in f:
            ln = ln.rstrip('\n')
            self._line += 1
            # Skip comments and empty lines
            if len(ln) == 0 or ln.startswith('#'):
                continue
            try:                
                (cmd,val) = ln.split('=',1)
            except ValueError:
                self._warn('invalid format: expected $(cmd)="$(value)"')
                continue
            if not val.startswith('"') and not val.endswith('"'):
                self._warn('invalid format: unquoted value')
                continue
            val = val.strip().strip('"')
            if cmd not in self._lookup:
                self._warn('unrecognized command')
                continue
            d = self._lookup[cmd]
            if val not in d['accepts']:
                if len(val) == 0:
                    if d['accepts'][0] != '__any_or_empty__':
                        self._err('expected non-empty value')
                    elif d['accepts'][0] not in ['__any__','__any_or_empty__']:
                        self._err('expected %s' % str(d['accepts']))
            if cmd == 'feed_url': # Exception for RSS/Atom feeds
                try:
                    self._config[cmd].append(val)
                except KeyError:
                    self._config[cmd] = [val,]
                if cmd not in found_cmds:
                    found_cmds.append(cmd)
            else:
                self._config[cmd] = val
                found_cmds.append(cmd)
        self._set_defaults(found_cmds)
        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, enable_confirm):
        if self._os.path.exists(ConfigDir().rcfile()):
            a = 'y'
            if enable_confirm:
                a = raw_input('> rewrite existing config file? (y/N): ')
            if len(a) > 0 and a.lower()[0] == 'y':
                self._os.remove(ConfigDir().rcfile())
            else:
                return
        # Autodetect: player, ffmpeg, xclip
        self._lookup['path_player']['default'] = \
            Autodetect().locate(['vlc','xine','mplayer'], '%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)
        # Autodetect: http_proxy env. setting
        v = self._os.getenv('http_proxy')
        if v: say('found: %s (http_proxy)' % v)
        self._lookup['http_proxy']['default'] = v
        # Write file
        f = open(ConfigDir().rcfile(), 'w')
        time = Modules().getinst('time')
        f.write('## Created: %s\n\n' % time.asctime())
        f.write('## Lines that begin with "##" try to explain what is ' \
            'going on.\n## Lines that begin with just "#" are disabled ' \
            'commands:\n## you can enable them 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 == 'feed_url': 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_cmds=None):
        for var in self._lookup:
            set = 0
            if found_cmds:
                if var not in found_cmds:
                    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'):
        sys = Modules().getinst('sys')
        fn = ConfigDir().rcfile()
        print >>sys.stderr, '%s:%d: %s' % (fn, self._line, error)
        if _type == 'error': raise SystemExit
