# -*- coding: utf-8 -*-
#
# This file is plugin for EventGhost.
# Copyright (C) 2005-2009 Lars-Peter Voss <bitmonster@eventghost.org>
#
# EventGhost is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 as published by the
# Free Software Foundation;
#
# EventGhost 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/>.
#
#
# $LastChangedDate: 2009-07-06 19:11:56 +0200 (po, 06 7 2009) $
# $LastChangedRevision: 1093 $
# $LastChangedBy: Bitmonster $

# POWER ON      80 70 C0 3F     
# POWER OFF     80 70 9F 60     
# MUTE          80 70 C1 3E     
# AVR           82 72 35 CA     
# DVD           80 70 D0 2F     
# CD            80 70 C4 3B     
# TAPE          80 70 CC 33     
# VID1          80 70 CA 35     
# VID2          80 70 CB 34     
# VID3          80 70 CE 31     
# VID4          80 70 D1 2E     
# VID5          80 70 F0 0F     
# AM/FM         80 70 81 7E     
# 6CH/8CH       82 72 DB 24     
# SLEEP         80 70 DB 24     
# SURR          82 72 58 A7     
# DOLBY         82 72 50 AF     
# DTS           82 72 A0 5F     
# DTS NEO:6     82 72 A1 5E     
# LOGIC7        82 72 A2 5D     
# STEREO        82 72 9B 64     
# TEST TONE     82 72 8C 73     
# NIGHT         82 72 96 69     
# 1             80 70 87 78     
# 2             80 70 88 77     
# 3             80 70 89 76     
# 4             80 70 8A 75     
# 5             80 70 8B 74     
# 6             80 70 8C 73     
# 7             80 70 8D 72     
# 8             80 70 8E 71     
# 9             80 70 9D 62     
# 0             80 70 9E 61     
# TUNE UP       80 70 84 7B     
# TUNE DOWN     80 70 85 7A     
# VOL UP        80 70 C7 38     
# VOL DOWN      80 70 C8 37     
# PRESET UP     82 72 D0 2F     
# PRESET DOWN   82 72 D1 2E     
# DIGITAL       82 72 54 AB     
# DIGITAL UP    82 72 57 A8     
# DIGITAL DOWN  82 72 56 A9     
# FMMODE        80 70 93 6C     
# DELAY         82 72 52 AD     
# DELAY UP      82 72 8A 75     
# DELAY DOWN    82 72 8B 74     
# COM SET       82 72 84 7B     
# COM UP        82 72 99 66     
# COM DOWN      82 72 9A 65     
# SPEAKER       82 72 53 AC     
# SPEAKER UP    82 72 8E 71     
# SPEAKER DOWN  82 72 8F 70     
# CHANNEL       82 72 5D A2     
# RDS           82 72 DD 22     
# DIRECT        80 70 9B 64     
# CLEAR         82 72 D9 26     
# MEMORY        80 70 86 79     
# MULTIROOM     82 72 DF 20     
# MULTIROOM UP  82 72 5E A1     
# MULTIROOM DN  82 72 5F A0     
# OSD           82 72 5C A3     
# OSD LEFT      82 72 C1 3E     
# OSD RIGHT     82 72 C2 3D     
# SURR UP       82 72 85 7A     
# SURR DOWN     82 72 86 79
# PRESCAN       80 70 96 69     
# DIMMER        80 70 DC 23     
# FAROUDJA      82 72 C6 39     
# TONE          82 72 C5 3A


eg.RegisterPlugin(
    name = "Harman Kardon Serial Control",
    author = "Kingtd & Bitmonster",
    version = "1.0." + "$LastChangedRevision: 1093 $".split()[1],
    description = "Allows control of a Harman Kardon AVR4xx/6xx series receiver through a serial port.",
    kind = "external",
    guid = "{460D31E6-231D-483D-8B9B-2781F9D2377A}",
    url = "http://www.eventghost.net/forum/viewtopic.php?t=447",
    canMultiLoad = True,
)
    
hktopmsg=""
hkbotmsg=""

class Text:
    port = "Port:"
    baudrate = "Baudrate:"
    bytesize = "Number of bits:"
    parity = "Parity:"
    parities = ['No parity', 'Odd', 'Even'] #, 'Mark', 'Space']
    stopbits = "Stopbits:"
    flowcontrol = "Flow control:"
    handshakes = ['None', 'Xon / Xoff', 'Hardware']
    generateEvents = "Generate events on incoming data"
    terminator = "Terminator:"
    eventPrefix = "Event prefix:"
    class send2hk:
        name = "Send to HK Receiver"    


import threading
import win32event
import win32file

class HarmanKardon(eg.RawReceiverPlugin):

    text = Text
    
    def __init__(self):
        eg.RawReceiverPlugin.__init__(self)
        self.AddAction(self.Send2hk)
        self.serial = None
        self.buffer = ""


    def __start__(
        self, 
        port, 
        baudrate,
        bytesize=8,
        parity=0,
        stopbits=0,
        handshake=0,
        generateEvents=False,
        terminator="",
        prefix="HK",
    ):
        xonxoff = 0
        rtscts = 0
        if handshake == 1:
            xonxoff = 1
        elif handshake == 2:
            rtscts = 1
            
        try:
            self.serial = eg.SerialPort(
                port, 
                baudrate=baudrate, 
                bytesize=(5, 6, 7, 8)[bytesize],
                stopbits=(1, 2)[stopbits],
                parity=('N', 'O', 'E')[parity],
                xonxoff=xonxoff,
                rtscts=rtscts,
            )
        except:
            self.serial = None
            raise self.Exceptions.SerialOpenFailed
        self.serial.timeout = 1.0
        self.serial.setRTS()
        if generateEvents:
            self.terminator = eg.ParseString(terminator).decode('string_escape')
            self.info.eventPrefix = prefix
            self.stopEvent = win32event.CreateEvent(None, 1, 0, None)
            self.receiveThread = threading.Thread(target=self.ReceiveThread)
            self.receiveThread.start()
        else:
            self.receiveThread = None


    def __stop__(self):
        if self.serial is not None:
            if self.receiveThread:
                win32event.SetEvent(self.stopEvent)
                self.receiveThread.join(1.0)
            self.serial.close()
            self.serial = None


    def HandleChar(self, ch):

        global hktopmsg
        global hkbotmsg

        if ord(ch) != 186:
                self.buffer += ch
        self.terminator = chr(0)+chr(242)
        pos = self.buffer.find(self.terminator)
        if pos != -1:
            eventstring = self.buffer[:pos].strip()
            st2=eventstring.find(chr(50)+chr(240))
            st3=eventstring.find(chr(0)+chr(241))
            ev1 = eventstring[st2+2:st3].strip()
            ev2 = eventstring[st3+2:].strip()

            if ev1 != hktopmsg and ev1 != "":
                hktopmsg=ev1
                for i in range(0, len(ev1), 1):
                   if ord(ev1[i])>127:
                      ev1="Result Unprintable"
                self.TriggerEvent('Top.'+ev1)
            if ev2 != hkbotmsg and ev1 != "":
                hkbotmsg=ev2
                for i in range(0, len(ev2), 1):
                   if ord(ev2[i])>127:
                      ev2="Result Unprintable"
                self.TriggerEvent('Bottom.'+ev2)

            self.buffer = self.buffer[pos+len(self.terminator):]
            
            
    def ReceiveThread(self):
        from win32event import (
            ResetEvent, 
            MsgWaitForMultipleObjects, 
            QS_ALLINPUT, 
            WAIT_OBJECT_0, 
            WAIT_TIMEOUT,
        )
        from win32file import ReadFile, AllocateReadBuffer, GetOverlappedResult
        from win32api import GetLastError

        continueLoop = True
        overlapped = self.serial._overlappedRead
        hComPort = self.serial.hComPort
        hEvent = overlapped.hEvent
        stopEvent = self.stopEvent
        n = 1
        waitingOnRead = False
        buf = AllocateReadBuffer(n)
        while continueLoop:
            if not waitingOnRead:
                ResetEvent(hEvent)
                hr, _ = ReadFile(hComPort, buf, overlapped)
                if hr == 997:
                    waitingOnRead = True
                elif hr == 0:
                    pass
                    #n = GetOverlappedResult(hComPort, overlapped, 1)
                    #self.HandleChar(str(buf))
                else:
                    self.PrintError("error")
                    raise

            rc = MsgWaitForMultipleObjects(
                (hEvent, stopEvent),
                0, 
                1000, 
                QS_ALLINPUT
            )
            if rc == WAIT_OBJECT_0:
                n = GetOverlappedResult(hComPort, overlapped, 1)
                if n:
                    self.HandleChar(str(buf))
                #else:
                #    print "WAIT_OBJECT_0", n, str(buf[:n])
                waitingOnRead = False
            elif rc == WAIT_OBJECT_0+1:
                continueLoop = False
            elif rc == WAIT_TIMEOUT:
                pass
            else:
                self.PrintError("unknown message")
                
            
    def Configure(
        self, 
        port=0, 
        baudrate=9600,
        bytesize=8,
        parity=0,
        stopbits=0,
        handshake=0,
        generateEvents=False,
        terminator="\\r",
        prefix="HK",
    ):
        text = self.text
        oldevstring=" "
        panel = eg.ConfigPanel(self)
        portCtrl = eg.SerialPortChoice(panel, value=port)
        
        baudrateCtrl = wx.ComboBox(
            panel,
            value=str(baudrate),
            choices=[
                        '38400' 
                    ],
            style=wx.CB_DROPDOWN,
            validator=eg.DigitOnlyValidator()
        )
        
        bytesizeCtrl = wx.Choice(panel, choices=['5', '6', '7', '8'])
        bytesizeCtrl.SetSelection(8 - 5)
        
        parityCtrl = wx.Choice(panel, choices=text.parities)
        parityCtrl.SetSelection(parity)
        
        stopbitsCtrl = wx.Choice(panel, choices=['1', '2'])
        stopbitsCtrl.SetSelection(stopbits)
        
        handshakeCtrl = wx.Choice(panel, choices=text.handshakes)
        handshakeCtrl.SetSelection(handshake)
        
        generateEventsCtrl = wx.CheckBox(panel, label=text.generateEvents)
        generateEventsCtrl.SetValue(generateEvents)
        
        terminatorCtrl = wx.TextCtrl(panel)
        terminatorCtrl.SetValue(terminator)
        terminatorCtrl.Enable(generateEvents)
        
        prefixCtrl = wx.TextCtrl(panel)
        prefixCtrl.SetValue(prefix)
        prefixCtrl.Enable(generateEvents)
        
        def OnCheckBox(event):
            flag = generateEventsCtrl.GetValue()
            terminatorCtrl.Enable(flag)
            prefixCtrl.Enable(flag)
        generateEventsCtrl.Bind(wx.EVT_CHECKBOX, OnCheckBox)
        
        flags = wx.ALIGN_RIGHT|wx.ALIGN_CENTER_VERTICAL
        mySizer = wx.GridBagSizer(5, 5)
        Add = mySizer.Add
        Add(wx.StaticText(panel, -1, text.port), (0, 0), flag=flags)
        Add(portCtrl, (0, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.baudrate), (1, 0), flag=flags)
        Add(baudrateCtrl, (1, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.bytesize), (2, 0), flag=flags)
        Add(bytesizeCtrl, (2, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.parity), (3, 0), flag=flags)
        Add(parityCtrl, (3, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.stopbits), (4, 0), flag=flags)
        Add(stopbitsCtrl, (4, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.flowcontrol), (5, 0), flag=flags)
        Add(handshakeCtrl, (5, 1), flag=wx.EXPAND)
        
        Add((5, 5), (6, 0), (1, 2), flag=flags)
        Add(generateEventsCtrl, (7, 0), (1, 2), flag=flags)
        Add(wx.StaticText(panel, -1, text.terminator), (8, 0), flag=flags)
        Add(terminatorCtrl, (8, 1), flag=wx.EXPAND)
        Add(wx.StaticText(panel, -1, text.eventPrefix), (9, 0), flag=flags)
        Add(prefixCtrl, (9, 1), flag=wx.EXPAND)
        panel.sizer.Add(mySizer)

        while panel.Affirmed():
            panel.SetResult(
                portCtrl.GetValue(), 
                int(baudrateCtrl.GetValue()),
                bytesizeCtrl.GetSelection(),
                parityCtrl.GetSelection(),
                stopbitsCtrl.GetSelection(),
                handshakeCtrl.GetSelection(),
                generateEventsCtrl.GetValue(),
                terminatorCtrl.GetValue(),
                prefixCtrl.GetValue(),
            )
        


    class Send2hk(eg.ActionWithStringParameter):
        description = (
            "Sends a HK control command through the serial port."
        )
        
        def bxor (self, a, b): 
            return a ^ b 

        def ByteToHex(self, byteStr ):
            return ''.join( [ "%02X " % ord( x ) for x in byteStr ] ).strip()
        
        def HexToByte(self, hexStr ):
            bytes = []
            hexStr = ''.join( hexStr.split(" ") )
            for i in range(0, len(hexStr), 2):
                bytes.append( chr( int (hexStr[i:i+2], 16 ) ) )
            return ''.join( bytes )

        def dec2hex(self, n):
            return "%X" % n 

        def hex2dec(self, s):
            return int(s, 16)

      
        def __call__(self, data):
    
            hkcmd=self.HexToByte(data)
            csodd = 0 
            cseven = 0 
            for i in range (0, len(hkcmd), 2):
                csodd = self.bxor(csodd, ord(hkcmd[i])) 
                cseven = self.bxor(cseven, ord(hkcmd[i+1])) 
            csodd = self.HexToByte(self.dec2hex(csodd))
            cseven = self.HexToByte(self.dec2hex(cseven))
            hkcmd=self.HexToByte('50 43 53 45 4e 44 02 04')+hkcmd
            hkcmd=hkcmd+csodd+cseven
            self.plugin.serial.write(str(hkcmd))        
            return self.plugin.serial
            
            
        def replaceFunc(self, data):
            data = data.strip()
            if data == "CR":
                return chr(13)
            elif data == "LF":
                return chr(10)
            else:
                return None

