#!/usr/bin/python
# -*- coding: utf-8 -*-

## Copyright © 2014 by Matthias Urlichs <matthias@urlichs.de>.
##
## 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/>.

from __future__ import print_function
__version__ = (0,1,4)

import sys, os, time
from flask import Flask, Response
from io import BytesIO
from threading import Thread,Event,Condition
from signal import signal,SIGINT
from Queue import Queue
from six import next

class Image(object):
	def __init__(self,data):
		self.data = data

	@property
	def headers(self):
		return {
			'X-Timestamp': time.time(),
			'Content-Length': len(self.data),
			#FIXME: mime-type must be set according file content
			'Content-Type': 'image/jpeg',
			'Content-Disposition': 'inline',
			#'Pragma': 'no-cache',
			#'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
		}

	@property
	def body(self):
		return self.data

class BasicCamStream(object):
	cam = None
	current_image = None
	boundary = '--boundary'
	clients = None
	client_lock = None
	reader = None

	def __init__(self):
		self.client_lock = Condition()
		self.clients = set()
		self.reader = Thread(target=self._server)
		self.reader.start()

	def stop(self):
		with self.client_lock:
			cl,self.clients = self.clients,None
			for c in cl:
				if not c.full():
					c.put(None)
			self.client_lock.notify_all()
		self.reader.join()
		if hasattr(self.cam,'close'):
			self.cam.close()
		self.cam = None

	def __delete__(self):
		self.stop()

	@property
	def headers(self):
		return {
			'Cache-Control': 'no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0',
			'Content-Type': 'multipart/x-mixed-replace;boundary=%s' % self.boundary,
			'Expires': 'Mon, 3 Jan 2000 12:34:56 GMT',
			'Pragma': 'no-cache',
		}

	def _server(self):
		getter = self._get_image()

		while True:
			with self.client_lock:
				while not self.clients:
					if self.clients is None:
						return
					self.current_image = None
					self.client_lock.wait()

			self.current_image = next(getter)
			with self.client_lock:
				progress = False
				if self.clients is None:
					return
				for q in self.clients:
					if not q.full():
						q.put(self.current_image)
						progress = True
				if not progress:
					self.client_lock.wait()

	@property
	def feed(self):
		return Feed(self)

	def _get_image(self):
		# This is a basic 16x16 image (black fuzzy dot on white background)
		img = Image(
			b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff'\
			b'\xdb\x00C\x00\x07\x05\x05\x06\x05\x04\x07\x06\x06\x06\x08\x07\x07'\
			b'\x08\x0b\x12\x0b\x0b\n\n\x0b\x16\x0f\x10\r\x12\x1a\x16\x1b\x1a\x19'\
			b'\x16\x19\x18\x1c ("\x1c\x1e&\x1e\x18\x19#0$&*+-.-\x1b"251,5(,-,\xff'\
			b'\xdb\x00C\x01\x07\x08\x08\x0b\t\x0b\x15\x0b\x0b\x15,\x1d\x19\x1d,,'\
			b',,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,\xff\xc2\x00\x11'\
			b'\x08\x00\x10\x00\x10\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\xff'\
			b'\xc4\x00\x16\x00\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
			b'\x00\x00\x00\x00\x00\x02\x07\xff\xc4\x00\x14\x01\x01\x00\x00\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00'\
			b'\x0c\x03\x01\x00\x02\x10\x03\x10\x00\x00\x01\xd0\x01\x00\xff\xc4'\
			b'\x00\x16\x10\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'\
			b'\x00\x00\x00\x03\x04\x02\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02'\
			b'g\xc4\xe6/\x8a\x0e\x99\xb1QM6%/\xff\xc4\x00\x14\x11\x01\x00\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xff\xda\x00'\
			b'\x08\x01\x03\x01\x01?\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xff\xda\x00\x08'\
			b'\x01\x02\x01\x01?\x01\x1f\xff\xc4\x00\x1a\x10\x00\x02\x02\x03\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x02\x11\x12'\
			b'\x131\xff\xda\x00\x08\x01\x01\x00\x06?\x02\xcen\x91\x9c\x1d\xa3\\'\
			b'\xf8k\x87\x0f\xff\xc4\x00\x16\x10\x01\x01\x01\x00\x00\x00\x00\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x11\xff\xda\x00\x08\x01'\
			b'\x01\x00\x01?!a\x89\x88jd#\xae\x028o\xff\xda\x00\x0c\x03\x01\x00'\
			b'\x02\x00\x03\x00\x00\x00\x10\x00\x0f\xff\xc4\x00\x14\x11\x01\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xff\xda'\
			b'\x00\x08\x01\x03\x01\x01?\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 \xff\xda\x00'\
			b'\x08\x01\x02\x01\x01?\x10\x1f\xff\xc4\x00\x16\x10\x01\x01\x01\x00'\
			b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x001\xff\xda'\
			b'\x00\x08\x01\x01\x00\x01?\x10+\xfbV+\xf9RkZBkXF\xff\xd9'
		)
		while True:
			time.sleep(1)
			yield img

class PiCamStream(BasicCamStream):
	def __init__(self):
		from picamera import PiCamera
		self.cam = PiCamera()
		super(PiCamStream,self).__init__()

	def _get_image(self):
		s = BytesIO()
		for foo in self.cam.capture_continuous(s, format='jpeg'):
			s.truncate()
			s.seek(0)
			yield Image(s.getvalue())

pyg = None
class WebCamStream(BasicCamStream):
	fn=None
	def __init__(self,dev="video0"):
		global pyg
		if pyg is None:
			import pygame
			import pygame.camera
			pyg = pygame

		pyg.camera.init()
		#pygame.camera.list_camera() #Camera detected or not
		self.cam = pyg.camera.Camera("/dev/"+dev,(640,480))
		self.cam.start()
		self.fn = "/run/user/{}/{}.jpg".format(os.getuid(),dev)
		super(WebCamStream,self).__init__()

	def _get_image(self):
		while True:
			img = self.cam.get_image()
			pyg.image.save(img,self.fn)
			with open(self.fn,"r") as f:
				yield Image(f.read())

class Feed(object):
	def __init__(self,cam):
		self.cam = cam
		self.q = Queue(1)

	def __enter__(self):
		with self.cam.client_lock:
			if self.cam.clients:
				self.q.put(self.cam.current_image)
			self.cam.clients.add(self.q)
			self.cam.client_lock.notify()
		return self
	
	def __iter__(self):
		return self
	def __next__(self):
		with self.cam.client_lock:
			self.cam.client_lock.notify()
			if self.cam.clients is None:
				raise StopIteration
		res = self.q.get()
		if res is None:
			raise StopIteration
		return res
	next=__next__

	def __exit__(self, a,b,c):
		with self.cam.client_lock:
			if self.cam.clients is not None:
				self.cam.clients.remove(self.q)

	@property
	def image(self):
		return self.next()

class CamServer(object):
	def __init__(self, cam, app):
		self.cam = cam
		self.init_app(app)
	def init_app(self, app, prefix=""):
		@app.route('/')
		def hello_world():
			return """\
<!DOCTYPE HTML>
<html><head><title>The Camera</title></head>
<body><img src="/stream" /></body></html>
"""

		@app.route(prefix+'/stream')
		def stream_me():
			def generate():
				with cam.feed as f:
					yield cam.boundary
					yield "\r\n"
					for img in f:
						for k, v in img.headers.items():
							yield "%s: %s\r\n" % (k,v)
						yield "\r\n"
						yield img.data
						yield "\r\n"
						yield cam.boundary
						yield "\r\n"

			res = Response(generate())
			for k, v in cam.headers.items():
				res.headers[k] = v
			res.headers['Connection'] = 'close' # only when streaming
			return res
			
		@app.route('/pic')
		def pic_me():
			with cam.feed as f:
				img = f.image
			res = Response(img.data)
			for k, v in img.headers.items():
				res.headers[k] = v
			res.headers['Connection'] = 'keep-alive' # when not streaming
			return res
	
def get_out(*a,**k):
	cam.stop()
	sys.exit()

def usage():
	print("Usage: {} pi|dummy|videoX".format(sys.argv[0]), file=sys.stderr)
	sys.exit(1)

if __name__ == '__main__':
	if len(sys.argv) != 2:
		usage()
	cam = sys.argv[1]
	if cam == "pi":
		cam = PiCamStream()
	elif cam.startswith("video"):
		cam = WebCamStream(cam)
	elif cam == "dummy":
		cam = BasicCamStream()
	else:
		usage()

	app = Flask(__name__)

	signal(SIGINT,get_out)
	c=CamServer(cam,app)
	app.run(host="0.0.0.0", port=50381, threaded=True, debug=False, use_reloader=False)
	# Threading _must_ be on, otherwise this code deadlocks.
	# The reloader _must_ be off, otherwise it'll try to open the camera
	# multiple times, which doesn't always work.
	# to work.

