# Copyright (C) 2021 Analog Devices, Inc.
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#     - Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     - Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in
#       the documentation and/or other materials provided with the
#       distribution.
#     - Neither the name of Analog Devices, Inc. nor the names of its
#       contributors may be used to endorse or promote products derived
#       from this software without specific prior written permission.
#     - The use of this software may or may not infringe the patent rights
#       of one or more patent holders.  This license does not release you
#       from the requirement that you obtain separate licenses from these
#       patent holders to use this software.
#     - Use of the software either in source or binary form, must be run
#       on or directly connected to an Analog Devices Inc. component.
#
# THIS SOFTWARE IS PROVIDED BY ANALOG DEVICES "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED.
#
# IN NO EVENT SHALL ANALOG DEVICES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, INTELLECTUAL PROPERTY
# RIGHTS, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

from adi.attribute import attribute
from adi.context_manager import context_manager


class yline(object):
    def __init__(self, dev, x, line):
        """Initializer for a Y line of switches."""
        self._line = line
        self._x = x
        self._dev = dev

    def __str__(self):
        """String representation of a Y line of switches."""
        return "[" + ", ".join([str(num) for num in self._line]) + "]"

    def __getitem__(self, y):
        """Read access a Y line using python array indexing."""
        return self._line[y]

    def __setitem__(self, y, value):
        """Write access a Y line using python array indexing."""
        self._dev._switch(self._x, y, value, self._dev._ldsw)
        self._line[y] = value


class adg2128(attribute, context_manager):
    """ADG2128 cross point switch."""

    def __init__(self, uri=""):
        """Initializer for the adg2128 cross point switch device."""
        self._device_name = "adg2128"
        context_manager.__init__(self, uri, self._device_name)

        self._i2c_devs = []
        self._xmax = 0
        self._xline = []
        self._ldsw = True

        self._ctrl = self._ctx.find_device(self._device_name)

        if not self._ctrl:
            raise Exception(self._device_name + " device not found")

    @property
    def immediate(self):
        """Specify whether the writing of a switch is immediate or not.

        When it's not immediate, the new switch configuration is only
        latched into the device (see LDSW in datasheet).
        """
        return self._ldsw

    @immediate.setter
    def immediate(self, value):
        self._ldsw = value

    def add(self, addr):
        """
        Add device by its i2c address.

        Multiple devices may be added provided they all have their Y terminals
        connected (common Y configuration).
        1x adg2128 is represented by a 12 by 8 matrix
        2x adg2128 is represented by a 24 by 8 matrix
        ...

        Arguments:
        addr - device address on i2c bus
        """
        self._i2c_devs.append(addr)
        self._xmax += 12
        for x in range(self._xmax - 12, self._xmax):
            y = yline(self, x, [False for y in range(8)])
            self._xline.append(y)

    def _read(self, addr):
        """Direct Register Access via debugfs."""
        self._set_iio_debug_attr_str("direct_reg_access", addr, self._ctrl)
        return self._get_iio_debug_attr_str("direct_reg_access", self._ctrl)

    def _write(self, addr, val):
        """Direct Register Access via debugfs."""
        self._set_iio_debug_attr_str(
            "direct_reg_access", "0x{:X} 0x{:X}".format(addr, val), self._ctrl
        )

    def _switch(self, x, y, closed, immediate):
        data = closed << 15
        ax_lookup = [0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13]
        ax = ax_lookup[x % 12] << 11
        ay = y << 8
        ldsw = immediate
        addr = self._i2c_devs[x // 12]
        val = data | ax | ay | ldsw
        self._write(addr, val)

    def _read_x(self, x):
        addr = self._i2c_devs[int(x) // 12]
        x_lookup = [
            0b00110100,
            0b00111100,
            0b01110100,
            0b01111100,
            0b00110101,
            0b00111101,
            0b01110101,
            0b01111101,
            0b00110110,
            0b00111110,
            0b01110110,
            0b01111110,
        ]
        val = x_lookup[x % 12] << 8
        self._write(addr, val)
        return self._read(addr)

    def __str__(self):
        """String representation of the cross point switch."""
        return "\n".join([f"x{idx}: {x}" for idx, x in enumerate(self._xline)])

    def __getitem__(self, x):
        """Read access a X line using python array indexing."""
        return self._xline[x]

    def __setitem__(self, x, value):
        """Write access a X line using python array indexing."""
        for y, closed in enumerate(value):
            self._xline[x][y] = closed

    def open_all(self):
        """
        Open all switches.

        For each device, iterate all x-y combinations and
        open all the switches at once.
        """
        ldsw = self._ldsw
        for x in range(self._xmax):
            for y in range(8):
                self._ldsw = False
                if (y == 7) and (x % 12 == 11):
                    self._ldsw = True
                self[x][y] = False
        self._ldsw = ldsw
