# PyEPD
# Gordon Inggs (gordon.e.inggs@ieee.org)
# November 2017

import numpy


class DisplayPanelController:
    """
    Base class for Mpico display panel controllers.
    """
    # Header constants
    COLOUR_DEPTH = [0x01]
    UNUSED = [0x00] * 9

    def generate_header(self):
        """
        Generates EPD format header

        :return: 16 element numpy array of uint8s
        """
        one_value_two_bytes = lambda val: [val >> 8, val & 255]

        header = numpy.array([self.panel_type] +
                             one_value_two_bytes(self.x_res) +
                             one_value_two_bytes(self.y_res) +
                             DisplayPanelController.COLOUR_DEPTH +
                             [self.pixel_format] +
                             DisplayPanelController.UNUSED
                             , dtype=numpy.uint8)

        return header

    def generate_epd_image_data(self, image_data):
        """
        Converts 1-bit image_data bytes into bits

        :param image_data: 1-bit monochrome, image byte array. Siz
        :return:
        """
        new_size = (self.x_res * self.y_res // 8,)

        new_data = numpy.zeros(shape=new_size, dtype=numpy.uint8)

        if self.pixel_format == 0x04:
            row = 30
            s = 1
            for i in numpy.arange(0, image_data.size, 16):
                # compressing bytes into bits
                offset = row - s
                new_data[offset] = 0x00
                new_data[offset] |= (image_data[i + 6] << 7) & 0x80
                new_data[offset] |= (image_data[i + 14] << 6) & 0x40
                new_data[offset] |= (image_data[i + 4] << 5) & 0x20
                new_data[offset] |= (image_data[i + 12] << 4) & 0x10
                new_data[offset] |= (image_data[i + 2] << 3) & 0x08
                new_data[offset] |= (image_data[i + 10] << 2) & 0x04
                new_data[offset] |= (image_data[i + 0] << 1) & 0x02
                new_data[offset] |= (image_data[i + 8] << 0) & 0x01

                offset = row + 30 - s
                new_data[offset] = 0x00
                new_data[offset] |= (image_data[i + 1] << 7) & 0x80
                new_data[offset] |= (image_data[i + 9] << 6) & 0x40
                new_data[offset] |= (image_data[i + 3] << 5) & 0x20
                new_data[offset] |= (image_data[i + 11] << 4) & 0x10
                new_data[offset] |= (image_data[i + 5] << 3) & 0x08
                new_data[offset] |= (image_data[i + 13] << 2) & 0x04
                new_data[offset] |= (image_data[i + 7] << 1) & 0x02
                new_data[offset] |= (image_data[i + 15] << 0) & 0x01

                # wrapping logic
                s += 1
                if s == 31:
                    s = 1
                    row += 60

        else:
            raise Exception("Pixel format {} not supported!".format(self.pixel_format))

        return new_data

    def assemble_epd(self, image_data):
        """
        Assemble final EPD file for this display panel controller

        :param image_data: monochromatic image data. Assumed to be generated by ImageHandler.convert
        :return: EPD image data as a numpy array of unit8s
        """
        epd_data = numpy.hstack((self.generate_header(),
                                 self.generate_epd_image_data(image_data)))

        return epd_data


class TC_P74_230(DisplayPanelController):
    """
    Implementation of TC-P74-230 Display Panel Controller
    AKA the 7.4" one.
    """

    def __init__(self):
        super().__init__()
        self.panel_type = 0x3A
        self.x_res = 480
        self.y_res = 800
        self.pixel_format = 0x04
