#!/usr/bin/env python

# episoder, http://episoder.googlecode.com/
#
# Copyright (C) 2004-2010 Stefan Ott. All rights reserved.
#
# 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

import os
import sys
import getopt
import logging
import datetime
import traceback
import pyepisoder.episoder as episoder
import pyepisoder.plugins as plugins

from pyepisoder.episoder import version

def show_version():
	print 'This is episoder ' + version

def show_parsers():
	parsers = plugins.all()['parsing']

	print 'Available parsers:\n'
	print 'name                description'
	print '-----               ------------'

	for parser in parsers:
		description = str(parser)
		if 'DO NOT USE' not in description:
			print "%-20s%s" % (parser.__class__.__name__, parser)

def show_help():
	print """Usage: %s [options]

Global options:
  -h			show this help
  -c <file>		use configuration from file (default: ~/.episoder)
  -b			update the database
  -B			force-update the database, disregard last update time
  -v			verbose operation
  -w			very verbose (debug) operation
  -V			show version information
  -p			show available parsers
  -l <file>		log to file instead of stdout

Options for database update:
  -d <YYYY-MM-DD>	remove episodes prior to this date (default: yesterday)
  -d <num>		remove episodes more than <num> days old (default: 1)
  -i			ignore date (don't remove old episodes)
  -f <file>		get data from file, ignore configured sources (needs -P)
  -P <parser>		force parser to be used (only in combination with -f)
  -S <id>		limit (force-)update to this show

Managing shows:
  -L			list shows in database
  -a <url>		add show to database
  -r <id>		remove show from database
  -E <id>		enable updates for show
  -D <id>		disable updates for show

Options for console output:
  -d <YYYY-MM-DD>	only show episodes newer than date (default: yesterday)
  -d <num>		only show episodes less than num days old (default: 1)
  -i			ignore date
  -n <days>		number of future days to show (default: 2),
			relative to the date set with -d
  -C			don't show any colors
  -s <text>		search database for text

Note that -i overrules -d and -n

Report episoder bugs on http://episoder.sf.net/""" % sys.argv[0]

def parse_rc(path):
	datafile = None
	format = '%airdate %show %seasonx%epnum'
	dateformat = '%a, %b %d, %Y'
	agent='episoder/' + version

	if not os.path.exists(path):
		sys.stderr.write(path + ' does not exist\n')
		sys.exit(2)

	sources = []

	try:
		rc = open(path)
	except Exception, msg:
		sys.stderr.write("%s\n" % msg)
		sys.exit(2)

	for line in rc:
		line = line.strip()
		if line.startswith('data='):
			(_, datafile) = line.split('=')
		elif line.startswith('format='):
			(_, format) = line.split('=')
		elif line.startswith('dateformat='):
			(_, dateformat) = line.split('=')
		elif line.startswith('agent='):
			(_, agent) = line.split('=')
		elif line.startswith('src='):
			data = {}
			name = None
			if ' ' in line:
				(src, opts) = line.split(' ')
				if opts.startswith('name='):
					(_, name) = opts.split('=')
				data['name'] = name
			else:
				src = line

			(_, url) = src.split('=')
			data['url'] = url

			sources.append(data)
			logging.debug('source entry: %s (%s)' % (url, name))

	rc.close()

	if not datafile:
		sys.stderr.write('No data file defined in configuration')
		sys.exit(3)
	if len(sources) < 1:
		logging.info('No sources defined in configuration')

	logging.debug('datafile=' + datafile)
	logging.debug('format=' + format)
	logging.debug('dateformat=' + dateformat)
	logging.debug('agent=' + agent)

	config = {
		'datafile': datafile,
		'format': format,
		'dateformat': dateformat,
		'sources': sources,
		'agent': agent
	}

	logging.info('Loaded rcfile')

	return config

def show_data(options, config):
	store = episoder.DataStore(config['datafile'])
	renderer = plugins.all()['output'][0]
	renderer.render(store, options, config)

def list_shows(options, config):
	store = episoder.DataStore(config['datafile'])
	store.update()

	shows = store.getShows()

	statusStrings = ["?Invalid", "Running", "Suspended", "Ended" ]
	enabledStrings = ['Disabled', 'Enabled']

	for show in shows:
		status = show.status

		print "[%4d] %s" % (show.show_id, show.url)
		print "       %s, %s, %s" % (show.show_name,
			statusStrings[status], enabledStrings[show.enabled])
		print "       Last update: %s" % (show.updated)

def add_show(options, config):
	store = episoder.DataStore(config['datafile'])
	store.update()

	url = options['search']
	show = store.getShowByUrl(url)

	if show:
		sys.stderr.write('A show with that url already exists\n')
		return

	show = episoder.Show('Unknown Show', url=url)

	store.addShow(show)
	store.commit()

def remove_show(options, config):
	store = episoder.DataStore(config['datafile'])
	store.update()

	id = options['search']
	store.removeShow(id)
	store.commit()

def set_show_enabled(store, id, enabled):
	show = store.getShowById(id)

	if not show:
		sys.stderr.write("There is no show with that ID\n")
		return

	show.enabled = enabled
	store.commit()

def enable_show(options, config):
	store = episoder.DataStore(config['datafile'])
	store.update()
	set_show_enabled(store, options['search'], True)

def disable_show(options, config):
	store = episoder.DataStore(config['datafile'])
	store.update()
	set_show_enabled(store, options['search'], False)

def update_data_from_file(file, parser, store):
	logging.debug('Only parsing %s' % file)

	show = store.getShowByUrl(file)

	if not show:
		show = episoder.Show(None, url=file)
		show = store.addShow(show)

	show.enabled = False

	parser.show = show
	parser.parseFile(file, store)

def do_update_data(options, config, force):
	store = episoder.DataStore(config['datafile'])
	store.update()

	if options['inputfile']:
		file = options['inputfile']
		parser = plugins.parser_named(options['parser'])
		update_data_from_file(file, parser, store)
		return

	# first, add all shows from the config file to our database
	for source in config['sources']:
		url = source['url']
		show = store.getShowByUrl(url)

		if not show:
			show = episoder.Show(None, url=url)
			show = store.addShow(show)
			store.commit()

	# then, update all shows that need updating
	if options['search']:
		show_id = int(options['search'])
		shows = [store.getShowById(show_id)]
	elif force:
		shows = store.getEnabledShows()
	else:
		shows = store.getExpiredShows()

	for show in shows:
		try:
			if 'agent' in config:
				agent = config['agent']
				show.update(store, agent)
			else:
				show.update(store)
		except:
			logging.error("Error parsing %s" % show)
			traceback.print_exc()
			store.rollback()

		if not options['nodate']:
			basedate = options['date']
			show.removeEpisodesBefore(store, basedate)

	if not shows:
		logging.info('None of your shows need to be updated')

def update_data(options, config):
	do_update_data(options, config, False)

def force_update_data(options, config):
	do_update_data(options, config, True)

def get_options():
	rcfile = os.path.join(os.environ["HOME"], '.episoder')
	loglevel = logging.WARNING
	daysahead = 2
	date = datetime.date.today() - datetime.timedelta(1)
	nodate = False
	colors = True
	search = ''
	command = show_data
	inputfile = None
	parser = None
	logfile = None

	try:
		valid_options = 'c:d:hin:s:vVwbBpf:P:l:CLa:r:E:D:S:'
		options, args = getopt.getopt(sys.argv[1:], valid_options)
	except getopt.error, msg:
		print msg
		print "for help use -h"
		sys.exit(1)

	for option, argument in options:
		if option == '-h':
			show_help()
			sys.exit(0)
		elif option == '-c':
			rcfile = argument
		elif option == '-v':
			loglevel = logging.INFO
		elif option == '-w':
			loglevel = logging.DEBUG
		elif option == '-V':
			show_version()
			sys.exit(0)
		elif option == '-n':
			daysahead = int(argument)
		elif option == '-p':
			show_parsers()
			sys.exit(0)
		elif option == '-d':
			if argument.isdigit():
				date = (datetime.date.today() -
					datetime.timedelta(int(argument)))
			else:
				parts = argument.split('-')
				date = datetime.date(int(parts[0]),
					int(parts[1]), int(parts[2]))
		elif option == '-i':
			nodate = True
		elif option == '-s':
			search = argument
		elif option == '-b':
			command = update_data
		elif option == '-B':
			command = force_update_data
		elif option == '-f':
			inputfile = argument
		elif option == '-P':
			parser = argument
		elif option == '-l':
			logfile = argument
		elif option == '-C':
			colors = False
		elif option == '-L':
			command = list_shows
		elif option == '-a':
			command = add_show
			# yes, this *is* a terrible abuse of the 'search' field
			search = argument
		elif option == '-r':
			command = remove_show
			search = argument
		elif option == '-E':
			command = enable_show
			search = argument
		elif option == '-D':
			command = disable_show
			search = argument
		elif option == '-S':
			search = argument

	return {
		'rcfile': rcfile,
		'loglevel': loglevel,
		'days': daysahead,
		'date': date,
		'nodate': nodate,
		'search': search,
		'command': command,
		'inputfile': inputfile,
		'parser': parser,
		'logfile': logfile,
		'colors': colors
	}

def main():
	options = get_options()

	if options['logfile']:
		logging.basicConfig(level=options['loglevel'],
				filename=options['logfile'])
	else:
		logging.basicConfig(level=options['loglevel'])
	config = parse_rc(options['rcfile'])

	if os.path.exists(config['datafile']):
		file = open(config['datafile'])
		if file.read(6) != 'SQLite':
			sys.stderr.write('episoder found an old data file at ' +
				'%s. You have to delete ' % config['datafile'] +
				'that file before episoder can proceed.\n')
			sys.exit(4)

	options['command'](options, config)

if __name__ == "__main__":
	main()
