#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# This program is copyright (c) 2016, P. Lutus and is released
# under the GPL (http://www.gnu.org/licenses/gpl-3.0.en.html).

import re, sys, cgi, cgitb, random, math

# enable error display and tracing

cgitb.enable()

print("Content-type:text/html\r\n\r\n")

# try to import Raspberry Pi GPIO library

try:
  import RPi.GPIO as G
except:
  G = False

# try to import Insteon library extension
  
try:
  import insteon_controller_extension as ice
except:
  ice = False

if(not G and not ice):
  print('<h4>Error: no GPIO extension library and no Insteon library.</h4>')
  quit()
              
class MyCgiHandler():
  
  def __init__(self):

    # this remote control button list
    # can be any length <= 25
    # and have any desired short names
    self.button_list = (
      'Lamp A','Lamp B','Lamp C','Lamp D',
      'Lamp E','Lamp F','Lamp G','Lamp H',
      'Lamp I','Lamp J','Lamp K','Lamp L',
      'Lamp M','Lamp N','Lamp O','Lamp P',
    )

    self.page_refresh_interval = 2 # seconds
    form = cgi.FieldStorage()
    self.button_tag = 'BTN'
    self.all_state = 0
    self.button_dic = {k:False for k in self.button_list}
    # map outputs to GPIO pins
    # this list must be at least
    # as long as the button list
    self.gpio_list = tuple(range(2,28))
    self.gpio_len = len(self.gpio_list)
    try:
      assert (len(self.button_dic) <= len(self.gpio_list))
    except:
      print('Error: the button list cannot be longer than the GPIO list.')
      quit()
    # insteon program extension mode
    if(ice):
      self.have_scenes = ice.provide_scenes_length() > 0
      # replace default state dict with extension's dict
      self.mode = ('Devices','Scenes')[self.have_scenes]
      for tag in ('SEL','HID','M'): # selection button, hidden tag, meta arg
        if(tag in form):
          self.mode = form[tag].value
          break
      if(self.mode == 'Devices'):
        self.button_dic = ice.get_device_status()
      else:
        self.mode == 'Scenes'
        self.button_dic = ice.get_scene_status()
    # Raspberry Pi GPIO mode
    if(G):
      # set GPIO mapping mode
      G.setmode(G.BCM)
      G.setwarnings(False)
      # set all channels to output mode
      G.setup(self.gpio_list,G.OUT)
      # if no extension present to provide states
      if(not ice):
        # initialize our state list to present GPIO states
        for n,k in enumerate(sorted(self.button_dic)):
          if(n < self.gpio_len):
            state = G.input(self.gpio_list[n]) == 1
          else:
            state = False
          self.button_dic[k] = state

    self.head_block = """
<title>Device Controller</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0,maximum-scale=1.0,user-scalable=no"/>
<style type="text/css">
html * {
  padding:0;
  margin:0;
  border:0;
  background:#1c2725;
  font-size:4vmin;
}

table {
  table-layout:fixed;
  padding-left:.5vmin;
  padding-top:.5vmin;
  width:99.7vw;
  height:99.7vh;
}

input {
  width:100%%;
  height:100%%;
  padding-left:1vmin;
  padding-right:1vmin;
  cursor: pointer;
  cursor: hand;
  white-space:normal;
}

input:hover {
  opacity: 0.85;
}

td {
  padding:0.2vmin;
  /* computed at runtime */
  height:%d%%;
}

.on { background:#ffffc0; }
.off { background:#f0f0f0; }

.Onbutton {
  background:#c0ffc0;
}
.Offbutton {
  background:#ffc0c0;
}
.modebutton {
  background:#c0e0ff;
}
</style>
"""

    self.process_response(form)
    self.render_page()
    
  def wrap_tag(self,tag,content='',extras = ''):
    if(content): content = '\n' + content
    if(extras): extras = ' ' + extras
    return '<%s%s>%s</%s>\n' % (tag,extras,content,tag)
    
  def render_page(self):
    table_cells = []
    for n,key in enumerate(sorted(self.button_dic)):
      state = self.button_dic[key]
      cls = ('off','on')[state]
      table_cells.append(self.wrap_tag('input',''
        ,'type="submit" class="%s" value="%s" name="%s"'
        % (cls,key,self.button_tag)))
    for tag in ('On','Off'):
      table_cells.append(self.wrap_tag('input',''
        ,'type="submit" value="All %s" name="%s" class="%s"' % (tag,self.button_tag,tag + 'button')))
    if(ice and self.have_scenes):
      for tag in ('Scenes','Devices'):
        table_cells.append(self.wrap_tag('input',''
          ,'type="submit" value="%s" name="%s" class="%s"' % (tag,'SEL','modebutton')))
    table = ''
    # roughly equal number of rows and columns
    # but favoring wider buttons
    row_length = int(math.sqrt(len(table_cells)))
    # track generated rows for layout adjustment
    rows = 0
    while(table_cells):
      row = ''
      for _ in range(row_length):
        if(table_cells):
          cell = table_cells.pop(0)
        else:
          cell = '&nbsp;'
        row += self.wrap_tag('td',cell)
      table += self.wrap_tag('tr',row)
      rows += 1
    page = self.wrap_tag('table',table)
    
    if(ice):
      page += self.wrap_tag('input','','type="hidden" value="%s" name="%s"' % (self.mode,'HID'))
    page = self.wrap_tag('form',page,'method="post"')
    meta_suff = ''
    if(ice):
      meta_suff = ';url=index.py?M=%s' % self.mode
    head = '<meta http-equiv="refresh" content="%d%s"/>' % (self.page_refresh_interval,meta_suff)
    head += self.head_block % (100.0/rows)
    head = self.wrap_tag('head',head)
    page = head + self.wrap_tag('body',page)
    page = self.wrap_tag('html',page)
    print(page)

  def exec_state_change(self,key,state,allf = 0.0):
    self.button_dic[key] = state
    # Raspberry Pi GPIO mode
    if(G):
      n = (sorted(self.button_dic)).index(key)
      if(n < self.gpio_len):
        G.output(self.gpio_list[n],state)
    # program extension mode
    if(ice):
      if(allf):
        if(allf != self.all_state):
          # emit this command just once
          self.all_state = allf
          ice.exec_com('All',state)
      else:
        ice.exec_com(key,state)
    if(verbose):
      print('%s : %s' % (key,('Off','On')[state])) 
      
  # process result of user input
  def process_response(self,form):
    if(self.button_tag in form):
      key = form[self.button_tag].value
      if(key == 'All On' or key == 'All Off'):
        state = (key == 'All On')
        # a scheme to emit the 'All' command only once
        allf = random.random()
        for key in self.button_dic:
          self.exec_state_change(key,state,allf)
      else:
        # get the state of this button
        state = self.button_dic[key]
        # flip the state
        state = not state
        self.exec_state_change(key,state)

if __name__ == "__main__" :
    
  verbose = False
  MyCgiHandler()