#Python Snarl bindings version 0.2a
#Hacked together by Alexander Lash in the wee hours of 2/27.
#Contact him at alexander.lash@gmail.com
#0.1a: Initial release
#0.2a: Added basic command line functions
#LARGELY UNTESTED REFERENCE IMPLEMENTATION
import win32gui, win32api, win32con
import struct, array
from ctypes import cast, POINTER, c_byte

class SNARL_COMMANDS:
    SNARL_SHOW = 1
    SNARL_HIDE = 2
    SNARL_UPDATE = 3
    SNARL_IS_VISIBLE = 4
    SNARL_GET_VERSION = 5
    SNARL_REGISTER_CONFIG_WINDOW = 6
    SNARL_REVOKE_CONFIG_WINDOW = 7
    SNARL_REGISTER_ALERT = 8
    SNARL_REVOKE_ALERT = 9
    SNARL_REGISTER_CONFIG_WINDOW_2 = 0xA
    SNARL_EX_SHOW = 0x20
    
    @staticmethod
    def sendCommand(command, id=0, timeout=0, longdata=0, title="", text="", icon="",
                      extra=None, extra2=None, reserved1=None, reserved2=None):
        if extra is None and extra2 is None and reserved1 is None and reserved2 is None:
            command = struct.pack("ILLL1024s1024s1024s",
                              command,
                              id,
                              timeout,
                              longdata, #LngData2
                              array.array('B', title).tostring(),
                              array.array('B', text).tostring(),
                              array.array('B', icon).tostring())
        else:
            if reserved1 is None:
                reserved1 = 0
            if reserved2 is None:
                reserved2 = 0
            if extra is None:
                extra = ""
            if extra2 is None:
                extra2 = ""
            command = struct.pack("ILLL1024s1024s1024s1024s1024sLL",
                                  command,
                                  id,
                                  timeout,
                                  longdata, #LngData2
                                  array.array('B', title).tostring(),
                                  array.array('B', text).tostring(),
                                  array.array('B', icon).tostring(),
                                  array.array('B', extra).tostring(),
                                  array.array('B', extra2).tostring(),
                                  reserved1,
                                  reserved2)
        command_pack = array.array("B", command)
        command_info = command_pack.buffer_info()
        
        cd = struct.pack("LLP", 2, command_info[1], command_info[0])
        cd_pack = array.array("B", cd)
        cd_info = cd_pack.buffer_info()
        
        
        hwnd = win32gui.FindWindow(None, 'Snarl')
        if hwnd:
            return win32api.SendMessage(hwnd, win32con.WM_COPYDATA, 0, cd_info[0])
        else:
            return False


#deviation from spec: Python doesn't do references.
def snGetVersion():
    hr = SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_GET_VERSION)
    if hr:
        ver = cast(hr, POINTER(c_byte))
        return (ver[0], ver[1])
    return False

#deviation from spec: sound is an optional parameter.
def snShowMessage(title, text, timeout=0, iconPath="", reply=0, reply_msg=0, sound=None):
    if title is None or text is None:
        return False
    if sound is None:
        return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_SHOW, reply, timeout, reply_msg,
                        title,text,iconPath)
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_SHOW, reply, timeout, reply_msg,
                        title,text,iconPath,sound)

def snUpdateMessage(id, title, text):
    if id is None or title is None or text is None:
        return False
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_UPDATE, id=id, title=title, text=text) == -1

def snHideMessage(id):
    id = int(id)
    if id is None:
        return False
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_HIDE, id=id) == -1

#For some reason this ALWAYS returns -1 for all messages that were once displayed.
def snIsMessageVisible(id):
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_IS_VISIBLE, id=id) == -1

#Untested
def snRegisterConfig(hwnd, appname, reply_msg, icon=None):
    if icon is None:
        return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_REGISTER_CONFIG_WINDOW,
                            longdata=hwnd, title=appname, id = reply_msg)
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_REGISTER_CONFIG_WINDOW,
                            longdata=hwnd, title=appname, id = reply_msg,
                            icon=icon)

#Untested
def snRevokeConfig(hwnd):
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_REVOKE_CONFIG_WINDOW,
                        longdata=hwnd)

#Reference implementation - seems to do nothing?
def snRegisterAlert(title, text):
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_REGISTER_ALERT,
                        title=title, text=text)

#Reference implementation - seems to do nothing?
def snRevokeAlert():
    return SNARL_COMMANDS.sendCommand(SNARL_COMMANDS.SNARL_REVOKE_ALERT)

if __name__=='__main__':
    import sys, inspect
    from optparse import OptionParser
    parser = OptionParser()
    parser.add_option("-s", "--show", action="store_const", dest="cmd",
                      const=snShowMessage,
                      help="Show a message with the given parameters.")
    parser.add_option("-u", "--update", action="store_const", dest="cmd",
                      const=snUpdateMessage,
                      help="Update a message with the given parameters.")
    parser.add_option("-c", "--hide", action="store_const", dest="cmd",
                      const=snHideMessage,
                      help="Hide a message with the given ID.")
    parser.add_option("-i", "--id", dest="id",
                      help="ID to use for the update/hide.")
    parser.add_option("-t", "--title", dest="title",
                      help="Title to use for the show/update.")
    parser.add_option("-x", "--text", dest="text",
                      help="Text to use for the show/update.")
    parser.add_option("-T", "--timeout", dest="timeout",
                      help="Timeout to use for the show.")
    parser.add_option("-I", "--icon", dest="iconPath",
                      help="Icon to use for the show.")
    parser.add_option("-S", "--sound", dest="sound",
                      help="Sound to use for the show.")
    if snGetVersion == False:
        print "Snarl not running!"
        sys.exit(1)
    (options, args) = parser.parse_args(sys.argv[1:])
    if options.cmd is None:
        parser.parse_args(['-h'])
    d = options.__dict__.copy()
    cmd = options.cmd
    for key in options.__dict__:
        if key not in inspect.getargspec(cmd)[0]:
            del d[key]
        elif d[key] is None:
            del d[key]
    print cmd(**d)
    sys.exit(0)