# The MIT License (MIT)
#
# Copyright (c) 2020 Melissa LeBlanc-Williams for Adafruit Industries
#
# 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.
"""
`adafruit_platformdetect.board`
================================================================================

Detect boards

* Author(s): Melissa LeBlanc-Williams

Implementation Notes
--------------------

**Software and Dependencies:**

* Linux and Python 3.5 or Higher

"""

# imports
import os
import re
from adafruit_platformdetect.constants import boards, chips

__version__ = "0.0.0-auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_PlatformDetect.git"


class Board:
    """Attempt to detect specific boards."""

    def __init__(self, detector):
        self.detector = detector
        self._board_id = None

    # pylint: disable=invalid-name, protected-access
    @property
    def id(self):
        """Return a unique id for the detected board, if any."""
        # There are some times we want to trick the platform detection
        # say if a raspberry pi doesn't have the right ID, or for testing

        # Caching
        if self._board_id:
            return self._board_id

        try:
            return os.environ["BLINKA_FORCEBOARD"]
        except KeyError:  # no forced board, continue with testing!
            pass

        chip_id = self.detector.chip.id
        board_id = None

        if chip_id == chips.BCM2XXX:
            board_id = self._pi_id()
        elif chip_id == chips.AM33XX:
            board_id = self._beaglebone_id()
        elif chip_id == chips.DRA74X:
            board_id = self._bbai_id()
        elif chip_id == chips.GENERIC_X86:
            board_id = boards.GENERIC_LINUX_PC
        elif chip_id == chips.SUN8I:
            board_id = self._armbian_id()
        elif chip_id == chips.SAMA5:
            board_id = self._sama5_id()
        elif chip_id == chips.IMX8MX:
            board_id = self._imx8mx_id()
        elif chip_id == chips.IMX6ULL:
            board_id = self._imx6ull_id()
        elif chip_id == chips.ESP8266:
            board_id = boards.FEATHER_HUZZAH
        elif chip_id == chips.SAMD21:
            board_id = boards.FEATHER_M0_EXPRESS
        elif chip_id == chips.STM32F405:
            board_id = boards.PYBOARD
        elif chip_id == chips.S805:
            board_id = boards.ODROID_C1
        elif chip_id == chips.S905:
            board_id = boards.ODROID_C2
        elif chip_id == chips.S905X3:
            board_id = boards.ODROID_C4
        elif chip_id == chips.S922X:
            board_id = boards.ODROID_N2
        elif chip_id == chips.EXYNOS5422:
            board_id = boards.ODROID_XU4
        elif chip_id == chips.FT232H:
            board_id = boards.FTDI_FT232H
        elif chip_id == chips.APQ8016:
            board_id = boards.DRAGONBOARD_410C
        elif chip_id in (chips.T210, chips.T186, chips.T194):
            board_id = self._tegra_id()
        elif chip_id == chips.HFU540:
            board_id = self._sifive_id()
        elif chip_id == chips.MCP2221:
            board_id = boards.MICROCHIP_MCP2221
        elif chip_id == chips.BINHO:
            board_id = boards.BINHO_NOVA
        elif chip_id == chips.LPC4330:
            board_id = boards.GREATFET_ONE
        elif chip_id == chips.MIPS24KC:
            board_id = boards.ONION_OMEGA
        elif chip_id == chips.MIPS24KEC:
            board_id = boards.ONION_OMEGA2
        elif chip_id == chips.ZYNQ7000:
            board_id = self._pynq_id()
        elif chip_id == chips.A64:
            board_id = self._pine64_id()
        elif chip_id == chips.H6:
            board_id = self._pine64_id()
        elif chip_id == chips.H5:
            board_id = self._armbian_id()
        elif chip_id == chips.H616:
            board_id = self._armbian_id()
        elif chip_id == chips.A33:
            board_id = self._clockwork_pi_id()
        elif chip_id == chips.RK3308:
            board_id = self._rock_pi_id()
        elif chip_id == chips.RK3399:
            board_id = self._rock_pi_id()
        elif chip_id == chips.ATOM_X5_Z8350:
            board_id = self._rock_pi_id()
        elif chip_id == chips.RK3288:
            board_id = self._asus_tinker_board_id()
        elif chip_id == chips.RYZEN_V1605B:
            board_id = self._udoo_id()
        elif chip_id == chips.PENTIUM_N3710:
            board_id = self._udoo_id()
        elif chip_id == chips.STM32MP157:
            board_id = self._stm32mp1_id()
        elif chip_id == chips.MT8167:
            board_id = boards.CORAL_EDGE_TPU_DEV_MINI

        self._board_id = board_id
        return board_id

    # pylint: enable=invalid-name

    def _pi_id(self):
        """Try to detect id of a Raspberry Pi."""
        # Check for Pi boards:
        pi_rev_code = self._pi_rev_code()
        if pi_rev_code:
            for model, codes in boards._PI_REV_CODES.items():
                if pi_rev_code in codes:
                    return model

        # We may be on a non-Raspbian OS, so try to lazily determine
        # the version based on `get_device_model`
        else:
            pi_model = self.detector.get_device_model()
            if pi_model:
                pi_model = pi_model.upper().replace(" ", "_")
                if "PLUS" in pi_model:
                    re_model = re.search(r"(RASPBERRY_PI_\d).*([AB]_*)(PLUS)", pi_model)
                elif "CM" in pi_model:  # untested for Compute Module
                    re_model = re.search(r"(RASPBERRY_PI_CM)(\d)", pi_model)
                else:  # untested for non-plus models
                    re_model = re.search(r"(RASPBERRY_PI_\d).*([AB])", pi_model)

                if re_model:
                    pi_model = "".join(re_model.groups())
                    available_models = boards._PI_REV_CODES.keys()
                    for model in available_models:
                        if model == pi_model:
                            return model

        return None

    def _pi_rev_code(self):
        """Attempt to find a Raspberry Pi revision code for this board."""
        # 2708 is Pi 1
        # 2709 is Pi 2
        # 2835 is Pi 3 (or greater) on 4.9.x kernel
        # Anything else is not a Pi.
        if self.detector.chip.id != chips.BCM2XXX:
            # Something else, not a Pi.
            return None
        rev = self.detector.get_cpuinfo_field("Revision")

        if rev is not None:
            return rev

        try:
            with open("/proc/device-tree/system/linux,revision", "rb") as revision:
                rev_bytes = revision.read()

                if rev_bytes[:1] == b"\x00":
                    rev_bytes = rev_bytes[1:]

                return rev_bytes.hex()
        except FileNotFoundError:
            return None

    # pylint: disable=no-self-use
    def _beaglebone_id(self):
        """Try to detect id of a Beaglebone."""
        try:
            with open("/sys/bus/nvmem/devices/0-00500/nvmem", "rb") as eeprom:
                eeprom_bytes = eeprom.read(16)
        except FileNotFoundError:
            return None

        if eeprom_bytes[:4] != b"\xaaU3\xee":
            return None

        # special condition for BeagleBone Green rev. 1A
        # refer to GitHub issue #57 in this repo for more info
        if eeprom_bytes == b"\xaaU3\xeeA335BNLT\x1a\x00\x00\x00":
            return boards.BEAGLEBONE_GREEN

        id_string = eeprom_bytes[4:].decode("ascii")
        for model, bb_ids in boards._BEAGLEBONE_BOARD_IDS.items():
            for bb_id in bb_ids:
                if id_string == bb_id[1]:
                    return model

        return None

    # pylint: enable=no-self-use

    def _bbai_id(self):
        """Try to detect id of a Beaglebone AI related board."""
        board_value = self.detector.get_device_model()
        if "BeagleBone AI" in board_value:
            return boards.BEAGLEBONE_AI
        return None

    # pylint: disable=too-many-return-statements
    def _armbian_id(self):
        """Check whether the current board is an OrangePi board."""
        board_value = self.detector.get_armbian_release_field("BOARD")
        board = None

        if board_value == "orangepipc":
            board = boards.ORANGE_PI_PC
        elif board_value == "orangepi-r1":
            board = boards.ORANGE_PI_R1
        elif board_value == "orangepizero":
            board = boards.ORANGE_PI_ZERO
        elif board_value == "orangepione":
            board = boards.ORANGE_PI_ONE
        elif board_value == "orangepilite":
            board = boards.ORANGE_PI_LITE
        elif board_value == "orangepiplus2e":
            board = boards.ORANGE_PI_PLUS_2E
        elif board_value == "orangepipcplus":
            board = boards.ORANGE_PI_PC_PLUS
        elif board_value == "pinebook-a64":
            board = boards.PINEBOOK
        elif board_value == "pineH64":
            board = boards.PINEH64
        elif board_value == "orangepi2":
            board = boards.ORANGE_PI_2
        elif board_value == "bananapim2zero":
            board = boards.BANANA_PI_M2_ZERO
        elif board_value == "orangepizeroplus2-h5":
            board = boards.ORANGE_PI_ZERO_PLUS_2H5
        elif board_value == "orangepizeroplus":
            board = boards.ORANGE_PI_ZERO_PLUS
        elif board_value == "nanopiair":
            board = boards.NANOPI_NEO_AIR
        elif board_value == "nanopiduo2":
            board = boards.NANOPI_DUO2

        return board

    # pylint: enable=too-many-return-statements

    # pylint: enable=too-many-return-statements

    def _sama5_id(self):
        """Check what type sama5 board."""
        board_value = self.detector.get_device_model()
        if "Giant Board" in board_value:
            return boards.GIANT_BOARD
        return None

    def _stm32mp1_id(self):
        """Check what type stm32mp1 board."""
        board_value = self.detector.get_device_model()
        if "STM32MP157C-DK2" in board_value:
            return boards.STM32MP157C_DK2
        if "LubanCat" in board_value:
            return boards.LUBANCAT_STM32MP157
        if "OSD32MP1-BRK" in board_value:
            return boards.OSD32MP1_BRK
        return None

    def _imx8mx_id(self):
        """Check what type iMX8M board."""
        board_value = self.detector.get_device_model()
        if "Phanbell" in board_value:
            return boards.CORAL_EDGE_TPU_DEV
        return None

    def _imx6ull_id(self):
        """Check what type iMX6ULL board."""
        board_value = self.detector.get_device_model()
        if "LubanCat" in board_value or "Embedfire" in board_value:
            return boards.LUBANCAT_IMX6ULL
        return None

    def _tegra_id(self):
        """Try to detect the id of aarch64 board."""
        compatible = self.detector.get_device_compatible()
        if not compatible:
            return None
        compats = compatible.split("\x00")
        for board_id, board_compats in boards._JETSON_IDS:
            if any(v in compats for v in board_compats):
                return board_id
        return None

    def _sifive_id(self):
        """Try to detect the id for Sifive RISCV64 board."""
        board_value = self.detector.get_device_model()
        if "hifive-unleashed-a00" in board_value:
            return boards.SIFIVE_UNLEASHED
        return None

    def _pine64_id(self):
        """Try to detect the id for Pine64 board or device."""
        board_value = self.detector.get_device_model()
        board = None
        if "pine64" in board_value.lower():
            board = boards.PINE64
        elif "pine h64" in board_value.lower():
            board = boards.PINEH64
        elif "pinebook" in board_value.lower():
            board = boards.PINEBOOK
        elif "pinephone" in board_value.lower():
            board = boards.PINEPHONE
        elif "sopine" in board_value.lower():
            board = boards.SOPINE
        return board

    # pylint: disable=no-self-use
    def _pynq_id(self):
        """Try to detect the id for Xilinx PYNQ boards."""
        try:
            with open("/proc/device-tree/chosen/pynq_board", "r") as board_file:
                board_model = board_file.read()
                match = board_model.upper().replace("-", "_").rstrip("\x00")
                for model in boards._PYNQ_IDS:
                    if model == match:
                        return model

                return None

        except FileNotFoundError:
            return None

    def _rock_pi_id(self):
        """Check what type of Rock Pi board."""
        board_value = self.detector.get_device_model()
        board = None
        if board_value and "ROCK Pi S" in board_value:
            board = boards.ROCK_PI_S
        if board_value and "ROCK PI 4" in board_value.upper():
            board = boards.ROCK_PI_4
        if self.detector.check_board_name_value() == "ROCK Pi X":
            board = boards.ROCK_PI_X
        return board

    def _clockwork_pi_id(self):
        """Check what type of Clockwork Pi board."""
        board_value = self.detector.get_device_model()
        board = None
        if board_value and "Clockwork CPI3" in board_value:
            board = boards.CLOCKWORK_CPI3
        return board

    def _udoo_id(self):
        """Try to detect the id of udoo board."""
        board_asset_tag = self.detector.check_board_asset_tag_value()
        for board_id, board_tags in boards._UDOO_BOARD_IDS.items():
            if any(v == board_asset_tag for v in board_tags):
                return board_id

        if self.detector.check_board_name_value() == "UDOO x86":
            return boards.UDOO_X86

        return None

    def _asus_tinker_board_id(self):
        """Check what type of Tinker Board."""
        board_value = self.detector.get_device_model()
        board = None
        if board_value and "ASUS Tinker Board" in board_value:
            board = boards._ASUS_TINKER_BOARD_IDS
        return board

    @property
    def any_nanopi(self):
        """Check whether the current board is any defined Nano Pi."""
        return self.id in boards._NANOPI_IDS

    @property
    def any_96boards(self):
        """Check whether the current board is any 96boards board."""
        return self.id in boards._LINARO_96BOARDS_IDS

    @property
    def any_raspberry_pi(self):
        """Check whether the current board is any Raspberry Pi."""
        return self._pi_rev_code() is not None

    @property
    def any_raspberry_pi_40_pin(self):
        """Check whether the current board is any 40-pin Raspberry Pi."""
        return self.id in boards._RASPBERRY_PI_40_PIN_IDS

    @property
    def any_raspberry_pi_cm(self):
        """Check whether the current board is any Compute Module Raspberry Pi."""
        return self.id in boards._RASPBERRY_PI_CM_IDS

    @property
    def any_beaglebone(self):
        """Check whether the current board is any Beaglebone-family system."""
        return self.id in boards._BEAGLEBONE_IDS

    @property
    def any_orange_pi(self):
        """Check whether the current board is any defined Orange Pi."""
        return self.id in boards._ORANGE_PI_IDS

    @property
    def any_lubancat(self):
        """Check whether the current board is any defined lubancat."""
        return self.id in boards._LUBANCAT_IDS

    @property
    def any_coral_board(self):
        """Check whether the current board is any defined Coral."""
        return self.id in boards._CORAL_IDS

    @property
    def any_pynq_board(self):
        """Check whether the current board is any defined PYNQ Board."""
        return self.id in boards._PYNQ_IDS

    @property
    def any_giant_board(self):
        """Check whether the current board is any defined Giant Board."""
        return self.GIANT_BOARD

    @property
    def any_odroid_40_pin(self):
        """Check whether the current board is any defined 40-pin Odroid."""
        return self.id in boards._ODROID_40_PIN_IDS

    @property
    def any_jetson_board(self):
        """Check whether the current board is any defined Jetson Board."""
        return self.id in [v[0] for v in boards._JETSON_IDS]

    @property
    def any_sifive_board(self):
        """Check whether the current board is any defined Jetson Board."""
        return self.id in boards._SIFIVE_IDS

    @property
    def any_onion_omega_board(self):
        """Check whether the current board is any defined OpenWRT board."""
        return self.id in boards._ONION_OMEGA_BOARD_IDS

    @property
    def any_pine64_board(self):
        """Check whether the current board is any Pine64 device."""
        return self.id in boards._PINE64_DEV_IDS

    @property
    def any_rock_pi_board(self):
        """Check whether the current board is any Rock Pi device."""
        return self.id in boards._ROCK_PI_IDS

    @property
    def any_clockwork_pi_board(self):
        """Check whether the current board is any Clockwork Pi device."""
        return self.CLOCKWORK_CPI3

    @property
    def any_udoo_board(self):
        """Check to see if the current board is an UDOO board"""
        return self.id in boards._UDOO_BOARD_IDS

    @property
    def any_asus_tinker_board(self):
        """Check to see if the current board is an ASUS Tinker Board"""
        return self.id in boards._ASUS_TINKER_BOARD_IDS

    @property
    def any_stm32mp1(self):
        """Check whether the current board is any stm32mp1 board."""
        return self.id in boards._STM32MP1_IDS

    @property
    def any_embedded_linux(self):
        """Check whether the current board is any embedded Linux device."""
        return any(
            [
                self.any_raspberry_pi,
                self.any_beaglebone,
                self.any_orange_pi,
                self.any_nanopi,
                self.any_giant_board,
                self.any_jetson_board,
                self.any_coral_board,
                self.any_odroid_40_pin,
                self.any_96boards,
                self.any_sifive_board,
                self.any_onion_omega_board,
                self.any_pine64_board,
                self.any_pynq_board,
                self.any_rock_pi_board,
                self.any_clockwork_pi_board,
                self.any_udoo_board,
                self.any_asus_tinker_board,
                self.any_stm32mp1,
                self.any_lubancat,
            ]
        )

    @property
    def ftdi_ft232h(self):
        """Check whether the current board is an FTDI FT232H."""
        return self.id == boards.FTDI_FT232H

    @property
    def microchip_mcp2221(self):
        """Check whether the current board is a Microchip MCP2221."""
        return self.id == boards.MICROCHIP_MCP2221

    @property
    def binho_nova(self):
        """Check whether the current board is an BINHO NOVA."""
        return self.id == boards.BINHO_NOVA

    @property
    def greatfet_one(self):
        """Check whether the current board is a GreatFET One."""
        return self.id == boards.GREATFET_ONE

    def __getattr__(self, attr):
        """
        Detect whether the given attribute is the currently-detected board.  See list
        of constants at the top of this module for available options.
        """
        if self.id == attr:
            return True
        return False
