# -*- coding: utf-8 -*-
#
# This file is part of EventGhost.
# Copyright Â© 2005-2016 EventGhost Project <http://www.eventghost.org/>
#
# EventGhost 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 2 of the License, or (at your option)
# any later version.
#
# 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 EventGhost. If not, see <http://www.gnu.org/licenses/>.


import eg


eg.RegisterPlugin(
    name="ELV IPIO88",
    author="Dieter Brillert",
    version="0.1.5",
    kind="external",
    canMultiLoad=True,
    createMacrosOnAdd=True,
    guid=u'{AB074230-C850-4C8C-B642-136B6C921130}',
    description=(
        "Very basic plugin to read the input states and set the output states "
        "of the ELV-IPIO88 Device.\n\n"
        " The plugin interacts with the web interface of the device. Please "
        "make sure that password check is DISABLED. The device has its own "
        "capabilities to combine input and output channels logically. Please "
        "be aware of that!"
    )
)

import threading # NOQA
import urllib2 as urllib # NOQA


class PollThread:

    def __init__(self, plugin):
        self.plugin = plugin
        self.event = threading.Event()
        self.interval = 2
        self.url = ''
        self.thread = None
        self.lock = None
        self.states = []

    def start(self, ip, interval):

        while self.event.isSet():
            pass

        self.lock = threading.Lock()
        self.url = "http://%s/ipio.cgi" % ip

        eg.Print( "ELV IPIO88: Start reading from IP: " + ip)
        try:
            self.interval = abs(float(interval))
            eg.Print("ELV IPIO88: Refresh cycle time: {}s".format(refresh))
        except ValueError:
            self.interval = 2
            eg.PrintNotice(
                "Invalid refresh cycle time configured.\n"
                "Set to default: 2 s."
            )

        self.thread = threading.Thread(
            name=__name__,
            target=self.run
        )
        self.thread.start()

    def run(self):

        connect = ""
        self.states = [None, None] * 9

        while not self.event.isSet():
            try:
                response = urllib.urlopen(self.url)
            except urllib.HTTPError, urllib.URLError:
                if connect != "no":
                    eg.PrintNotice(
                        "ELV-IPIO88: Can't connect to device"
                    )
                    self.plugin.TriggerEvent("DeviceIO-Error")
                    connect = "no"
            else:
                if connect != "yes":
                    self.plugin.TriggerEvent("DeviceConnected")
                    connect = "yes"

                page = response.read()

                self.lock.acquire()
                for i, in_, out_ in enumerate(self.states[:], 1):

                    if 'input name="in%d" checked="checked"' % i not in page:
                        if in_:
                            self.states[i - 1][0] = False

                            self.plugin.TriggerEvent(
                                suffix="Input%d.Off" % i,
                                payload="In/%d/0" % i
                            )
                    elif in_ is False:
                        self.states[i - 1][0] = True

                        self.plugin.TriggerEvent(
                            suffix="Input%d.On" % i,
                            payload="In/%d/1" % i
                        )

                    if 'input name="out%d" checked="checked"' % i not in page:
                        if out_:
                            self.states[i - 1][1] = False

                            self.plugin.TriggerEvent(
                                suffix="Output%d.Off" % i,
                                payload="In/%d/0" % i
                            )
                    elif out_ is False:
                        self.states[i - 1][1] = True

                        self.plugin.TriggerEvent(
                            suffix="Output%d.On" % i,
                            payload="In/%d/1" % i
                        )
                self.lock.release()
            finally:
                self.event.wait(self.interval)

        eg.Print("ELV IPIO88: Stopped reading from " + self.url.split('/')[2])

    def stop(self):
        self.event.set()
        try:
            self.thread.join(self.interval * 1.10)
        except threading.ThreadError:
            pass

    def change_state(self, index, state, value):
        self.lock.acquire()
        self.states[index][state] = value
        self.lock.release()


class IPIO88(eg.PluginBase):
    def __init__(self):
        self.AddAction(GetAllInputStates)
        self.AddAction(GetInputState)
        self.AddAction(GetAllOutputStates)
        self.AddAction(GetOutputState)
        self.AddAction(SwitchAllOutputStatesOn)
        self.AddAction(SwitchAllOutputStatesOff)
        self.AddAction(SwitchOutputStateOn)
        self.AddAction(SwitchOutputStateOff)
        self.AddAction(DeleteAllinternalIOBindings)

        self.thread = PollThread(self)

    def __start__(self, ip, refresh):
        self.ip = ip
        self.thread.start(ip, refresh)

    def __stop__(self):
        self.thread.stop()

    def Configure(self, ip="192.168.100.1", refresh="2"):
        helpString = (
            "Please configure the IP adress of the IPIO88 device you want to "
            "control.\n\n"
            "Format is xxx.xxx.xxx.xxx e.g: 192.168.100.1"
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)
        ipEdit = panel.TextCtrl(ip)
        refreshEdit = panel.TextCtrl(refresh)

        panel.AddLine(helpLabel)
        panel.AddLine("Device IP adress: ", ipEdit)
        panel.AddLine("Refresh Cycle Time (seconds): ", refreshEdit)

        while panel.Affirmed():
            panel.SetResult(
                ipEdit.GetValue(),
                refreshEdit.GetValue()
            )

    def send(self, pg, **kwargs):

        command = "http://%s/ipio.cgi?pg=%s" % (self.ip, pg)

        for key, value in kwargs.items():
            command += '&%s=%s' % (key, value)
        command += '&end=' + pg

        try:
            response = urllib.urlopen(command)
        except urllib.HTTPError, urllib.URLError:
            import traceback
            traceback.print_exc()
        else:
            return response.read()


class GetAllInputStates(eg.ActionBase):
    name = "Get all Input States"

    def __call__(self):

        for r in range(9):
            self.plugin.thread.change_state(r, 0, "?")


class GetInputState(eg.ActionBase):
    name = "Get single Input State"

    def __call__(self, InChannel):

        if InChannel.isdigit():
            InChannel = int(InChannel)

        if not isinstance(InChannel, int):
            eg.PrintError("Invalid Channel Number %s!" % str(InChannel))
            return

        if 1 > InChannel > 8:
            eg.PrintError("Invalid Channel Number %d!" % InChannel)
            return

        eg.Print("Reading channel %d ..." % InChannel)
        self.plugin.thread.change_state(InChannel - 1, 0, '?')

    def Configure(self, InChannel="1"):
        helpString = (
            "Please configure Input Channel you want to read.\n\n"
            "Channel number between 1 and 8 is allowed."
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)
        inStringEdit = panel.TextCtrl(InChannel)
        panel.AddLine(helpLabel)
        panel.AddLine("Channel number: ", inStringEdit)

        while panel.Affirmed():
            panel.SetResult(inStringEdit.GetValue())


class GetAllOutputStates(eg.ActionBase):
    name = "Get all Output States"

    def __call__(self):

        for r in range(9):
            self.plugin.thread.change_states(r, 1, "?")


class GetOutputState(eg.ActionBase):
    name = "Get single Output State"

    def __call__(self, OutChannel):

        if OutChannel.isdigit():
            OutChannel = int(OutChannel)

        if not isinstance(OutChannel, int):
            eg.PrintError("Invalid Channel Number %s!" % str(OutChannel))
            return

        if 1 > InChannel > 8:
            eg.PrintError("Invalid Channel Number %d!" % OutChannel)
            return

        eg.Print("Reading channel %d ..." % OutChannel)
        self.plugin.thread.change_state(OutChannel - 1, 1, '?')

    def Configure(self, OutChannel="1"):
        helpString = (
            "Please configure Output channel you want to read.\n\n"
            "Channel number between 1 and 8 is allowed."
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)
        inStringEdit = panel.TextCtrl(OutChannel)
        panel.AddLine(helpLabel)
        panel.AddLine("Channel number: ", inStringEdit)

        while panel.Affirmed():
            panel.SetResult(inStringEdit.GetValue())


class SwitchAllOutputStatesOn(eg.ActionBase):
    name = "Switch all Output States ON"

    def __call__(self):
        self.plugin.send(
            pg='main',
            out1='on',
            out2='on',
            out3='on',
            out4='on',
            out5='on',
            out6='on',
            out7='on',
            out8='on'
        )


class SwitchAllOutputStatesOff(eg.ActionBase):
    name = "Switch all Output States OFF"

    def __call__(self):
        self.plugin.send(
            pg='main',
            out1='off',
            out2='off',
            out3='off',
            out4='off',
            out5='off',
            out6='off',
            out7='off',
            out8='off'
        )


class SwitchOutputStateOn(eg.ActionBase):
    name = "Switch single Output State ON"

    def __call__(self, OutChannel):

        if OutChannel.isdigit():
            OutChannel = int(OutChannel)

        if not isinstance(OutChannel, int):
            eg.PrintError("Invalid Channel Number %s!" % str(OutChannel))
            return

        if 1 > InChannel > 8:
            eg.PrintError("Invalid Channel Number %d!" % OutChannel)
            return

        eg.Print("Switch Output channel %d ..." % OutChannel)

        command = {'out%d' % (OutChannel - 1,): 'on'}
        self.plugin.send(pg='main', **command)

    def Configure(self, OutChannel="1"):
        helpString = (
            "Please configure Output channel you want to switch ON.\n\n"
            "Channel number between 1 and 8 is allowed."
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)
        inStringEdit = panel.TextCtrl(OutChannel)
        panel.AddLine(helpLabel)
        panel.AddLine("Channel number: ", inStringEdit)

        while panel.Affirmed():
            panel.SetResult(inStringEdit.GetValue())


class SwitchOutputStateOff(eg.ActionBase):
    name = "Switch single Output State OFF"

    def __call__(self, OutChannel):

        if OutChannel.isdigit():
            OutChannel = int(OutChannel)

        if not isinstance(OutChannel, int):
            eg.PrintError("Invalid Channel Number %s!" % str(OutChannel))
            return

        if 1 > InChannel > 8:
            eg.PrintError("Invalid Channel Number %d!" % OutChannel)
            return

        eg.Print("Switch Output channel %d ..." % OutChannel)

        command = {'out%d' % (OutChannel - 1,): 'off'}
        self.plugin.send(pg='main', **command)

    def Configure(self, OutChannel="1"):
        helpString = (
            "Please configure Output channel you want to switch OFF.\n\n"
            "Channel number between 1 and 8 is allowed."
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)
        inStringEdit = panel.TextCtrl(OutChannel)
        panel.AddLine(helpLabel)
        panel.AddLine("Channel number: ", inStringEdit)

        while panel.Affirmed():
            panel.SetResult(inStringEdit.GetValue())


class DeleteAllinternalIOBindings(eg.ActionBase):
    name = "Delete all internal device I/O bindings"

    def __call__(self):
        command = {
            '1A': 'fallende+Flanke',
            '1P': '0',
            '2A': 'positive+Logik',
            '2P': '0',
            '3A': 'positive+Logik',
            '3P': '0',
            '4A': 'positive+Logik',
            '4P': '0',
            '5A': 'positive+Logik',
            '5P': '0',
            '6A': 'positive+Logik',
            '6P': '0',
            '7A': 'positive+Logik',
            '7P': '0',
            '8A': 'positive+Logik',
            '8P': '0',
            'set': '%DCbernehmen'
        }

        self.plugin.send(pg='io', **command)
        eg.PrintNotice("Bindings deleted")

    def Configure(self):
        helpString = (
            "WARNING! Excecution of this action deletes all internal\n"
            "In/Out bindings set in the device internally. \n\n"
            "This CAN NOT BE UNDONE!\n\n"
            "Useful if you want to control the In-Out behavior with EG soley."
        )

        panel = eg.ConfigPanel(self)
        helpLabel = panel.StaticText(helpString)

        panel.AddLine(helpLabel)

        while panel.Affirmed():
            panel.SetResult()
