#! /usr/bin/env python

#-------------------------------------------------------------------------------
# Raspberry Pi Digital Signage
# v1.4 (08/06/2014)
# Copyright (C) 2014  Jake Martinez
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
# 
# Author: Jake Martinez (jrm98@vt.edu)
# Description: Designed to run on a Raspbian based, Raspberry Pi system to be
#     used as a means of producing imagery for display. This script is dependent
#     on the presence and execution of the update script also packaged with this
#     script and must run subsequently to it. The script is currently set to 
#     display a slideshow loop indefinitely as well as a video at 09:09:55 and 
#     10:54:55. Furthermore, the script will reboot the system at 01:55:55. If
#     configured correctly, the updater script will then run on reboot
#     automatically before this script is then executed.
#
# For the latest version, visit github.com/jrm98/raspi-dsign
#-------------------------------------------------------------------------------

import sys, getopt, json, datetime, subprocess, time, ConfigParser, os, signal
from pprint import pprint

sshow_pid = 0

# called if incorrect usage was performed
def usage():
	print('./rpi-dsignage.py [-ihu] [PLAYLIST PATH]')
	print('  -i\t\t\tenables the GUI (not implemented)')
	print('  -u <on, off>\t\tenables/disables updates (default is on)')
	print('  -h\t\t\tdisplays this screen')
	print('  --config <path>\tspecifies a configuration to load')
	print('                 \tfrom a file with the given path')
	pass

def main(argv):
	# process command line arguments
	#  - options for:
	#          > auto updating enabled/disabled ("-u <on,off>")
	#          > video playback settings (not implemented)
	#          > config file destination ("--config=<path>")
	try:
		opts, args = getopt.getopt(argv, "hiu:", 
			["config=","interface"])
	except getopt.GetoptError as err:
		print(str(err))
		usage()
		sys.exit(2)

	# options
	config_file = ""
	updates = True
	interface = False;

	# 
	for o, a in opts:
		if o == "-u":
			if a == "on":
				updates = True;
			elif a == "off":
				updates = False;
			else:
				usage()
				assert False, "invalid argument"
		elif o == "--config":
			config_file = a;
		elif o in ("-i", "--interface"):
			interface = True;
		elif o == "-h":
			usage();
			return
		else:
			usage()
			assert False, "invalid option"


	# beginning of primary functionality:
	# load playlist data -> determine date and eliminate unnecessary data ->
	# parse data into playlist -> send playlist data to event handler ->
	# begin update script

	print("starting slideshow...")
	# update/reboot
	sshow()

	print("loading playlist...")
	# load playlist from json file, maybe add xml support too...
	data = load_playlist_data(argv)

	print("creating playlist...")
	# test
	playlist = create_playlist(data)

	print("starting playlist...")
	# events handler
	event_handler(playlist)

	print("starting update scripts...")
	update()
	reboot()

	pass

#-------------------------------------------------------------------------------
# load_playlist_data()
#
# Loads in data from a json file for playlist events. It is recommended that all
# playlists be generated by the packaged rpi-dsign-config.py program
#-------------------------------------------------------------------------------
def load_playlist_data(args):
	# load playlist data
	if len(args) > 0:
		filename = args[0]
	else:
		filename = '/home/pi/rpids/playlists/default.json'

	# try to load playlist or load default, else make new default then load
	check = True;
	while check:
		try:
			f = open(filename)
			raw = f.read()
			data = json.loads(raw)
			f.close()
			check = False;
		except IOError as err:
			print('rpids: could not find playlist "' + filename + '"')
			if filename == '/home/pi/rpids/playlists/default.json':
				# create a new default playlist
				print('rpids: creating new default playlist...')
				data = []
				check = False;
			else:
				print('rpids: reverting to default playlist...')
				filename = '/home/pi/rpids/playlists/default.json'
		except ValueError:
			print("Error: file not formatted correctly")
			data = []
			check = False;
	return data

#-------------------------------------------------------------------------------
# create_playlist()
#
# analyzes the current date compared to the dates of the event data loaded from
# a playlist file, then eliminates entries that are not on the current day of
# the week. 
#-------------------------------------------------------------------------------
def create_playlist(data):
	playlist = []

	today = datetime.datetime.today()

	for event in data:

		day = event['start_day']
		dayofweek = 0

		# set day of the week for event
		if day == "Mon":
			dayofweek = 0
		elif day == "Tue":
			dayofweek = 1
		elif day == "Wed":
			dayofweek = 2
		elif day == "Thu":
			dayofweek = 3
		elif day == "Fri":
			dayofweek = 4
		elif day == "Sat":
			dayofweek = 5
		elif day == "Sun":
			dayofweek = 6

		# removes events not on the current day
		if today.weekday() != dayofweek:
			continue

		playlist.append(event_time(int(event['start_hour']),
			int(event['start_min']),
			int(event['start_sec']),
			event))

	playlist.sort()

	for i in playlist:
		print(" -> " + str(i))

	return playlist

#-------------------------------------------------------------------------------
# time_between()
#
# gets the total number of seconds between two datetime objects. 
# should be in the form: FIRST - SECOND (so FIRST is in the future usually)
#-------------------------------------------------------------------------------
def time_between(t1, t2):
	t = datetime.timedelta(
		days=(t1.day-t2.day),
		hours=(t1.hour-t2.hour), 
		minutes=(t1.minute-t2.minute),
		seconds=(t1.second-t2.second))

	return t.total_seconds()

#-------------------------------------------------------------------------------
# sleep()
#
# Makes the process sleep until slightly before the desired time to minimize
# CPU utilization. Called by the event handler.
#-------------------------------------------------------------------------------
def sleep(t):
	if t > 0:
		time.sleep(t)
		return True
	return False

#-------------------------------------------------------------------------------
# event_time()
#
# Generates a timestamp for the next occurance of a specific day of the week
# and time of day. Called by the event handler.
#-------------------------------------------------------------------------------
def event_time(hour, minute, sec, data):
	# compute next day
	today = datetime.datetime.today()

	# create event time
	event = datetime.datetime(today.year, today.month, today.day, hour, minute, sec)

	return Event(event, data)

#-------------------------------------------------------------------------------
# event_handler()
#
# Handles the event playlist that was previously generated from reading the
# json formatted playlist file.
#-------------------------------------------------------------------------------
def event_handler(events):
	for event in events:
		next_event = time_between(event.time, datetime.datetime.today())
		print("next event: " + str(event))
		print("sleeping (until: " + str(next_event - 30) + " sec)...")
		sleep(next_event - 30)
		while event.time > datetime.datetime.today():
			sleep(1)
			print("...waiting...")
			pass
		print("<<event (" + str(event) + ") @ " + str(datetime.datetime.today()) + ">>")

		# very insecure line of code... looking for a fix.
		subprocess.call(["sh", "-c", "/home/pi/rpids/scripts/video.sh", event.data['source']], shell=True)
	pass

#-------------------------------------------------------------------------------
# update()
#
# An overloaded method for setting up and running the update script
#-------------------------------------------------------------------------------
def update(config_file='/home/pi/rpids/config/default.ini'):
	# check specified file for FTP -> host/user/pass
	# config = configparser.ConfigParser()
	# try:
	# 	config.read(config_file)
	# except FileNotFoundError as err:
	# 	print(str(err))
	# 	usage()
	# 	sys.exit(2)

	print("waiting to update...")
	# compute next day
	today = datetime.datetime.today()

	# create event time
	update = datetime.datetime(today.year, today.month, today.day, 0, 0, 0)

	update += datetime.timedelta(days=1)
	next_event = time_between(update, datetime.datetime.today())
	print(" - next update: " + str(update))
	print(" - sleeping (until: " + str(next_event) + " sec)...")
	sleep(next_event)
	while update.time > datetime.datetime.today():
		sleep(1)
		print("...waiting...")
		pass
	print("<<update @ " + str(datetime.datetime.today()) + " from " 
		+ config.get('FTP','host') + ">>")

	config = ConfigParser.ConfigParser()
	config.read(config_file)
	_update(config.get('FTP','host'), config.get('FTP','user'), config.get('FTP','pass'))
	pass

def _update(ftp_host, ftp_user, ftp_pass):
	# run the update script
	subprocess.call(
		["sh", "-c", "/home/pi/rpids/scripts/update.sh", 
		ftp_host, 
		ftp_user, 
		ftp_pass], 
		shell=True)
	pass

# script handler for slideshow
def sshow():
	# global sshow_pid
	# if sshow_pid == 0:
	# 	sshow_pid = os.fork()
 #        if sshow_pid == 0:
	# 		# subprocess.call(["sh", "-c", "/home/pi/rpids/scripts/sshow.sh"], shell=True)
	# 		print("rpids: sshow exiting...")
	# 		sys.exit(1)
	# else:
	# 	# os.kill(sshow_pid, signal.SIGTERM)
	# 	# sshow()
	# 	pass

	os.system("sh /home/pi/rpids/scripts/sshow.sh")
	print("rpids: sshow started; continuing...")
	pass

# begins a reboot of the system
def reboot():
	if sshow_pid != 0:
		os.kill(sshow_pid, signal.SIGTERM)

	subprocess.call(["sudo", "reboot"], shell=True)
	pass

#-------------------------------------------------------------------------------
# log()
#
# logs information to the log.txt file
#-------------------------------------------------------------------------------
def log(str):
	try:
		f = open('/home/pi/rpids/log/log.txt', 'a')
	except FileNotFoundError as err:
		f = open('/home/pi/rpids/log/log.txt', 'w')
	f.write(datetime.datetime.today().__format__("[%x %X]: ") + str + "\n")
	f.close()
	pass

#-------------------------------------------------------------------------------
# Event class
#
# Represents an event, has a specific time associated with it and a payload to
# store necessary data about the event.
#-------------------------------------------------------------------------------
class Event:
	def __init__(self, time, data):
		self.time = time
		self.data = data

	def __eq__(self, other):
		return self.time == other.time

	def __gt__(self, other):
		return self.time > other.time

	def __lt__(self,other):
		return self.time < self.time

	def __str__(self):
		return str(self.time) + " " + str(self.data['source'])


if __name__ == '__main__':
	main(sys.argv[1:])




