#
# This file is part of GreatFET
#

from ..interface import GreatFETInterface
from .firmware import DeviceFirmwareManager

from pygreat.comms import CommandFailureError


class SPIFlash(DeviceFirmwareManager, GreatFETInterface):
    """ Class representing an SPI flash connected to the GreatFET. """

    #
    # Common JEDEC manufacturer IDs for SPI flash chips.
    #
    JEDEC_MANUFACTURERS = {
        0xFF: "unknown",
        0x01: "AMD/Spansion/Cypress",
        0x04: "Fujitsu",
        0x1C: "eON",
        0x1F: "Atmel/Microchip",
        0x20: "Micron/Numonyx/ST",
        0x37: "AMIC",
        0x62: "SANYO",
        0x89: "Intel",
        0x8C: "ESMT",
        0xA1: "Fudan",
        0xAD: "Hyundai",
        0xBF: "SST",
        0xC2: "Micronix",
        0xC8: "Gigadevice",
        0xD5: "ISSI",
        0xEF: "Winbond",
    }

    #
    # Common JEDEC device IDs. Prefixed with their manufacturer for easy / unique lookup.
    #
    JEDEC_PARTS = {
        0xFFFFFF: "unknown part",
        0xEF3015: "W25X16L",
        0xEF3014: "W25X80L",
        0xEF3013: "W25X40L",
        0xEF3012: "W25X20L",
        0xEF3011: "W25X10L",
        0xEF4015: "W25Q16DV",
        0xEF4018: "W25Q16DV",
        0xC22515: "MX25L1635E",
        0xC22017: "MX25L6405D",
        0xC22016: "MX25L3205D",
        0xC22015: "MX25L1605D",
        0xC22014: "MX25L8005",
        0xC22013: "MX25L4005",
        0xC22010: "MX25L512E",
        0x204011: "M45PE10",
        0x202014: "M25P80",
        0x1f4501: "AT24DF081",
        0x1C3114: "EN25F80",
    }


    #
    # Common JEDEC capacity values.
    #
    JEDEC_CAPACITIES = {
        0x18: 0x1000000,
        0x17: 0x800000,
        0x16: 0x400000,
        0x15: 0x200000,
        0x14: 0x100000,
        0x13: 0x080000,
        0x12: 0x040000,
        0x11: 0x020000,
        0x10: 0x010000,
    }



    def _stuck_signal_check(self, fail_on_null_jedec_id=True):
        """ Attempts to detect a stuck MISO value; raises an IOError if one is detected. """

        # Attempt to read the raw values for the device's ID...
        info = self.api.query_device_id()

        # ... and check to see if it's stuck at 1/0
        if fail_on_null_jedec_id and (info == (0xff, 0xffff, 0xff)):
            raise IOError("spiflash: this target lacks a JEDEC ID, or DO/MISO appears to be stuck at '1'; check your connections?")
        if info == (0x00, 0x0000, 0x00):
            raise IOError("spiflash: DO/MISO appears to be stuck at '0'; check your connections")



    def _parse_jedec_id(self):
        """ Attempts to extract manufacturer name and string from JEDEC information. """

        manufacturer, part, capacity = self.api.query_device_id()

        # Correct the endianness of the part number.
        part = (part & 0xff) << 8 | (part >> 8)

        # Build a unique value that identifies the part and manufacturer -- this allows us to
        full_id = manufacturer << 16 | part

        self.manufacturer_id = manufacturer
        self.part_id = part
        self.jedec_capacity = capacity

        # Populate our information strings.
        self.manufacturer = self.JEDEC_MANUFACTURERS[manufacturer]  \
            if (manufacturer in self.JEDEC_MANUFACTURERS) else "unrecognized manufacturer"

        # If we don't currently have a capacity, and
        if (not self.maximum_address) and (capacity in self.JEDEC_CAPACITIES):
            self.maximum_address = self.JEDEC_CAPACITIES[capacity]

        self.part = self.JEDEC_PARTS[full_id] if (full_id in self.JEDEC_PARTS) else "unrecognized part"



    def __init__(self, board, autodetect=True, allow_fallback=False,
            page_size=256, pages=8192, maximum_address=None, allow_null_jedec=False,
            device_id=0, chip_select_port=0, chip_select_pin=15, force_page_size=None):
        """Set up a new SPI flash connection.

        Args:
            board -- The GreatFETBoard that will be programming our flash chip.
            autodetect -- If True, the API will attempt to automatically detect the flash's parameters.
            allow_fallback -- If False, we'll fail if autodetect is set and we can't autodetect the flash's paramters.
                If true, we'll fall-back to the keyword arguments provided
        """

        # Store a reference to the parent board, via which we'll program the
        # the actual SPI flash.
        self.board = board
        self.api = board.apis.spi_flash
        self.comms = board.comms
        self.capacity = 0

        # If autodetect is set to True, we'll try to automatically detect the device's topology.
        if autodetect:

            # Override the device ID to accept anything, as we're not tied to a specific device.
            device_id = 0

            self.api.initialize(0, 0, 0, chip_select_port, chip_select_pin, device_id)
            try:
                page_size, pages, size_in_bytes = self.api.query_topology()
                maximum_address = size_in_bytes - 1
            except CommandFailureError:

                # If we weren't able to read the SPDF, we either want to fall back to our
                # keyword arguments, or to throw an exception.
                if not allow_fallback:
                    raise
                else:
                    # If we do fall back, then check to make sure none of our signals are stuck at 1/0.
                    self._stuck_signal_check(fail_on_null_jedec_id=not allow_null_jedec)


        # If we have an argument forcing a given page size, override everything
        # we've worked out above with the forced page size.
        if force_page_size:
            total_size = page_size * pages
            page_size = force_page_size

            # Adjust the number of pages accordingly.
            pages = total_size // page_size


        # Store the limitations for this SPI flash.
        self.page_size = page_size
        self.maximum_address = maximum_address if maximum_address else (page_size * pages) - 1
        self.api.initialize(page_size, pages, page_size * pages, chip_select_port, chip_select_pin, device_id)

        # Extract manufacturer and device info, where possible.
        self._parse_jedec_id()

        # TODO: re-initialize the flash if the JEDEC identification updates our capacity?
        # TODO: decide if we want to do ^


