#!/usr/bin/python3
"""

"""
import time, atexit, re, flask
from flask import Flask, render_template, request, json, jsonify
import RPi.GPIO as GPIO
from tinydb import TinyDB, Query

app = Flask(__name__)

@app.route('/')
def home():
	return render_template('index.html')

###############################################

@app.route('/<device_id>/on', methods=['GET', 'POST'])
def switchOn(device_id):
	device_id = validate_device_id(device_id)
	print('turned ', device_id, ' on')
	remoteSwitch.switchOn(device_id)
	db.switch_state(device_id, 'on')
	return 'switched on'

@app.route('/<device_id>/off', methods=['GET', 'POST'])
def switchOff(device_id):
	device_id = validate_device_id(device_id)
	print('turned ', device_id, ' off')
	remoteSwitch.switchOff(device_id)
	db.switch_state(device_id, 'off')
	return 'switched off'

###############################################

@app.route('/<device_id>/add', methods=['POST'])
def add(device_id):
	device_id = validate_device_id(device_id)
	if is_authorized(request):
		if request.headers['Content-Type'] == 'application/json':
			content = request.json
			if 'name' not in content: flask.abort(400)
			name = content['name']
			if 'state' in content:
				state = content['state']
				if state == 'on' or state == 'off':
					return db.add(device_id, name, state)
				else:
					flask.abort(400)
			else:
				return db.add(device_id, name)
		else:
			flask.abort(400)

@app.route('/<device_id>/remove', methods=['POST'])
def remove(device_id):
	device_id = validate_device_id(device_id)
	if is_authorized(request):
		return db.remove(device_id)

@app.route('/list', methods=['POST'])
def list():
	if is_authorized(request):
		devices_list = db.list()
		return jsonify(devices_list)


#######################################################################

def validate_device_id(device_id):
	device_id = device_id[0:5]+device_id[5].upper()
	if not device_regex.match(device_id):
		print('device id not matching regex')
		flask.abort(400)
	return device_id


def is_authorized(request):
	if request.headers['Content-Type'] == 'application/json':
		newInput = request.json
		if 'secret' in newInput and newInput['secret'] == secret:
			print('request authorized')
			return True
	elif request.args['secret'] == secret:
		print('request authorized')
		return True
	else:
		flask.abort(550)


def cleanup():
	GPIO.cleanup()

#######################################################################

class RemoteSwitch(object):
	repeat = 10 # Number of transmissions
	pulselength = 300 # microseconds
	GPIOMode = GPIO.BCM
	
	def __init__(self, pin):
		self.pin = pin
		''' 
		devices: A = 1, B = 2, C = 4, D = 8, E = 16  
		key: according to dipswitches on your Elro receivers
		pin: according to Broadcom pin naming
		'''		
		self.device_letter = { "A":1, "B":2, "C":4, "D":8, "E":16, "F":32, "G":64 } 

		GPIO.setmode(self.GPIOMode)
		GPIO.setup(self.pin, GPIO.OUT)
		
	def switchOn(self, device_id):
		key = [int(device_id[0]),int(device_id[1]),int(device_id[2]),int(device_id[3]),int(device_id[4])]
		device = self.device_letter[device_id[5]]
		self._switch(GPIO.HIGH, key, device)

	def switchOff(self, device_id):
		key = [int(device_id[0]),int(device_id[1]),int(device_id[2]),int(device_id[3]),int(device_id[4])]
		device = self.device_letter[device_id[5]]
		self._switch(GPIO.LOW, key, device)

	def _switch(self, switch, key, device):
		self.bit = [142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 142, 136, 128, 0, 0, 0]		

		for t in range(5):
			if key[t]:
				self.bit[t]=136	
		x=1
		for i in range(1,6):
			if device & x > 0:
				self.bit[4+i] = 136
			x = x<<1

		if switch == GPIO.HIGH:
			self.bit[10] = 136
			self.bit[11] = 142
				
		bangs = []
		for y in range(16):
			x = 128
			for i in range(1,9):
				b = (self.bit[y] & x > 0) and GPIO.HIGH or GPIO.LOW
				bangs.append(b)
				x = x>>1
				
		GPIO.output(self.pin, GPIO.LOW)
		for z in range(self.repeat):
			for b in bangs:
				GPIO.output(self.pin, b)
				time.sleep(self.pulselength/1000000.)

#######################################################################

class Database:

	def __init__(self, file):
		self.tinydb = TinyDB(file)
		self.devices_table = self.tinydb.table('devices')
		self.Device = Query()

	def get_eid(self, device_id):
		el_list = self.devices_table.search(self.Device.device == device_id)
		if len(el_list) < 1: return False 	# nothing found
		eid = el_list[0].eid
		print(device_id,' already exists in db with eid ',eid)
		return eid

	def add(self, device_id, name, state='off'):
		eid = self.get_eid(device_id)
		if eid is False:
			self.devices_table.insert({'device':device_id,'name':name,'state':state})
			print('added: ',device_id,', ',name,', state: ', state)
			return 'added'
		else:
			self.devices_table.update({'name':name,'state':state}, eids=[eid])
			print('updated: ',device_id,', ',name,', ', state)
			return 'updated'

	def remove(self, device_id):
		eid = self.get_eid(device_id)
		if eid is False: flask.abort(400) # does not exist
		self.devices_table.remove(eids=[eid])
		print('removed: ',device_id)
		return 'removed'

	def list(self):
		print('sending list ...')
		return self.devices_table.all()

	def switch_state(self, device_id, state):
		eid = self.get_eid(device_id)
		if eid is False: return False # does not exist
		self.devices_table.update({'state':state}, eids=[eid])
		return True


#######################################################################

if __name__ == '__main__':
	atexit.register(cleanup)
	
	default_GPIO_pin = 17					# change the GPIO pin according to your wiring
	remoteSwitch = RemoteSwitch(pin=default_GPIO_pin)

	secret = 'test' 					# TODO: changable

	device_regex = re.compile("[01]{5}[A-G]")	# regex to test device indentifiers

	db = Database(file='db.json')
	app.run(host='0.0.0.0', port=80)
