#!/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 picamera
import pygame
import random
import time

# ~ import server




__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




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


# 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"
IMAGE_DIR		= "images/0-CURRENT"
COUNTDOWN		= 3
SHOT_COUNT		= 3
BORDER			= 10

GPIO_LED_BORDER	= 7
GPIO_SPOT_RIGHT	= 35
GPIO_SPOT_LEFT	= 37
GPIO_BUTTON_LED	= 36
GPIO_BUTTON		= 38

THUMB_WIDTH		= int((800 - BORDER) / SHOT_COUNT - BORDER)
THUMB_HEIGHT	= int(THUMB_WIDTH / 1.333)

IMAGES_ORIGINAL	= os.path.join(IMAGE_DIR, "original")
IMAGES_TODO		= os.path.join(IMAGE_DIR, "todo")
IMAGES_UPLOAD	= os.path.join(IMAGE_DIR, "upload")



# global variables
spot_mode		= 0
session_start	= monotonic_time()



# GPIO helpers

def led_border(state):
	global GPIO_LED_BORDER
	GPIO.output(GPIO_LED_BORDER, state)


def set_spot_mode(new_mode):
	global spot_mode
	spot_mode = new_mode % 4
	GPIO.output(GPIO_SPOT_LEFT, not (spot_mode & 1))
	GPIO.output(GPIO_SPOT_RIGHT, not (spot_mode & 2))


def next_spot_mode():
	global spot_mode
	set_spot_mode(spot_mode + 1)



# user interface

def button_press(gpio_id):
	next_spot_mode()



# INIT GPIO
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)

GPIO.setup(GPIO_SPOT_RIGHT,		GPIO.OUT,	initial=GPIO.HIGH)
GPIO.setup(GPIO_SPOT_LEFT,		GPIO.OUT,	initial=GPIO.HIGH)
GPIO.setup(GPIO_LED_BORDER,		GPIO.OUT,	initial=GPIO.LOW)
GPIO.setup(GPIO_BUTTON_LED,		GPIO.OUT,	initial=GPIO.LOW)

GPIO.setup(GPIO_BUTTON,			GPIO.IN)
GPIO.add_event_detect(GPIO_BUTTON, GPIO.FALLING, button_press, bouncetime=300)

led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)
sleep(0.02)
led_border(True)
sleep(0.02)
led_border(False)



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



# LOAD GRAPHICS
gfx = {}

def _load(name):
	filename = "%s.png" % name
	path = os.path.join(GRAPHICS_DIR, filename)
	
	img = pygame.image.load(path)
	rect = img.get_rect()
	
	gfx[name] = (img, rect)

_load("begin")
_load("loading")
_load("yes")
_load("no")
_load("end_yes")
_load("end_no")
_load("saved")
_load("canceled")



# LOAD COUNTDOWN OVERLAYS
img_cdn = []
pad_cdn = []
for i in range(0, COUNTDOWN):
	filename = "%s.png" % (COUNTDOWN - i)
	path = os.path.join(GRAPHICS_DIR, filename)
	
	img = Image.open(path)
	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)



# 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(*gfx["begin"])
	pygame.display.flip()
	
	# wait
	pos = waitForTouch()
	# if pos[0] > 720 and pos[1] > 400:
	# 	maintainance()



def countdown(camera, pad, img):
	sleep(PIC_INTERVAL)
	o = camera.add_overlay(pad.tostring(), size=img.size)
	o.alpha = OVERLAY_ALPHA
	o.layer = 3
	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 = 0
	camera.start_preview()
	
	# COUNTDOWN
	led_border(True)
	for i in range(0, COUNTDOWN):
		countdown(camera, pad_cdn[i], img_cdn[i])
	led_border(False)
	
	# SHOTS
	for i in range(0, SHOT_COUNT):
		# SHOT!
		sleep(PIC_INTERVAL)
		led_border(True)
		camera.capture("./tmp%s.jpg" % i, format='jpeg', quality=100, thumbnail=None)
		led_border(False)
	
		# 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(*gfx["loading"])
	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  = 120
		
		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 (if nothing selected)
		save = False
		
		# draw background
		screen.fill(COLOR_BACK)
		if any_choice:
			screen.blit(*gfx["end_yes"])
		else:
			screen.blit(*gfx["end_no"])
		
		# 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:
				(img, rect) = gfx["yes"]
			else:
				(img, rect) = gfx["no"]
			
			rect.x = x + (THUMB_WIDTH - rect.width) / 2
			rect.y = 300
			screen.blit(img, rect)
			
			x = x + THUMB_WIDTH + BORDER
		
		# update display
		pygame.display.flip()
		
		# wait for new event (touch screen / mouse / keyboard)
		pos = waitForTouch()
		if pos[1] < 400:
			# 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] > 420:
			# 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
	
	return (choices, save)
	


def saveImages(choices):
	local = datetime.datetime.now()
	session_age = (monotonic_time() - session_start)
	
	try:
		os.makedirs(IMAGES_ORIGINAL)
	except OSError:
		pass
	
	try:
		os.makedirs(IMAGES_TODO)
	except OSError:
		pass
	
	try:
		os.makedirs(IMAGES_UPLOAD)
	except OSError:
		pass
	
	print("saving images...")
	for i in range(0, SHOT_COUNT):
		if choices[i] > 0:
			print("  saving image #%s" % i)
			
			filename = "%s-%s-session_age=%05d.jpg" % (local.strftime("%Y%m%dT%H:%M:%S.%f%z"), i, session_age)
			
			path_tmp = "tmp%s.jpg" % i
			path_original = os.path.join(IMAGES_ORIGINAL, filename)
			path_todo = os.path.join(IMAGES_TODO, filename)
			path_upload = os.path.join(IMAGES_UPLOAD, filename)
			
			os.rename(path_tmp, path_original)
			copyfile(path_original, path_todo)
			os.rename(path_todo, path_upload)



try:
	while 1:
		waitForBegin()
		
		makePhotos()
		(choices, save) = chooseImages()
		if save:
			saveImages(choices);
			screen.fill(COLOR_BACK)
			screen.blit(*gfx["saved"])
			pygame.display.flip()
		else:
			screen.fill(COLOR_BACK)
			screen.blit(*gfx["canceled"])
			pygame.display.flip()
		
		sleep(2)
	
except KeyboardInterrupt:
	print("")
	
finally:
	led_border(False)
	pygame.quit()
