###########################################################################
# Copyright (c) 2018- Franco (nextime) Lanza <franco@nexlab.it>
#
# Penguidom System client Daemon "penguidomd"  [https://git.nexlab.net/domotika/Penguidom]
#
# This file is part of penguidom.
#
# penguidom 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 mapping import EVENTMAP, REGISTERS

import ctypes
c_uint8 = ctypes.c_uint8

class LowNibble_bits( ctypes.LittleEndianStructure ):
    _fields_ = [
                ("evt_disable",             c_uint8, 1 ),  
                ("alarm",            c_uint8, 1 ), 
                ("winload",              c_uint8, 1 ),  
                ("neware",        c_uint8, 1 ),  
               ]

class Flags( ctypes.Union ):
    _anonymous_ = ("bit",)
    _fields_ = [
                ("bit",    LowNibble_bits ),
                ("asByte", c_uint8    )
               ]

"""
flags = Flags()
flags.asByte = 0xe2

print( "event_disable: %i"      % flags.evt_disable     )
print( "alarm:  %i" % flags.alarm )
print( "winload   :  %i" % flags.winload    )
print( "neware  : %i"      % flags.neware       )
"""

CMD_EVENT = 0xe
# This is the only message type we fully know how to parse, and lucky enough
# they are also really what you need to have.
# Byte    Value   Description 
# 00      0xEX   High Nibble : Command
#                Low Nibble : Bit 3 - Event Report Enable (0) / Disable (1)
#                             Bit 2 - System in alarm
#                             Bit 1 - Winload connected
#                             Bit 0 - NEware connected
# 01      0xXX   Century
# 02      0xXX   Year
# 03      0xXX   Month
# 04      0xXX   Day
# 05      0xXX   Hour
# 06      0xXX   Minute
# 07      0xXX   Event Group Number
# 08      0xXX   Event Sub Group Number
# 09      0xXX   Partition Number
# 10      0xXX   Module Serial Number (Digit 1 and 2)
# 11      0xXX   Module Serial Number (Digit 3 and 4)
# 12      0xXX   Module Serial Number (Digit 5 and 6)
# 13      0xXX   Module Serial Number (Digit 6 and 8)
# 14      0xXX   Label Type
#                 0 = Zone Label
#                 1 = User Label
#                 2 = Partition Label
#                 3 = PGM Label
#                 4 = Bus Module Label
#                 5 = Wireless Repeater Label
#                 6 = Wireless Keypad Label
# 15      0xXX   Module / User / Partition Label Byte 00
# 16      0xXX   Module / User / Partition Label Byte 01
# 17      0xXX   Module / User / Partition Label Byte 02
# 18      0xXX   Module / User / Partition Label Byte 03
# 19      0xXX   Module / User / Partition Label Byte 04
# 20      0xXX   Module / User / Partition Label Byte 05
# 21      0xXX   Module / User / Partition Label Byte 06
# 22      0xXX   Module / User / Partition Label Byte 07
# 23      0xXX   Module / User / Partition Label Byte 08
# 24      0xXX   Module / User / Partition Label Byte 09
# 25      0xXX   Module / User / Partition Label Byte 10
# 26      0xXX   Module / User / Partition Label Byte 11
# 27      0xXX   Module / User / Partition Label Byte 12
# 28      0xXX   Module / User / Partition Label Byte 13
# 29      0xXX   Module / User / Partition Label Byte 14
# 30      0xXX   Module / User / Partition Label Byte 15
# 31      0xXX   Not Used (Reserved)
# 32      0xXX   Not Used (Reserved)
# 33      0xXX   Not Used (Reserved)
# 34      0xXX   Not Used (Reserved)
# 35      0xXX   Not Used (Reserved)
# 36      0xXX   Checksum


def checkCmd(msg, cmd, nibble=False):
   if isinstance(cmd, str):
      if isinstance(msg, str):
         if len(msg)>=len(cmd):
            return msg[:len(cmd)] == cmd
         return False
   if isinstance(msg, str):
      msg=ord(msg[0])
   if nibble:
      return (msg >> 4) == cmd
   return msg == cmd


# XXX I don't really like i don't fully understand 
#     the meaning of some messages (yet?), but those are needed and i know
#     what the give me back at least.
#
#     To save computational timing there is no need to format those messages 
#     at runtime, as they are static, so, full string included here.

MSG_CONNECT=        '\x72\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x72'
MSG_DISCONNECT=     '\x70\x00\x05\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x76'
MSG_GETSTATUS=      '\x50\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD0'
MSG_GETALARMSTATUS= '\x50\x00\x80\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xD1'
MSG_SYNC=           '\x5F\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x7F'
MSG_UNKWNOWN_HS1=   '\x50\x00\x1F\xE0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4F'
MSG_UNKWNOWN_HS2=   '\x50\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x50'


# Command sent starts with
SEND_QUERY =   '\x50'
SEND_CONTROL = '\x40'


# Expected replies command
REPLY_CONNECT=0x7
REPLY_GETSTATUS=[0x5, 0x7]
REPLY_SYNC=0x0
REPLY_QUERY=0x5

def checkSumCalc(message):
   checksum = 0
   return chr(sum(map(ord, message)) % 256)


def checkSumTest(message):
   if len(message) == 37:
      if message[36] == checkSumCalc(message[:36]):
         return True
   return False

def format37ByteMessage(message):
   checksum = 0
   message = message.ljust(36, '\x00')
   if len(message) % 37 != 0:
      message += checkSumCalc(message) # Add check to end of message
   return message


def getPanelName(message):
   return str(message[28:36]).strip('\x00')


ARM = REGISTERS.ARM
DISARM = REGISTERS.DISARM
STAY = REGISTERS.STAY
SLEEP = REGISTERS.SLEEP
PGM_ON = REGISTERS.ON
PGM_OFF = REGISTERS.OFF
#PGM_CHANGE = REGISTERS.CHANGE