#!/bin/python
import os


class MapNotSquareException(Exception):
    """
    Matrix used is not a Square and cannot be interpretted as a piece
    """


class PieceNotSquareException(Exception):
    """
    Matrix used is not a Square and cannot be interpretted as a piece
    """


class SaveParser:
    """
    Parser for the game file
    """
    def __init__(self):
        self.map_shape = [[0]]
        self.pieces = []

    def define_map(self, map_shape):
        size = len(map_shape)
        for row in map_shape:
            if size != len(row):
                raise MapNotSquareException
        self.map_shape = map_shape

    def add_piece(self, piece_shape):
        size = len(piece_shape)
        for row in piece_shape:
            if size != len(row):
                raise PieceNotSquareException
        self.pieces.append(piece_shape)

    def __str__(self):
        return str(self.map_shape)

    def shape_to_bytes(self, shape_matrix):
        shape_list = [b for r in shape_matrix for b in r]
        byte_ammount = len(shape_list) // 8 + 1
        tray = 0
        for i in shape_list:
            tray = (tray << 1) | i
        return tray.to_bytes(byte_ammount, 'big')

    def bytes_to_shape(self, bytes_list, map_size):
        list_octet = []
        for octet in bytes_list:
            octet_data = list(f"{octet:08b}")
            [list_octet.append(d) for d in octet_data]
        list_octet = list_octet[-(map_size**2):]

        matrix = []
        for i in range(map_size):
            matrix.append([])
            for j in range(map_size):
                matrix[i].append(list_octet.pop(0))
        return matrix

    def load(self, filename):
        """
        load the file and prepare to parse informations
        """
        with open(filename, mode='br') as file:
            data = list(file.read())

            data_pos = [0, 0]
            for i in range(len(data)):
                if data[i] == 83 and data[i+1] == 77 and data[i+2] == 83:  # SMS
                    data_pos[0] = i+3
                    break
            for i in range(data_pos[0], len(data)):
                if data[i] == 83 and data[i+1] == 77 and data[i+2] == 69:  # SME
                    data_pos[1] = i
                    break
            map_data = data[data_pos[0]:data_pos[1]]
            self.define_map(self.bytes_to_shape(map_data[1:((map_data[0]**2)//8)+2], map_data[0]))
            map_data = map_data[(map_data[0]**2) // 8 + 2:]
            pieces_ammount = map_data.pop(0)
            for i in range(pieces_ammount):
                print(map_data)
                piece_size = map_data.pop(0)
                print(piece_size)
                piece_data = map_data[:(piece_size**2) // 8 + 1]
                print(piece_data)
                map_data = map_data[(map_data[0]**2) // 8 + 2:]
                print(map_data)
                self.add_piece(self.bytes_to_shape(piece_data, piece_size))

    def save(self, filename):
        """
        save parsed info to the file
        """
        save_data = b''
        save_data += b'SMS'
        save_data += len(self.map_shape).to_bytes(1, 'big')
        save_data += self.shape_to_bytes(self.map_shape)
        save_data += len(self.pieces).to_bytes(1, 'big')
        for piece in self.pieces:
            save_data += len(piece).to_bytes(1, 'big')
            save_data += self.shape_to_bytes(piece)
        save_data += b'SME'
        with open(filename, mode='bw') as file:
            file.write(save_data)


def show_matrix(matrix, highlight: tuple = None):
    """
    :matrix: matrix to draw
    :highlight: tuple of the coordinates to hightligh
    """
    size = len(matrix)
    h_x, h_y = None, None
    if highlight:
        h_x, h_y = highlight
    if size != len(matrix[0]):
        print("ERROR: The matrix is not square")
        return

    line_str = "+" + ''.join(['-+' for _ in range(size)])

    for k, x in enumerate(matrix):
        print(line_str)
        print('|', end="")
        for l, y in enumerate(x):
            if k == h_x and l == h_y:
                print("\033[42m", end="")
            print(str(y) + "\033[00m" + '|', end="")
        print()
    print(line_str)


def cls():
    'clear the screen'
    for _ in range(os.get_terminal_size()[1]):
        print()


def menu(P):
    """draw a simple menu to test the SaveParser class"""

    print("1) define terrain")
    print("2) add a piece")
    print("3) show data")
    print("4) save data")
    print("5) load data")
    print("6) quit")
    item = int(input("Select an option :"))
    cls()
    match item:
        case 1:
            size = int(input("what is the size of the map: "))
            P.map_shape = [[0 for _ in range(size)] for _ in range(size)]
            for i in range(size):
                for j in range(size):
                    cls()
                    show_matrix(P.map_shape, (i,j))
                    P.map_shape[i][j] = int(input("0: empty; 1: filled :"))
        case 2:
            size = int(input("what is the size of the piece: "))
            temp = [[0 for _ in range(size)] for _ in range(size)]
            for i in range(size):
                for j in range(size):
                    cls()
                    show_matrix(temp, (i,j))
                    temp[i][j] = int(input("0: empty; 1: filled :"))
            P.add_piece(temp)
        case 3:
            if P.map_shape:
                print("map:")
                show_matrix(P.map_shape)
                for i, v in enumerate(P.pieces):
                    print()
                    print(f"piece {i+1}")
                    show_matrix(v)
        case 4:
            filename = input('enter the file name (default: default.smap):')
            filename = filename if filename else "default.smap"
            P.save(filename)
        case 5:
            filename = input('enter the file name (default: default.smap):')
            filename = filename if filename else "default.smap"
            P.load(filename)
        case 6:
            return False
    return True


if __name__ == "__main__":
    cls()
    P = SaveParser()
    while menu(P):
        pass
