#!/usr/bin/python

import sys, os, inspect
import pytz, datetime
from time import sleep
from PIL import Image
from shutil import copyfile
from math import floor
import RPi.GPIO as GPIO

import traceback
import picamera
import pygame
import random
import time
import json



__all__ = ["monotonic_time"]

import ctypes

CLOCK_MONOTONIC_RAW = 4 # see <linux/time.h>

class timespec(ctypes.Structure):
	_fields_ = [
		('tv_sec', ctypes.c_long),
		('tv_nsec', ctypes.c_long)
	]

librt = ctypes.CDLL('librt.so.1', use_errno=True)
clock_gettime = librt.clock_gettime
clock_gettime.argtypes = [ctypes.c_int, ctypes.POINTER(timespec)]

def monotonic_time():
	t = timespec()
	if clock_gettime(CLOCK_MONOTONIC_RAW , ctypes.pointer(t)) != 0:
		errno_ = ctypes.get_errno()
		raise OSError(errno_, os.strerror(errno_))
	return t.tv_sec + t.tv_nsec * 1e-9



# CONFIGURATION
WIDTH			= 800
HEIGHT			= 480
FONTSIZE		= 500
PIC_INTERVAL	= 0.5
TEXT_INTERVAL	= 0.5
TEXT_POS		= (250, -40)
OVERLAY_ALPHA	= 64
COLOR_BACK		= pygame.Color(0, 0, 0)
COLOR_FLASH		= pygame.Color(255, 255, 255)
GRAPHICS_DIR	= "./graphics"
BASE_PATH		= "/var/lib/photobox"
COUNTDOWN		= 3
SHOT_COUNT		= 3
BORDER			= 10

THUMB_WIDTH		= int((800 - BORDER) / SHOT_COUNT - BORDER)
THUMB_HEIGHT	= int(THUMB_WIDTH / 1.333)
CONFIG_LOCATION	= "%s/config.json" % (BASE_PATH, )



os.chdir(os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))))



# global variables
session_start	= monotonic_time()



# INIT GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO.setup(7, GPIO.OUT)
GPIO.output(7, GPIO.LOW)
sleep(0.02)
GPIO.output(7, GPIO.HIGH)
sleep(0.02)
GPIO.output(7, GPIO.LOW)
sleep(0.02)
GPIO.output(7, GPIO.HIGH)
sleep(0.02)
GPIO.output(7, GPIO.LOW)
sleep(0.02)
GPIO.output(7, GPIO.HIGH)
sleep(0.02)
GPIO.output(7, GPIO.LOW)



# INIT PYGAME
pygame.init()
pygame.mixer.quit()
pygame.mouse.set_visible(False)
screen = pygame.display.set_mode((WIDTH,HEIGHT))



# LOAD OVERLAYS
loading		 = pygame.image.load(GRAPHICS_DIR + "/loading.png")
loading_rect	= loading.get_rect()

begin		   = pygame.image.load(GRAPHICS_DIR + "/begin.png")
begin_rect	  = begin.get_rect()

yes			 = pygame.image.load(GRAPHICS_DIR + "/yes.png")
yes_rect		= yes.get_rect()
yes_rect.y	  = 340

no			  = pygame.image.load(GRAPHICS_DIR + "/no.png")
no_rect		 = no.get_rect()
no_rect.y	   = 340

end_yes		 = pygame.image.load(GRAPHICS_DIR + "/end_yes.png")
end_yes_rect	= end_yes.get_rect()

end_no		  = pygame.image.load(GRAPHICS_DIR + "/end_no.png")
end_no_rect	 = end_no.get_rect()

saved		   = pygame.image.load(GRAPHICS_DIR + "/saved.png")
saved_rect	  = saved.get_rect()

canceled		= pygame.image.load(GRAPHICS_DIR + "/canceled.png")
canceled_rect   = canceled.get_rect()

#~ img_info = Image.open(GRAPHICS_DIR + '/info.png')



# LOAD COUNTDOWN OVERLAYS
img_cdn = []
pad_cdn = []
for i in range(0, COUNTDOWN):
	img = Image.open(GRAPHICS_DIR + "/%s.png" % (COUNTDOWN - i))
	pad = Image.new('RGB', (
		((img.size[0] + 31) // 32) * 32,
		((img.size[1] + 15) // 16) * 16,
	))
	pad.paste(img, (0, 0))
	img_cdn.append(img)
	pad_cdn.append(pad)



# LOAD INFORMATION OVERLAY
img_info = Image.open(GRAPHICS_DIR + '/info.png')
pad_info = Image.new('RGB', (
	((img_info.size[0] + 31) // 32) * 32,
	((img_info.size[1] + 15) // 16) * 16,
))
pad_info.paste(img_info, (0, 0))



# FUNCTIONS ========================================================================================

def waitForEvent():
	pygame.event.clear()
	while 1:
		sleep(0.25)
		events = pygame.event.get()
		if len(events) > 0:
			for event in events:
				if event.type == pygame.QUIT:
					sys.exit(0)
			return events



def waitForTouch():
	pos = (-1, -1)
	while True:
		events = waitForEvent()
		for event in events:
			if event.type == pygame.MOUSEMOTION:
				pos = event.pos
			elif event.type == pygame.MOUSEBUTTONUP:
				return pos



def maintainance():
	screen.fill(COLOR_BACK)
	screen.blit(maintain, maintain_rect)
	pygame.display.flip()
	
	# wait
	pos = waitForTouch()
	



def waitForBegin():
	screen.fill(COLOR_BACK)
	screen.blit(begin, begin_rect)
	pygame.display.flip()
	
	# wait
	pos = waitForTouch()
	if pos[0] > 720 and pos[1] > 400:
		maintainance()
	


def countdown(camera, pad, img, flash):
	sleep(PIC_INTERVAL)
	o = camera.add_overlay(pad.tostring(), size=img.size)
	o.alpha = OVERLAY_ALPHA
	o.layer = 3
#	if flash:
#		GPIO.output(7, GPIO.HIGH)
#		sleep(0.01)
#		GPIO.output(7, GPIO.LOW)
	sleep(TEXT_INTERVAL)
	camera.remove_overlay(o)



def makePhotos():
	# CLEAR SCREEN
	screen.fill(COLOR_BACK)
	pygame.display.update()
	
	# INIT CAMERA
	camera = picamera.PiCamera()
	camera.vflip = False
	camera.hflip = True
	camera.resolution = (2592, 1944)
	camera.rotation = 90
	
	# PREVIEW
	camera.start_preview()
	o = camera.add_overlay(pad_info.tostring(), size=img_info.size)
	o.alpha = 255
	o.layer = 3
	sleep(3)
	camera.remove_overlay(o)
	sleep(1)
	
	# COUNTDOWN
	GPIO.output(7, GPIO.HIGH)
	for i in range(0, COUNTDOWN):
		countdown(camera, pad_cdn[i], img_cdn[i], True)
	GPIO.output(7, GPIO.LOW)
	
	# SHOTS
	for i in range(0, SHOT_COUNT):
		# SHOT!
		sleep(PIC_INTERVAL)
		GPIO.output(7, GPIO.HIGH)
		screen.fill(COLOR_FLASH)
		pygame.display.update()
		camera.stop_preview()
		camera.capture("./tmp%s.jpg" % i, format='jpeg', quality=100, thumbnail=None)
		GPIO.output(7, GPIO.LOW)
	
		# PAUSE
		if i < SHOT_COUNT - 1:
			screen.fill(COLOR_BACK)
			pygame.display.update()
			camera.start_preview()
			sleep(TEXT_INTERVAL)
	
	# CLOSE CAMERA
	camera.close()
	


def chooseImages():
	# clear screen
	screen.fill(COLOR_BACK)
	screen.blit(loading, loading_rect)
	pygame.display.update()
	
	# load images from disk
	img_tmp = []
	rect_tmp = []
	choices = []
	x = 12
	for i in range(0, SHOT_COUNT):
		img	 = pygame.image.load("tmp%s.jpg" % i)
		img	 = pygame.transform.scale(img, (THUMB_WIDTH, THUMB_HEIGHT))
		rect	= img.get_rect()
		rect.x  = x
		rect.y  = 146
		
		img_tmp.append(img)
		rect_tmp.append(rect)
		choices.append(0)
		
		x = x + THUMB_WIDTH + BORDER
	
	
	save = False
	cancel = False
	any_choice = False
	while not cancel and (not save or not any_choice):
		# reset save (due to no choice made)
		save = False
		
		# draw background
		screen.fill(COLOR_BACK)
		if any_choice:
			screen.blit(end_yes, end_yes_rect)
		else:
			screen.blit(end_no, end_no_rect)
		
		# draw images
		for i in range(0, SHOT_COUNT):
			screen.blit(img_tmp[i], rect_tmp[i])
		
		# draw choices
		x = 12
		for choice in choices:
			if choice > 0:
				yes_rect.x  = x + (THUMB_WIDTH - yes_rect.width) / 2
				screen.blit(yes, yes_rect)
			else:
				no_rect.x   = x + (THUMB_WIDTH - no_rect.width) / 2
				screen.blit(no, no_rect)
			
			x = x + THUMB_WIDTH + BORDER
		
		# update display
		pygame.display.flip()
		
		# wait for new event (touch screen / mouse / keyboard)
		pos = waitForTouch()
		if pos[1] > 140:
			# toggle image
			i = int(floor((pos[0] - BORDER / 2) / (THUMB_WIDTH + BORDER)))
			if i >= 0 and i < SHOT_COUNT:
				choices[i] = (choices[i] + 1) % 2
		elif pos[1] < 100:
			# top menu buttons
			if pos[0] > 0 and pos[0] < 266:
				cancel = True
			elif pos[0] > 533 and pos[0] < 800 and (choices[0] > 0 or choices[1] > 0 or choices[2] > 0):
				save = True
		
		any_choice = False
		for choice in choices:
			if choice > 0:
				any_choice = True
				break
		
	# END: choice
	#~ print("# END: choice")
	
	return (choices, save)
	


def saveImages(choices):
	local = datetime.datetime.now()
	session_age = (monotonic_time() - session_start)
	
	print("saving images...")
	for i in range(0, SHOT_COUNT):
		if choices[i] > 0:
			print("  saving image #%s" % i)
			
			EVENT_PATH = "%s/events/%s" % (BASE_PATH, EVENT_ID)
			
			try:
				os.makedirs(EVENT_PATH + "/original")
			except OSError:
				pass
			
			try:
				os.makedirs(EVENT_PATH + "/todo")
			except OSError:
				pass
			
			filename = "%s-%s-session_age=%05d.jpg" % (local.strftime("%Y%m%dT%H:%M:%S.%f%z"), i, session_age)
			os.rename("tmp%s.jpg" % i, EVENT_PATH + "/original/" + filename)
			copyfile(EVENT_PATH + "/original/" + filename, EVENT_PATH + "/todo/" + filename)
		
	# show message
	screen.fill(COLOR_BACK)
	screen.blit(saved, saved_rect)
	pygame.display.flip()
	sleep(2)



def loadConfig():
	global EVENT_ID
	
	try:
		if os.path.isfile(CONFIG_LOCATION):
			config = open(CONFIG_LOCATION, "r").read()
			config = json.loads(config)
			
			EVENT_ID = int(config["id"])
		
	except:
		traceback.print_exc()
		
		EVENT_ID = -1



EVENT_ID = -1

while 1:
	waitForBegin()
	makePhotos()
	(choices, save) = chooseImages()
	
	loadConfig()
	if save:
		saveImages(choices);
	else:
		# show message
		screen.fill(COLOR_BACK)
		screen.blit(canceled, canceled_rect)
		pygame.display.flip()
		sleep(2)

pygame.quit()
