#!/usr/bin/env python
# -*- coding: UTF-8 -*-
'''
 Copyright (C) 2004 jan gerber <j@reboot.fm>

 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 Library 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.
'''
# streamswitch - parses a ics (ical) file and switches
#                to another ogg stream if nessessary
#                ogg streams are provided by 
#                - live encoders (studioA,..)
#                - an archive (preproduced shows, repetitions)
import os,string,re
from sys import exit,stdout
from ogg import OggSyncState, vorbis
from shout import Shout,ShoutException
from types import NoneType
from urllib2 import urlopen,HTTPError
import urllib2
import socket
from math import *
import syslog
import thread

# installed by hand right now. might put it in one package. python-pdi
from pdi.icalendar import VCalendar,ICalendar
import pdi.parser

# installed by hand right now. might put it in one package. python-dateutil
from dateutil.parser import *
import time,datetime

import re_codec
from re_codec import print_log

calendar=None

debug=0

if debug:
	import traceback

class switchogg :
	def __init__(self) :
		#some variables
		self.fallback=0
		self.manual=0
		self.calendar=None
		self.url=None
		self.url_content_length=None
		self.length_so_far=0
		# some stuff to handle the shout to the icecast server
		self.ogg = OggSyncState()
		self.page = 0
		self.serial = 1
		self.shout = Shout()
		self.shout.host = re_codec.conf.switch_shout_host
		self.shout.port = re_codec.conf.switch_shout_port
		self.shout.user = re_codec.conf.switch_shout_user
		self.shout.password = re_codec.conf.switch_shout_password
		self.shout.mount = re_codec.conf.switch_shout_mount
		self.shout.format = re_codec.conf.switch_shout_format
		self.shout.protocol = re_codec.conf.switch_shout_protocol
		self.shout.open()
		
	def __call__(self, data) :
		self.ogg.bytesin(data)
		while 1:
				newpage = self.ogg.pageseek()
				if newpage == None :
					break
				if self.page == 0 :
				   	self.page = newpage
				   	continue
				if self.page.serialno != newpage.serialno : self.page.eos = 1
				if newpage.pageno==0 : self.page.eos = 1
				self.page.serialno=self.serial
				if self.page.eos == 1 :
				   if self.serial<65536 : self.serial = self.serial + 1
				   else : self.serial = 0
				self.shout.sync()
				out=self.page.header+self.page.body
				self.shout.send(out)
				self.page=newpage
		return


def status_log(item,value):
	try:
		if item =='url':
			stat=open(re_codec.conf.StreamSwitchStatusfile,"w")
			stat.write(value)
			stat.close()
	except:
		print_log("could not update status")

def upcoming_show(so):
	global calendar
	shows= {}
	date_now=parse(time.strftime("%Y%m%dT%H%M%S"))

	if calendar==None:
		if so.calendar==None:
			return (date_now,shows)
	else:
		so.calendar=calendar

	for show in so.calendar.components:
		try:
			next_show=parse(show.properties['DTSTART'].getContent().strip())
			next_show_end=parse(show.properties['DTEND'].getContent().strip())
		except:
			continue
		date_now=parse(time.strftime("%Y%m%dT%H%M%S"))
		if next_show_end > date_now:
			if show.properties['LOCATION'].getContent().strip():
				url=show.properties['LOCATION'].getContent().strip()
				# the "play files on the same system not over http" hack
				if re.search("http://archive.reboot.fm/",url):
				 shows[next_show]=url.replace("http://archive.reboot.fm/","file:///data/archive/")
				else:
					shows[next_show]=show.properties['LOCATION'].getContent().strip()
			else:
				shows[next_show]=re_codec.conf.StreamSwitchDefaultURL

	return (date_now,shows)

def skipnseconds(f,seconds):
		print seconds
		read = f.read(4096)

def fillbuffer(f):
	length=0
	read=0
	try:
		read = f.read(4096)
		length=len(read)
	except socket.error, msg:  ### non blocking
		print_log("fill buffer error:%s" % msg)
		return (read,length)
	return (read,length)

def reconnect(so):
	f_tmp=0	
	try:
		#that does not work. as intentdet. need to find another way.
		#if so.url_content_length!=None:
		#	print_log("reconnect to file url: %s" % so.url)
		#	extra_headers={'Range':'bytes=%s-'%so.length_so_far}
		#	req = urllib2.Request(url=so.url,headers=extra_headers)
		#	f_tmp = urllib2.urlopen(req)
		#else:
			#print_log("reconnect to stream.: %s" %so.url)
			f_tmp = urlopen(so.url)

	except:
		f_tmp=0
		if debug:
			traceback.print_exc()
	return f_tmp

def switch_loop():
	dto = socket.getdefaulttimeout()
	socket.setdefaulttimeout(4.0)
	syslog.openlog('streamswitch',syslog.LOG_NOWAIT,syslog.LOG_USER)

	#initalize some variables,
	now=0
	f_tmp=0
	
	print_log("starting up Ogg switch.")
	while 1:
		try:
			so=switchogg()
		except:
			if debug:
				traceback.print_exc()
			print_log("couldn't connect to icecast server: %s"%re_codec.conf.switch_shout_host)
			time.sleep(1)
			continue
		print_log("fallback, open: %s" % re_codec.conf.StreamSwitchFallbackURL)
		f=None
		while f==None:
			try:
				f=urlopen(re_codec.conf.StreamSwitchFallbackURL)
				so.fallback=1				
			except socket.error, msg:  ### non blocking
				print_log("connect to fallback: timeout - %s"%msg)
			except HTTPError, msg:
				print_log("could not connect to fallback: %s" %msg)
				time.sleep(10)
				continue

		while 1:
			length = 0
			while length == 0 :
				if now < int(time.time()):  #run only once a second
					now=int(time.time())
					date_now,next_shows = upcoming_show(so)
					if so.url == None:  #this is part of initializing
						print_log("getting the latest show (booting)")
						next_key=None
						for key in next_shows.keys():
							if next_key == None: next_key=key
							if key > next_key and key < date_now: 
								next_key= key

						if next_key !=None:
							so.url=next_shows[next_key]
							try:
								print_log("latest show, open: %s" % so.url)
								f_tmp = urlopen(so.url)
								'''
								#if we have a local file seek to now in file.
								if re.search(so.url,'file://'):
									skipnseconds(f_tmp,date_now-next_key)
								'''

							except:
								print_log("couldn't open stream: %s" % so.url)
							if f_tmp != 0:
								f.close()
								f=f_tmp
								f_tmp=0
								so.fallback=0
								#so.url_content_length=f.headers.get("content-length")
								status_log("url",so.url)
	
				# now actually look if there is a show to start.
				# the update process has to go into another thread?
				# otherwise it could take longer than a second.
					if re_codec.conf.StreamSwitchOverwriteURL!='' and so.url !=re_codec.conf.StreamSwitchOverwriteURL:
						try:
							so.url=re_codec.conf.StreamSwitchOverwriteURL
							print_log("manual overwrite: %s" % so.url)
							f_tmp = urlopen(so.url)
							so.manual=1
						except:
							print_log("couldn't open stream: %s" % so.url)
							so.fallback=1
						if f_tmp != 0:
							f.close()
							f=f_tmp
							f_tmp=0
							so.fallback=0
							so.length_so_far = 0
							#so.url_content_length=f.headers.get("content-length")
							status_log("url",so.url)
					elif re_codec.conf.StreamSwitchOverwriteURL=='' and so.manual==1:
						print_log("manual off, back to regular schedule")
						date_now,next_shows = upcoming_show(so)
						next_key=None
						for key in next_shows.keys():
							if next_key == None: next_key=key
							if key > next_key and key < date_now: 
								next_key= key
						if next_key !=None:
							so.url=next_shows[next_key]
							try:
								print_log("latest show, open: %s" % so.url)
								f_tmp = urlopen(so.url)
							except:
								print_log("couldn't open stream: %s" % so.url)
							if f_tmp != 0:
								so.manual=0
								f.close()
								f=f_tmp
								f_tmp=0
								#so.url_content_length=f.headers.get("content-length")
								status_log("url",so.url)

					elif next_shows.has_key(date_now):
						if next_shows[date_now] != so.url: #we only want to switch if the url changes.
							try:
								print_log("new show, open: %s" % next_shows[date_now])
								so.url=next_shows[date_now]
								f_tmp = urlopen(so.url)
							except:
								print_log("couldn't open stream: %s" % so.url)
								so.fallback=1
							if f_tmp != 0:
								f.close()
								f=f_tmp
								f_tmp=0
								so.fallback=0
								so.length_so_far = 0
								#so.url_content_length=f.headers.get("content-length")
								status_log("url",so.url)
						else:
							print_log("new show, same url: %s" % so.url)

				# do some reconnecting magic
				if so.fallback ==1:
					f_tmp=reconnect(so)
					if f_tmp != 0:
						f.close()
						f=f_tmp
						#so.url_content_length=f.headers.get("content-length")
						f_tmp=0
						so.fallback=0
						flag=1
						status_log("url",so.url)
						so.page.eos = 1 
						print_log("now playing: %s" % so.url)

				(read,length) = fillbuffer(f)

				#if so.fallback==0:
				#	so.length_so_far = so.length_so_far + length

				if length == 0:
					f.close()
					f_tmp=reconnect(so)
					if f_tmp==0:
						try:
							f= urlopen(re_codec.conf.StreamSwitchFallbackURL)
							print_log("fallback, open: %s" % re_codec.conf.StreamSwitchFallbackURL)
							so.fallback=1
						except:
							print_log("fallback down!!!!!!: %s" % re_codec.conf.StreamSwitchFallbackURL)
					else:
						f=f_tmp

			try:
				so(read)
			except ShoutException,msg:
			# so the connection to the icecast server is down.
			# clean up some open filehandles and reconnect
				print_log("ShoutException: %s"%msg)
				f.close()
				break
				# could that be all needed?
				#so.shout.close()
				#so.shout.open()
		f.close()
		# restore default
		socket.setdefaulttimeout(dto)

def get_new_ical_from_url(url):
	global calendar
	try:
		ics_fh=urlopen(url)
	except:
		print_log("coudn't open ical url: %s",url)
		return None
	try:
		new_calendar = pdi.parser.fromFileObject(ics_fh, ICalendar())
	except:
		print_log("coudn't get the ical file")
		ics_fh.close
		return None
	ics_fh.close
	calendar=new_calendar

def icalUpdateThread():
	while 1:
		try:
			get_new_ical_from_url(re_codec.conf.ics_url)
		except:
			print_log("error updating ical file")
		time.sleep(60)

if __name__ == '__main__':
	re_codec.startConfigfileUpdateThread()
	get_new_ical_from_url(re_codec.conf.ics_url)
	thread.start_new(icalUpdateThread, ())	
	switch_loop()
