#!/usr/bin/env python3
import dbus
import dbus.exceptions
import dbus.mainloop.glib
import dbus.service
import array
try:
  from gi.repository import GLib
except ImportError:
  import gobject as GLib
import sys
import re
import time

from random import randint
from rest_api.products.mts_services.SCPI_Socket import SCPI_Socket

mainloop = None

BLUEZ_SERVICE_NAME = 'org.bluez'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
DBUS_OM_IFACE =      'org.freedesktop.DBus.ObjectManager'
DBUS_PROP_IFACE =    'org.freedesktop.DBus.Properties'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_SERVICE_IFACE = 'org.bluez.GattService1'
GATT_CHRC_IFACE =    'org.bluez.GattCharacteristic1'
GATT_DESC_IFACE =    'org.bluez.GattDescriptor1'
IP_AP_MASTER =       "192.168.42.1"
REST_API_PORT =      80

class InvalidArgsException(dbus.exceptions.DBusException):
    _dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'

class NotSupportedException(dbus.exceptions.DBusException):
    _dbus_error_name = 'org.bluez.Error.NotSupported'

class NotPermittedException(dbus.exceptions.DBusException):
    _dbus_error_name = 'org.bluez.Error.NotPermitted'

class InvalidValueLengthException(dbus.exceptions.DBusException):
    _dbus_error_name = 'org.bluez.Error.InvalidValueLength'

class FailedException(dbus.exceptions.DBusException):
    _dbus_error_name = 'org.bluez.Error.Failed'

class Application(dbus.service.Object):
    """
    org.bluez.GattApplication1 interface implementation
    """
    def __init__(self, bus):
        self.path = '/'
        self.services = []
        dbus.service.Object.__init__(self, bus, self.path)
        self.add_service(DeviceLinkService(bus, 0))

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_service(self, service):
        self.services.append(service)

    @dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
    def GetManagedObjects(self):
        response = {}
        print('GetManagedObjects')
        for service in self.services:
            response[service.get_path()] = service.get_properties()
            chrcs = service.get_characteristics()
            for chrc in chrcs:
                response[chrc.get_path()] = chrc.get_properties()
                descs = chrc.get_descriptors()
                for desc in descs:
                    response[desc.get_path()] = desc.get_properties()
        return response

class Service(dbus.service.Object):
    """
    org.bluez.GattService1 interface implementation
    """
    PATH_BASE = '/org/bluez/example/service'

    def __init__(self, bus, index, uuid, primary):
        self.path = self.PATH_BASE + str(index)
        self.bus = bus
        self.uuid = uuid
        self.primary = primary
        self.characteristics = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_properties(self):
        return {
                GATT_SERVICE_IFACE: {
                        'UUID': self.uuid,
                        'Primary': self.primary,
                        'Characteristics': dbus.Array(
                                self.get_characteristic_paths(),
                                signature='o')
                }
        }

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_characteristic(self, characteristic):
        self.characteristics.append(characteristic)

    def get_characteristic_paths(self):
        result = []
        for chrc in self.characteristics:
            result.append(chrc.get_path())
        return result

    def get_characteristics(self):
        return self.characteristics

    @dbus.service.method(DBUS_PROP_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        if interface != GATT_SERVICE_IFACE:
            raise InvalidArgsException()

        return self.get_properties()[GATT_SERVICE_IFACE]

class Characteristic(dbus.service.Object):
    """
    org.bluez.GattCharacteristic1 interface implementation
    """
    def __init__(self, bus, index, uuid, flags, service):
        self.path = service.path + '/char' + str(index)
        self.bus = bus
        self.uuid = uuid
        self.service = service
        self.flags = flags
        self.descriptors = []
        dbus.service.Object.__init__(self, bus, self.path)

    def get_properties(self):
        return {
                GATT_CHRC_IFACE: {
                        'Service': self.service.get_path(),
                        'UUID': self.uuid,
                        'Flags': self.flags,
                        'Descriptors': dbus.Array(
                                self.get_descriptor_paths(),
                                signature='o')
                }
        }

    def get_path(self):
        return dbus.ObjectPath(self.path)

    def add_descriptor(self, descriptor):
        self.descriptors.append(descriptor)

    def get_descriptor_paths(self):
        result = []
        for desc in self.descriptors:
            result.append(desc.get_path())
        return result

    def get_descriptors(self):
        return self.descriptors

    @dbus.service.method(DBUS_PROP_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        if interface != GATT_CHRC_IFACE:
            raise InvalidArgsException()

        return self.get_properties()[GATT_CHRC_IFACE]

    @dbus.service.method(GATT_CHRC_IFACE,
                        in_signature='a{sv}',
                        out_signature='ay')
    def ReadValue(self, options):
        print('Default ReadValue called, returning error')
        raise NotSupportedException()

    @dbus.service.method(GATT_CHRC_IFACE, in_signature='aya{sv}')
    def WriteValue(self, value, options):
        print('Default WriteValue called, returning error')
        raise NotSupportedException()

    @dbus.service.method(GATT_CHRC_IFACE)
    def StartNotify(self):
        print('Default StartNotify called, returning error')
        raise NotSupportedException()

    @dbus.service.method(GATT_CHRC_IFACE)
    def StopNotify(self):
        print('Default StopNotify called, returning error')
        raise NotSupportedException()

    @dbus.service.signal(DBUS_PROP_IFACE,
                         signature='sa{sv}as')
    def PropertiesChanged(self, interface, changed, invalidated):
        pass

class Descriptor(dbus.service.Object):
    """
    org.bluez.GattDescriptor1 interface implementation
    """
    def __init__(self, bus, index, uuid, flags, characteristic):
        self.path = characteristic.path + '/desc' + str(index)
        self.bus = bus
        self.uuid = uuid
        self.flags = flags
        self.chrc = characteristic
        dbus.service.Object.__init__(self, bus, self.path)

    def get_properties(self):
        return {
                GATT_DESC_IFACE: {
                        'Characteristic': self.chrc.get_path(),
                        'UUID': self.uuid,
                        'Flags': self.flags,
                }
        }

    def get_path(self):
        return dbus.ObjectPath(self.path)

    @dbus.service.method(DBUS_PROP_IFACE,
                         in_signature='s',
                         out_signature='a{sv}')
    def GetAll(self, interface):
        if interface != GATT_DESC_IFACE:
            raise InvalidArgsException()

        return self.get_properties()[GATT_DESC_IFACE]

    @dbus.service.method(GATT_DESC_IFACE,
                        in_signature='a{sv}',
                        out_signature='ay')
    def ReadValue(self, options):
        print ('Default ReadValue called, returning error')
        raise NotSupportedException()

    @dbus.service.method(GATT_DESC_IFACE, in_signature='aya{sv}')
    def WriteValue(self, value, options):
        print('Default WriteValue called, returning error')
        raise NotSupportedException()

class DeviceLinkService(Service):
    HR_UUID = '0f5c0001-1730-409b-88fd-952cca9c270f'
    print('DeviceLinkService')
    def __init__(self, bus, index):
        Service.__init__(self, bus, index, self.HR_UUID, True)
        self.add_characteristic(DeviceLinkVersionChrc(bus, 0, self))
        self.add_characteristic(DeviceLinkCommandChrc(bus, 1, self))
        self.add_characteristic(DeviceLinkResponseChrc(bus, 2, self))
        self.add_characteristic(DeviceLinkStatusChrc(bus, 3, self))
        self.energy_expended = 0

class DeviceLinkVersionChrc(Characteristic):
    DL_VERSION_UUID = '0f5c0002-1730-409b-88fd-952cca9c270f'

    def __init__(self, bus, index, service):
        Characteristic.__init__(
                self, bus, index,
                self.DL_VERSION_UUID,
                ['read'],
                service)

    def ReadValue(self, options):
        print('Device Link Version ReadValue called')
        return [ 0x06 ]

class DeviceLinkCommandChrc(Characteristic):
    DL_COMMAND_UUID = '0f5c0003-1730-409b-88fd-952cca9c270f'
    CmdCounter = 0
    CmdNumber = 0

    def __init__(self, bus, index, service):
        Characteristic.__init__(
                self, bus, index,
                self.DL_COMMAND_UUID,
                ['write'],
                service)

    def WriteValue(self, value, options):
        print('Device Link Command WriteValue called')
        #print(value)
        CmdCounter = int(value[0])
        CmdNumber = int(value[1])
        #print("Cde",CmdCounter,"(",CmdNumber,")")
        if DeviceLinkResponseChrc.instance is not None:
            DeviceLinkResponseChrc.instance.EvaluateResponse(CmdCounter, CmdNumber)        

class DeviceLinkResponseChrc(Characteristic):
    DL_RESPONSE_UUID = '0f5c0004-1730-409b-88fd-952cca9c270f'
    Response = []
    instance = None

    def __init__(self, bus, index, service):
        Characteristic.__init__(
                self, bus, index,
                self.DL_RESPONSE_UUID,
                ['read', 'notify'],
                service)
        self.notifying = False
        DeviceLinkResponseChrc.instance = self

    def EvaluateResponse(self, CmdCounter=0, CmdNumber=0):
        if CmdNumber == 20:
           print('Enable Wi-Fi Hotspot')
           ISU = SCPI_Socket('127.0.0.1',8000)
           ISU.send("*rem")

           # Get Wlan Mode [ ON/OFF ]
           result = ISU.query("SYST:WLAN:SET?")
           if result == "OFF":
              print("Wlan OFF -> ON")
              # Force On if necessary
              success = ISU.send("SYST:WLAN:SET ON")
           else:
              print("Wlan already ON")

           keep = "FALSE"
           mode = ISU.query("SYST:WLAN:MOD?")
           if mode == "APM":
              print("Already in APM")
              autoconnect = ISU.query("SYST:WLAN:APA?")
              encryption  = ISU.query("SYST:WLAN:APEN?")
              print("encryption  : "+encryption)
              print("autoconnect : "+autoconnect)
              if (encryption == "1") or (autoconnect == "NO"):
                  print("APM must be reseted -> WCL")
                  # Set Mode to WCL (reset AP Master)
                  success = ISU.send("SYST:WLAN:MOD WCL")
              else:
                  print("But compatible")
                  keep= "TRUE"
            
           if keep == "FALSE":
              # Set Mode to AP Master
              success = ISU.send("SYST:WLAN:MOD APM")
              # Force Auto Create
              success = ISU.send("SYST:WLAN:APA YES")
              # Get Encryption [ None, WEP, WPA ]
              mode = ISU.query("SYST:WLAN:APEN?")
              if mode == "1":
                 # Force None rather than WEP
                 print("WEP detected -> None")
                 ISU.send("SYST:WLAN:APEN 0")
              
           # Start the AP Master
           result = ISU.query("SYST:WLAN:APC?")
           print(result)
           if result == "OK":
              print("Wifi OK")
              Response = [dbus.Byte(CmdCounter), dbus.Byte(255), dbus.Byte(0)]
           else:
              print("Wifi KO")
              Response = [dbus.Byte(CmdCounter), dbus.Byte(255), dbus.Byte(1)]
        elif CmdNumber == 21:
           print('Disable Wi-Fi Hotspot')
           Response = [dbus.Byte(CmdCounter), dbus.Byte(255), dbus.Byte(0)]
        elif CmdNumber == 22:
           print('Get Wi-Fi Hotspot Information')
           Response = [dbus.Byte(CmdCounter), dbus.Byte(135), dbus.Byte(1)]
           ISU = SCPI_Socket('127.0.0.1',8000)
           ISU.send("*rem")           
           ##############  Get SSID #############
           ssid = None
           ssid = ISU.query("SYST:WLAN:APSS?")
           print("SSID:"+ssid)
           ############# passphrase #############
           mode = ISU.query("SYST:WLAN:APEN?")
           if mode == "0":
               passphrase = ""
           else:
               passphrase = ISU.query("SYST:WLAN:APP?")
           print("pass:"+passphrase)
           Response += [ dbus.Byte(bytes(b,encoding="utf8")) for b in ssid ]
           Response += [ dbus.Byte(0) ]
           Response += [ dbus.Byte(bytes(b,encoding="utf8")) for b in passphrase ]
           Response += [ dbus.Byte(0) ]
           ISU.close()
        elif CmdNumber == 36:
           ############  Get IP addr ############
           print('Get IP Address')
           Response = [dbus.Byte(CmdCounter), dbus.Byte(141)]
           ip = IP_AP_MASTER 
           ip_byte=ip.split('.')
           ip_byte.reverse()
           Response += [ dbus.Byte(int(b)) for b in ip_byte ]
           ########### REST API Port ############
           rest_port = REST_API_PORT
           port_h = int(rest_port/256)
           port_l = rest_port-port_h*256
           Response += [ dbus.Byte(port_l), dbus.Byte(port_h)]

        self.PropertiesChanged(GATT_CHRC_IFACE, {'Value': Response}, [])        
        return

    def StartNotify(self):
        print('Device Link Response StartNotify called')
        if self.notifying:
            print('Already notifying, nothing to do')
            return
        self.notifying = True

    def StopNotify(self):
        print('Device Link Response StopNotify called')
        if not self.notifying:
            print('Not notifying, nothing to do')
            return
        self.notifying = False

    def ReadValue(self, options):
        print('Device Link Response ReadValue called')
        self.EvaluateValue()
        return value

class DeviceLinkStatusChrc(Characteristic):
    DL_STATUS_UUID = '0f5c0005-1730-409b-88fd-952cca9c270f'

    def __init__(self, bus, index, service):
        Characteristic.__init__(
                self, bus, index,
                self.DL_STATUS_UUID,
                ['read','notify'],
                service)
        self.notifying = False        

    def GetValue(self):
        if not self.notifying:
            return
        self.PropertiesChanged(GATT_CHRC_IFACE, {'Value': [dbus.Byte(CmdCounter), dbus.Byte(0)] }, [])
        return

    def ReadValue(self, options):
        value = [dbus.Byte(CmdCounter), dbus.Byte(0)]
        return value 

    def StartNotify(self):
        print('Device Link Status StartNotify called')
        if self.notifying:
            print('Already notifying, nothing to do')
            return
        self.notifying = True

    def StopNotify(self):
        print('Device Link Status StopNotify called')
        if not self.notifying:
            print('Not notifying, nothing to do')
            return
        self.notifying = False

def register_app_cb():
    print('GATT application registered')

def register_app_error_cb(error):
    print('Failed to register application: ' + str(error))
    mainloop.quit()

def find_adapter(bus):
    remote_om = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, '/'),
                               DBUS_OM_IFACE)
    objects = remote_om.GetManagedObjects()

    for o, props in objects.items():
        if GATT_MANAGER_IFACE in props.keys():
            return o
    return None

def register_ad_cb():
    print('Advertisement registered')

def register_ad_error_cb(error):
    print('Failed to register advertisement: ' + str(error))
    mainloop.quit()

def main():
    global mainloop

    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
    bus = dbus.SystemBus()
    adapter = find_adapter(bus)
    if not adapter:
        print('GattManager1 interface not found')
        return
    service_manager = dbus.Interface(
            bus.get_object(BLUEZ_SERVICE_NAME, adapter),
            GATT_MANAGER_IFACE)
    ad_manager = dbus.Interface(bus.get_object(BLUEZ_SERVICE_NAME, adapter),
                                LE_ADVERTISING_MANAGER_IFACE)
    app = Application(bus)
    
    mainloop = GLib.MainLoop()
    print('Registering GATT application...')
    service_manager.RegisterApplication(app.get_path(), {},
                                    reply_handler=register_app_cb,
                                    error_handler=register_app_error_cb)
    mainloop.run()

if __name__ == '__main__':
    main()
