# -----------------------------------------------------------------------------
# Copyright (c) Peter Bingham  (petersbingham@hotmail.co.uk)
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# -----------------------------------------------------------------------------

import serial
import time
from . import dae_RelayBoard_Common


class DAE_RelayBoard_VCP:
    TIMEOUT = 1.0
    DELIM = "//"
    DEFAULT_CMDDELAY = 0.05  # Can override this default with zero and control delay in client code. Says 5ms but was getting corruption even around 20ms, so increased to 50ms. See test16Intervals.

    def __init__(self, *args):
        if len(args) > 0:
            self.cmdDelay = args[0]
        else:
            self.cmdDelay = self.DEFAULT_CMDDELAY
        self.ser = None

    def initialise(self, *args):
        self.disconnect()
        self.portstring = args[0]
        self.ser = self._connectToSerial()

    def disconnect(self):
        if self.ser is not None:
            self.ser.close()
            self.ser = None

    def _connectToSerial(self):
        try:
            return serial.Serial(
                port=self.portstring,
                baudrate=9600,
                parity=serial.PARITY_NONE,
                stopbits=serial.STOPBITS_ONE,
                bytesize=serial.EIGHTBITS,
                xonxoff=0,  # no software flow control
                rtscts=0,  # no RTS/CTS flow control
                timeout=self.TIMEOUT,
                writeTimeout=self.TIMEOUT,
            )
        except serial.SerialException:
            raise dae_RelayBoard_Common.Denkovi_Exception(
                "Could not connect to relay board"
            )

    ###############
    ### Setters ###
    ###############

    def setAllStatesOn(self):
        self._setAllStates(True)

    def setAllStatesOff(self):
        self._setAllStates(False)

    def setStates(self, states):
        for relay in states:
            self.setState(relay, states[relay])

    def setState(self, relay, on):
        if relay > self.NUMRELAYS or relay < 1:
            raise dae_RelayBoard_Common.Denkovi_Exception("Relay Index out of bound")
        else:
            self._prepForCmd()
            if on:
                stateString = "+"
            else:
                stateString = "-"
            numString = "%02d" % relay
            self._write(numString + stateString)

    def _setAllStates(self, on):
        self._prepForCmd()
        if on:
            stateString = "on"
        else:
            stateString = "off"
        self._write(stateString)

    ###############
    ### Getters ###
    ###############

    def getStates(self):
        self._prepForCmd()
        stateString = self._getStateString()
        states = {}
        for relay in range(1, self.NUMRELAYS + 1):
            states[relay] = self._getState(relay, stateString)
        return states

    def getState(self, relay):
        if relay > self.NUMRELAYS or relay < 1:
            raise dae_RelayBoard_Common.Denkovi_Exception("Relay Index out of bound")
        else:
            self._prepForCmd()
            stateString = self._getStateString()
            return self._getState(relay, stateString)

    def _getStateString(self):
        self._write("ask")
        time.sleep(self.cmdDelay)
        return self._read(2)

    def _getState(self, relay, stateString):
        if relay > self.NUMRELAYS / 2:
            stateStringIndex = 1
            shifts = relay - self.NUMRELAYS / 2 - 1
        else:
            stateStringIndex = 0
            shifts = relay - 1

        field = 128 >> int(shifts)
        try:
            return field & ord(stateString[stateStringIndex]) > 0  # Python 2
        except TypeError:
            return field & stateString[stateStringIndex] > 0  # Python 3

    ###############
    ### Helpers ###
    ###############

    def _prepForCmd(self):
        if self.ser == None:
            raise dae_RelayBoard_Common.Denkovi_Exception("Board Not Initialised")
        else:
            time.sleep(self.cmdDelay)
            self.ser.flushInput()
            self.ser.flushOutput()

    def _write(self, cmdString):
        try:
            writeString = cmdString + self.DELIM
            bytesWritten = self.ser.write(writeString.encode())
            if len(writeString) != bytesWritten:
                raise dae_RelayBoard_Common.Denkovi_Exception("Error during write")
        except (serial.SerialException, serial.SerialTimeoutException):
            raise dae_RelayBoard_Common.Denkovi_Exception("Error while writing")

    def _read(self, numBytes):
        try:
            retString = self.ser.read(size=numBytes)
            if len(retString) < numBytes:
                raise dae_RelayBoard_Common.Denkovi_Exception(
                    "Timeout waiting for response"
                )
            return retString
        except (serial.SerialException, serial.SerialTimeoutException):
            raise dae_RelayBoard_Common.Denkovi_Exception("Error while writing")


class DAE_RelayBoard_16(DAE_RelayBoard_VCP):
    NUMRELAYS = 16
