#!/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).

# this program must have access to a subdirectory
# named "http_cache", world writable

import os, sys, re, fcntl
import urllib.request, base64
import xml.etree.ElementTree as ET

try:
  import http_cache.scenes_devices as ext
except:
  ext = False

verbose = False

# set this to the network name or address of your Insteon controller

topurl = 'http://pl-isy99'

# create this value with: '$ echo "user:password" | base64'

user_pass_base64 = 'YWRtaW46YWRtaW4=' # admin:admin

dict_failed = {}

dict_dir = 'http_cache'

dict_path = os.path.join(dict_dir,'scenes_devices.py')

def fetch_content(suff,need_response = False):
  global topurl
  url = topurl + suff
  url = re.sub(' ','%20',url)
  request = urllib.request.Request(url)
  request.add_header("Authorization", "Basic %s" % user_pass_base64) 
  response = ''
  try:
    response = urllib.request.urlopen(request).read()
    if(need_response):
      response = ET.XML(response)
  except:
    None
  return response

def fetch_conditional(node,tag):
  try:
    v = node.find(tag).text
  except:
    v = None
  return v

def parse_nodes(nodes,memberf = False):
  result = {}
  for node in nodes:
    link = None
    members = False
    name = fetch_conditional(node,'name')
    address =fetch_conditional(node,'address')
    typ = fetch_conditional(node,'type')
    if(memberf):
      members = node.find('members')
    if(members):
      link = members.find('link').text
    if(name != None):  
      if(link != None):
        if(name == 'Devices'):
          result[name] = '0.0.0.0'
        else:
          result[name] = link
      else:
        if(typ == None or (typ != '0.5.0.0' and typ != '16.1.65.0')):
          result[name] = address

  return result

def provide_devices():
  nodes = fetch_content('/rest/nodes/devices',True)
  return parse_nodes(nodes)

def provide_scenes():
  nodes = fetch_content('/rest/nodes/scenes',True)
  result = parse_nodes(nodes)
  return result

def provide_scenes_length():
   return len(ext.dict_scenes)

def read_file_with_locking(path):
  data = False
  if(os.path.isfile(path)):
    with open(path) as f:
      try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        data = f.read()
      finally:
        fcntl.flock(f, fcntl.LOCK_UN | fcntl.LOCK_NB)
  return data

def write_file_with_locking(path,data):
  outcome = False
  with open(path,'w') as f:
    try:
      fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
      f.write(data)
      outcome = True
    finally:
      fcntl.flock(f, fcntl.LOCK_UN | fcntl.LOCK_NB)
  return outcome
  
def control_scene(key,state):
  com = ('DFOF','DFON')[state]
  ids = ext.dict_scenes[key]
  suff = '/rest/nodes/%s/cmd/%s' % (ids,com)
  fetch_content(suff)

def toggle_element(key,dic,state):
  global all_state
  com = ('DFOF','DFON')[state]
  if(key == 'All'):
    control_scene('Devices',state)
  else:
    ids = dic[key]
    suff = '/rest/nodes/%s/cmd/%s' % (ids,com)
    response = fetch_content(suff)
    if(verbose):
      print(response)

def parse_status(nodes,dic):
  global dict_failed
  result = {}
  for node in nodes:
    id = node.attrib['id']
    if(id in dic):
      key = dic[id]
      nc = node.getchildren()
      value = nc[0].attrib['value']
      result[key] = (value != '0' and value != ' ')
  return result

def get_device_status():
  status = fetch_content('/rest/status',True)
  return parse_status(status,ext.dict_dev_ids)

def get_scene_status():
  status = fetch_content('/rest/status',True)
  return parse_status(status,ext.dict_scene_ids_b)

def format_dic(name,dic, reverse = False):
  result = '%s = {\n' % name
  for key in sorted(dic):
    if(reverse):
      result += '  \'%s\' : \'%s\',\n' % (dic[key],key)
    else:
      result += '  \'%s\' : \'%s\',\n' % (key,dic[key])
  result += '}\n\n'
  return result

def generate_dict_file():
  if(not os.access(dict_dir, os.W_OK)):
    print('<h4>Error: directory %s either not present or not writable.</h4>' % dict_dir)
    return
  data = fetch_content('/rest/nodes/devices',True)
  devices = parse_nodes(data)

  data = fetch_content('/rest/nodes/scenes',True)
  scenes = parse_nodes(data)

  # this contains device addresses instead of scene addresses

  scenes_b = parse_nodes(data,True)

  s_devices = format_dic('dict_devices',devices)
  s_dev_ids = format_dic('dict_dev_ids',devices,True)

  s_scenes = format_dic('dict_scenes',scenes)
  s_scene_ids = format_dic('dict_scene_ids',scenes,True)

  s_scene_ids_b = format_dic('dict_scene_ids_b',scenes_b,True)

  data = s_devices+s_dev_ids+s_scenes+s_scene_ids+s_scene_ids_b

  header = '#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n\n'
  comment = '# generated by %s\n\n' % os.path.abspath(__file__)
  content = header+comment+data
  write_file_with_locking(dict_path,content)

def read_dicts():
  global ext
  if(not ext):
    generate_dict_file()
    import html_cache.scenes_devices as ext
  
def exec_com(key,state):
  if(verbose):
    print('command: "%s" %s' % (key,state))
  if(key in ext.dict_scenes):
    toggle_element(key,ext.dict_scenes,state)
  else:
    toggle_element(key,ext.dict_devices,state)

funct_dic = {
  '--scenes' : provide_scenes,
  '--devices' : provide_devices,
  '--sstatus' : get_scene_status,
  '--dstatus' : get_device_status,
}

read_dicts()

if __name__ == "__main__" :

  args = sys.argv[1:]
  
  key = False
  
  for arg in args:
    if(key):
        exec_com(key,arg.lower() == 'true' or arg == '1')
        key = False
    elif(arg in funct_dic):
      print(funct_dic[arg]())
    else:
      key = arg