eg.RegisterPlugin(
    name = 'Keene IR Anywhere',
    author = 'ldobson',
    version = '1.0.5',
    kind = 'remote',
    guid = "{695FA442-24CE-4D82-B38B-8A772BBEA8FD}",
    canMultiLoad = True,
    description = '''\
        Hardware plugin for the 
        <a href="http://www.keene.co.uk/iranywhere">
            Keene Electronics IR Anywhere
        </a>
         transceiver

        <p>
            <div align="center">
                <img src="KIRA.jpg"/>
            </div>
        </p>
        <p>
            If you are talking to multiple units and they are using the
            same port then make sure you only start one server on that
            port. Otherwise you will be confused as to which unit you are
            receiving information from, as only one of the plugin instances
            will be able to listen on that port.
        </p>
        <p>
            The plugin supports one event:
            <ul>
                <li>
                    <b>KIRA.XXXXXX.DDDDDDDD</b> - 
                    (assuming "KIRA" is your event prefix).
                    Happens when we receive an IR stream, this event name
                    is the decoded IR that can be used to pick up on
                    subsequent presses of the same button. "XXXXXX" is the
                    IR protocol (it's not uncommon to see this as "Unknown"
                    so don't panic) and "DDDDDDDD" is the code itself.
                </li>
            </ul>
            <p>
                If you switch on "Log incoming raw IR streams", the IR 
                streams will be printed in the log window in the format:
            </p>
            <p>
                <div align="center">
                    <b>KIRA.Raw.XXYY DDDD DDDD DDDD 2000</b>
                </div>
            </p>
            <p>
                When a unit sends an "ACK" response to an action, 
                then this will appear in the log window:
            </p>
            <p>
                <div align="center">
                    <b>KIRA.Acknowledgement</b>
                </div>
            </p>
        </p>
    '''
)

import asyncore, socket

class Text:
    host = 'Host:'
    port = 'UDP Port:'
    svr = 'Start a server on this port'
    eventPrefix = 'Event Prefix:'
    ipBox = 'IP Settings'
    eventBox = 'Event generation'
    logRaw = 'Log incoming raw IR streams'
    transmitDesc = '''\
        <p>
            Transmits an IR code via the IR Anywhere hardware
        </p>

        <p>
            Remove the leading "KIRA.Raw." from the logged 
            incoming raw IR streams before using them 
            (where "KIRA" is your event prefix)
        </p>
        <p>
            Remove the leading "K " from IR streams that 
            you have learned with the Keene software
        </p>
        <p>
            A valid code looks like this: 
        </p>
        <p>
            <div align="center">
                <b>XXYY DDDD DDDD DDDD 2000</b>
            </div>
        </p>
        <p>
            where "XX" is the frequency, "YY" is the number of 
            following pairs, "DDDD" is the data (there will be an 
            odd number of these chunks), and "2000" is hard-coded
        </p>
        <p>
            You can use:
        </p>
        <p>
            <div align="center">
                <b>{eg.event.payload}</b>
            </div>
        </p>
        <p>
            to use the IR stream that triggered the current KIRA event
        </p>
    '''
    sendToMeDesc = (
        'Sets destination address 2 (PC) address to this '
        'PCs IP address and switches on "Attempt to locate PC"'
    )
    targetDesc = 'Sets the unit to Target mode'
    receiverDesc = 'Sets the unit to Receiver mode'
    standaloneDesc = 'Sets the unit to Stand-alone mode'

class KIRA(eg.PluginClass):
    text = Text

    def __init__(self):
        self.AddAction(Transmit)
        self.AddAction(SendToMe)
        self.AddAction(SetAsTarget)
        self.AddAction(SetAsReceiver)
        self.AddAction(SetAsStandAlone)
        self.irDecoder = eg.IrDecoder(self, 1)

    def __close__(self):
        self.irDecoder.Close()

    def __start__(self, host, port, svr, prefix, logRaw):
        self.host = host
        self.port = port
        self.svr = svr
        self.info.eventPrefix = prefix
        self.logRaw = logRaw
        if (self.svr):
            try:
                self.server = Server(self.port, self)
            except socket.error, exc:
                raise self.Exception(exc[1])
        else:
            self.server = None

    def __stop__(self):
        if self.server:
            self.server.close()
        self.server = None

    def Configure(
        self, 
        host = 'standalone', 
        port = 65432, 
        svr = True, 
        prefix = 'KIRA',
        logRaw = True
    ):
        text = self.text
        panel = eg.ConfigPanel(self)
        hostCtrl = panel.TextCtrl(host)
        portCtrl = panel.SpinIntCtrl(port, max = 65535)
        svrCtrl = panel.CheckBox(svr, text.svr)
        eventPrefixCtrl = panel.TextCtrl(prefix)
        logRawCtrl = panel.CheckBox(logRaw, text.logRaw)

        st1 = panel.StaticText(text.host)
        st2 = panel.StaticText(text.port)
        st3 = panel.StaticText(text.eventPrefix)
        eg.EqualizeWidths((st1, st2, st3))

        ipBox = panel.BoxedGroup(
            text.ipBox,
            (st1, hostCtrl),
            (st2, portCtrl, svrCtrl),
        )

        eventBox = panel.BoxedGroup(
            text.eventBox, 
            (st3, eventPrefixCtrl, logRawCtrl) 
        )

        panel.sizer.Add(ipBox, 0, wx.EXPAND)
        panel.sizer.Add(eventBox, 0, wx.TOP|wx.EXPAND, 10)

        eventPrefixCtrl.Enable(svrCtrl.IsChecked())
        logRawCtrl.Enable(svrCtrl.IsChecked())

        def OnCheckBox(event):
            eventPrefixCtrl.Enable(svrCtrl.IsChecked())
            logRawCtrl.Enable(svrCtrl.IsChecked())
            event.Skip()
            
        svrCtrl.Bind(wx.EVT_CHECKBOX, OnCheckBox)

        while panel.Affirmed():
            panel.SetResult(
                hostCtrl.GetValue(), 
                portCtrl.GetValue(), 
                svrCtrl.GetValue(), 
                eventPrefixCtrl.GetValue(), 
                logRawCtrl.GetValue()
            )

    def Send(self, mesg):
        if (self.server):
            self.server.sendto(mesg, (self.host, self.port))
        else:
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.bind(('', self.port))
            sock.sendto(mesg, (self.host, self.port))
            sock.close()

class Transmit(eg.ActionClass):
    name = 'Transmit IR'
    description = Text.transmitDesc

    def __call__(self, mesg):
        mesg = eg.ParseString(mesg)
        codes = mesg.split()
        try:
            info = codes.pop(0)
            frequency = int(info[:2], 16)
            numpairs = int(info[2:], 16)
            data = [int(i, 16) for i in codes]
            if (
                len(codes) == (numpairs * 2)
                and
                codes[len(codes) - 1] == '2000'
            ):
                self.plugin.Send('K ' + mesg)
            else:
                raise self.Exception()
        except:
            raise self.Exception('IR code does not conform to protocol')

    def Configure(self, mesg = ''):
        panel = eg.ConfigPanel(self)
        textControl = wx.TextCtrl(
            panel, 
            -1, 
            mesg, 
            style = wx.TE_MULTILINE
        )
        panel.sizer.Add(textControl, 1, wx.EXPAND)
        while panel.Affirmed():
            panel.SetResult(textControl.GetValue())

class SendToMe(eg.ActionClass):
    name = 'Send to me'
    description = Text.sendToMeDesc

    def __call__(self):
        self.plugin.Send('cmdI')

class SetAsTarget(eg.ActionClass):
    name = 'Set as Target'
    description = Text.targetDesc

    def __call__(self):
        self.plugin.Send('cmdM1')

class SetAsReceiver(eg.ActionClass):
    name = 'Set as Receiver'
    description = Text.receiverDesc

    def __call__(self):
        self.plugin.Send('cmdM0')

class SetAsStandAlone(eg.ActionClass):
    name = 'Set as Stand-alone'
    description = Text.standaloneDesc

    def __call__(self):
        self.plugin.Send('cmdM2')

class Server(asyncore.dispatcher):

    def __init__(self, port, handler):
        self.handler = handler
        asyncore.dispatcher.__init__(self)
        self.create_socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.bind(('', port))
        self.add_channel()
        eg.RestartAsyncore()

    def handle_read(self):
        try:
            rawdata, client_addr = self.socket.recvfrom(1024)

            codes = rawdata.split()
            if (codes[0] == 'K'):
                codes.remove('K')

                rawCode = ' '.join(codes)

                if (self.handler.logRaw):
                    print(
                        self.handler.info.eventPrefix + 
                        '.Raw.' + rawCode
                    )

                try:
                    info = codes.pop(0)
                    numpairs = int(info[2:], 16)
                    frequency = int(info[:2], 16)
                    data = [int(i, 16) for i in codes]
                    if (
                        len(codes) == (numpairs * 2)
                        and
                        codes[len(codes) - 1] == '2000'
                    ):
                        self.socket.sendto(
                            'ACK', 
                            (client_addr[0], client_addr[1])
                        )
                        self.handler.irDecoder.Decode(data, len(data))
                        self.handler.irDecoder.event.payload = rawCode

                    else:
                        raise self.handler.Exception()
                except:
                    print 'Malformed incoming IR code'

            elif (codes[0] == 'ACK'):
                print(
                    self.handler.info.eventPrefix + 
                    '.Acknowledgement'
                )
                
        except socket.timeout:
            pass

        except socket.error, (errno, strerror):
            if (errno == 10054):
                # this happens when we transmit from the server socket
                pass
            else:
                raise self.handler.Exception()

    def handle_accept(self):
        pass

    def handle_connect(self):
        pass

    def handle_close(self):
        pass

    def writable(self):
        return False
