"""Text encoder for 2D datamatrix barcode encoder"""

__revision__ = "$Rev$"

from .reedsolomon import get_reed_solomon_code
import logging

log = logging.getLogger("datamatrix")

data_word_length = (3, 5, 8, 12, 18, 22, 30, 36, 44,
                    62, 86, 114, 144, 174, 204,
                    280, 368, 456, 576, 696, 816,
                    1050, 1304, 1558)

error_word_length = (5, 7, 10, 12, 14, 18, 20, 24, 28,
                     36, 42, 48, 56, 68, 84,
                     112, 144, 192, 224, 272, 336,
                     408, 496, 620)

data_region_size = (8, 10, 12, 14, 16, 18, 20, 22, 24,
                    14, 16, 18, 20, 22, 24,
                    14, 16, 18, 20, 22, 24,
                    18, 20, 22)

hv_regions = (1, 1, 1, 1, 1, 1, 1, 1, 1,
              2, 2, 2, 2, 2, 2,
              4, 4, 4, 4, 4, 4,
              6, 6, 6)


class DataTooLongForImplementation(Exception):
    pass


class TextEncoder:
    """Text encoder class for 2D datamatrix"""

    def __init__(self):
        self.codewords = ''
        self.size_index = None
        self.mtx_size = 0
        self.regions = None

    def encode(self, text):
        """Encode the given text and add padding and error codes
        also set up the correct matrix size for the resulting codewords"""

        self.__init__()

        self.encode_text(text)

        self.pad()

        self.append_error_codes()

        log.debug(
            "Codewords: " +
            ' '.join([str(ord(codeword)) for codeword in self.codewords]))

        self.mtx_size = data_region_size[self.size_index]
        self.regions = hv_regions[self.size_index]

        log.debug("Matrix size will be %d", self.mtx_size)

        return self.codewords

    def encode_text(self, text):
        """Encode the given text into codewords"""

        numbuf = ''

        for char in text:
            if char.isdigit():
                numbuf += char
                if len(numbuf) == 2:
                    # we have collected two numbers: add them as a digit pair
                    self.append_digits(numbuf)
                    numbuf = ''
            else:
                if numbuf:
                    # an unpaired number: add it as an ascii character
                    self.append_ascii_char(numbuf)
                    numbuf = ''

                # a regular ascii character
                self.append_ascii_char(char)

        # there might be a single number left over at the end
        if numbuf:
            self.append_ascii_char(numbuf)

    def pad(self):
        """Pad out the encoded text to the correct word length"""

        unpadded_len = len(self.codewords)
        log.debug("Unpadded data size:   %d bytes", unpadded_len)

        # Work out how big the matrix needs to be
        for self.size_index, length in enumerate(data_word_length):
            if length >= unpadded_len:
                break

        if self.size_index > 13:
            raise DataTooLongForImplementation("PyStrich does not currently support encoding beyond 174 characters. "
                                               "See https://github.com/mmulqueen/pyStrich/issues/2")

        # Number of characters with which the data will be padded
        padsize = length - unpadded_len

        log.debug("Pad size: %d bytes", padsize)

        # First pad character is 129
        if padsize:
            self.codewords += chr(129)

        def rand253(pos):
            """generate the next padding character"""
            rnd = ((149 * pos) % 253) + 1
            return (129 + rnd) % 254

        # Remaining pad characters generated by 253-state algorithm
        for i in range(1, padsize):
            self.codewords += chr(rand253(unpadded_len + i + 1))

        log.debug("Word size after padding: %d bytes", len(self.codewords))

    def append_error_codes(self):
        """Calculate the necessary number of Reed Solomon error codes for the
        encoded text and padding codewords, and append to the codeword buffer"""

        error_length = error_word_length[self.size_index]
        log.debug("Error word length: %d bytes", error_length)

        error_words = get_reed_solomon_code(self.codewords, error_length)

        self.codewords += error_words

    def append_digits(self, digits):
        """Write a pair of digits
        (the appended value is 130 + the integer value of the digits together"""

        append = chr(130 + int(digits))
        self.codewords += append
        log.debug('digits:  "%s" ==> %s', digits, ord(append))

    def append_ascii_char(self, char):
        """Append a single ascii character
        (the appended value is the value of the char plus 1"""

        append = chr(ord(char) + 1)
        self.codewords += append
        log.debug('ascii:   "%c" ==> %d', char, ord(append))
