#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# ZetpeR xax007@yandex.ru
# Дача кота Леопольда, или Особенности мышиной охоты

import os, sys, io, struct
from PIL import Image
import numpy as np

NAME = "Дача кота Леопольда, или Особенности мышиной охоты" 
FORMATS_ARCHIVE = ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','bas']
TYPES_ARCHIVE = [('Дача кота Леопольда, или Особенности мышиной охоты', ('00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17','*.bas'))]
GAMES = ["Дача кота Леопольда, или Особенности мышиной охоты"]
AUTHOR = "ZetpeR xax007@yandex.ru"

class Game_Res:
    def __init__(self,app):
        self.file = None
        self.data = [] 
        self.app = app
        
        self.sup_formats = ["bmp",
                            "anm",
                            "wav"]
        self.sup_types = {"bmp":1,
                          "anm":2,
                          "wav":3}
        self.images = []   
        self.sound = None
        self.fwav = io.BytesIO() # Соединенные звуки

    def open_data(self,file):
        format = file.split(".")[-1].lower()
        (dirname, filename) = os.path.split(file)
        if filename in ['00','01','02','03','04','05','06','07','08','09','10','11','12','13','14','15','16','17']:
            self.OpenArchive00(file)
        elif format == "bas":
            self.OpenArchiveBAS(file)

    def unpack(self,data_res):
        self.images = []
        self.sound = None
        
        name = data_res[0]
        offset = data_res[1]
        size = data_res[2]
        format = data_res[3]
        self.file.seek(offset)
        if format == "bmp":
            self.Unpack_BMP(io.BytesIO(self.file.read(size)))
        elif format == "anm":
            self.Unpack_ANM(io.BytesIO(self.file.read(size)))
        elif format == "wav":
            self.Unpack_WAV(io.BytesIO(self.file.read(size)))

    def OpenArchive00(self,file):
        self.data = [] 
        f = open(file,"rb")
        data = [] # Список размеров
    
        size = struct.unpack("<I",f.read(4))[0] # Первый размер файла
        data.append(size)
        f.seek(12)
        col_f = struct.unpack("<I",f.read(4))[0] # Сколько прочитать размеров дальше
        for i in range(col_f): 
            size = struct.unpack("<I",f.read(4))[0] # Размеры файлов
            data.append(size)
        f.seek(232) # Начало файлов
        ss = 0
        for i in data:
            ss += 1
            offset = f.tell()
            tip1 = f.read(2) # Проверка на тип bmp

            f.seek(offset+583)
            tip2 = f.read(1) # Проверка на тип anm

            if tip1 == b'\x42\x4D': # bmp
                self.data.append((str(ss)+".bmp",offset,i,"bmp"))

            elif tip2 == b'\x03': # Анимация
                self.data.append((str(ss)+".anm",offset,i,"anm"))

            else: # Непонятно
                self.data.append((str(ss)+".bin",offset,i,"bin"))
                
            f.seek(offset+i) # Переход на начало следующего файла
        self.file = f
        
    def OpenArchiveBAS(self,file):
        f = open(file,"rb")
        data = [] # Список размеров
        col_f = struct.unpack("<I",f.read(4))[0] # Сколько прочитать файлов
        for i in range(col_f):
            size = struct.unpack("<I",f.read(4))[0]
            data.append((size))
        offset = f.tell() # Cделано чтоб правельно начинались звук wav
        f.seek(offset-4)
        ss = 0 # Номер файла
        for i in data:
            ss += 1
            offset = f.tell()
            tip1 = f.read(1) # Проверка на тип anm
            f.seek(offset)
            tip2 = f.read(2) # Проверка на тип bmp, wav

            if tip2 == b'\x42\x4D': # bmp
                self.data.append((str(ss)+".bmp",offset,i,"bmp"))
            
            elif tip2 == b'\x52\x49' or tip2 == b'\x54\xF8': # wav    tip2 это для первого звука в архиве GAME.BAS
                self.data.append((str(ss)+".wav",offset,i,"wav"))

            elif tip1 == b'\x03': # Анимация
                self.data.append((str(ss)+".anm",offset,i,"anm"))
            
            else: # Непонятно
                self.data.append((str(ss)+".bin",offset,i,"bin"))
                
            f.seek(offset+i) # Переход на начало следующего файла
        self.file = f
        return 1

    def Unpack_BMP(self, f):
        image = Image.open(f)
        self.images = [image]

    def Unpack_WAV(self, f):
        tip2 = f.read(4)
        if tip2 == b'\x54\xF8\x32\x00': # Для первого звука в архиве GAME.BAS
            f.seek(0)
            f.write(b'\x52\x49\x46\x46')
            self.sound = f
        else:    
            self.sound = f
            
    def Unpack_NEW_WAV(self):
        size = self.fwav.tell()
        if size > 0: # Если записан звук
            self.fwav.seek(0)
            fd = self.fwav.read()

            wav = b""
            wav += b"RIFF"
            wav += struct.pack("<I", size+44-8)# chunkSize размер файла-8
            wav += b"WAVE" # format WAVE
            wav += b"fmt " # subchunk1Id fmt 0x666d7420    
            subchunk1Size = 16 
            audioFormat = 1 
            numChannels = 1    # Количество каналов
            sampleRate = 22050 # Частота файла
            byteRate = 44100   # Частота выхода звука, для расчёта длины звучания
            blockAlign = 2
            bitsPerSample = 16 # Битность звука
            wav += struct.pack("<IHHIIHH", subchunk1Size, audioFormat, numChannels, sampleRate, byteRate, blockAlign, bitsPerSample)
            wav += b"data"
            wav += struct.pack("<I", size)
            wav += fd # Данные
            f3 = io.BytesIO(wav)
            self.sound = f3
            self.fwav = io.BytesIO() # Чистиим память
            
    def Unpack_ANM(self, f):
        f.seek(0,2)  
        end_f = f.tell()
        f.seek(0)
    
        check_1 = f.read(1) # Проверка
        f.seek(1025)
        check_2 = f.read(2) # Проверка 2
        if check_1 == b'\x03' and check_2 == b'\x05\x01': # Это Просто картинка
            List_sizes = [(0,end_f)]
            f.seek(1) # Переходим к началу палитры
        else:   
            f.seek(582)
            check_3 = f.read(2) # Проверка
            if check_3 == b'\x00\x03': # Это анимация
                #print("Это анимация")
                # Находимся на 584 байте, тут уже палитра
                List_sizes = self.Size_Anm(f,583,end_f) # Ищем размеры файлов анимации
                f.seek(584)

        #print("Нашлось",List_sizes)
        for it in List_sizes:
            end_f = it[1] # Это нужно для остановки

            f.seek(it[0])
            fd = f.read(it[1])
            f2 = io.BytesIO(fd)

            check_1 = f2.read(1) # Проверка это одна анимации
            f2.seek(1025)
            check_2 = f2.read(2) # Проверка 2
            if check_1 == b'\x03' and check_2 == b'\x05\x01':
                f2.seek(1) # Переходим к началу палитры
            else:
                print("Ошибка непонятно это картинка или нет")
                return(0)

            Pal = b''
            for j in range(256): # Палитра 1024 байта
                b = f2.read(1)
                g = f2.read(1)
                r = f2.read(1)
                a = f2.read(1)
                Pal += r+g+b

            unclear = struct.unpack("<H",f2.read(2))[0] # Непонятно блок что дальше будет размер сжатого файла
            size = struct.unpack("<I",f2.read(4))[0] # Размер одной сжатой картинки
            #print("Начало файла")
            #print("Непонятно",unclear,"Размер одной сжатой анимации",size,"\n")

            # Функцию флаг 1 создать картинку, если 0 используем уже созданную картинку, посылать в функцию img пустую "" если надо создать файл
            img = self.decompression(f2,1,Pal,"") # распаковываем данные Создаёт картинку 1
            self.images.append(img.copy()) # Делаем копию картинки
            #print("Распаковали главную картинку")

            # Читает дальше блоки после картинки
            while True:
                poz = f2.tell()
                while True:
                    if f2.tell() == end_f-1: # Остановка последний байт должен быть 00
                        #print("Достигли конца файла Проверка")
                        break
                    poz3 = f2.tell()
                    check = f2.read(1) # Проверка

                    if check == b'\x01':
                        size = struct.unpack("<I",f2.read(4))[0] # Размер блока
                        #print("Блок 01",poz,"Размер без заголовка",size)
                        img = self.decompression(f2,0,Pal,img)
                        self.images.append(img.copy()) # Делаем копию картинки
                        break

                    elif check == b'\x02':
                        #print("Блок 02 pass")
                        pass

                    elif check == b'\x03': # Это скорей звук
                        f2.seek(poz3+1) # Чтоб правельно прочетать размер файла
                        size = struct.unpack("<I",f2.read(4))[0] # Размер блока
                        #print("Блок 03 звук позиция",poz,"Размер без заголовка",size)
                        fd = f2.read(size)  
                        self.fwav.write(fd) # Запись
                        break

                    elif check == b'\x05':
                        #print("Блок 05 pass")
                        pass

                    else:
                        print("    Ошибка непонятные байты",check,f2.tell()-1)
                        return(0)

                if f2.tell() == end_f -1:
                    #print("Достигли конца файла 2")
                    break
            f2.close()
            img.close()

        f.close()
        #print()
        self.Unpack_NEW_WAV()

    def Size_Anm(self, f,offset,end_f): # Ищем размеры файлов анимации
        f.seek(0)
        List_sizes = [] # Список оффсетов и размеров файлов анимации
        while True:
            if f.tell() >= 583:
                break
            posf0 = f.tell()
            size = struct.unpack("<I",f.read(4))[0] # Возможный размер анимации
            #print("Позиция чтения размера",posf0,size)
            if offset+size <= end_f: # Это чтоб невызывало ошибок если значение получится больше файла
                f.seek(offset+size)
                posboz = f.tell() # Возможная позиция начало новой анимации
                #print("Зашли сюда 2 позиция",posboz)
                if posboz == end_f: # Дошли до конца файла
                    #print("    Дошли до конца файла",offset,size)
                    #print("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%")
                    List_sizes.append((offset,size)) # Сохранение данных последней анимации
                    break

                check_1 = f.read(1) # Проверка
                #print("Проверка",check_1,posboz,"Размер",size)
                if check_1 == b'\x03' and size > 0: # Это возможно анимация
                    f.seek(posboz+1025)
                    check_2 = f.read(2) # Проверка 2
                    if check_2 == b'\x05\x01' or check_2 == b'\x05\x05' and f.tell() == 1541707: # Это анимация # Исправление b'\x05\x05' для файла 14 4 5368423.anm
                        #print("    Это анимация Оффсет и размер",offset,size)
                        List_sizes.append((offset,size))
                        offset += size # Новое начало анимации
                        f.seek(posf0+1)

                    else: # Если это 4 байта небыли размером анимации
                        f.seek(posf0+1)
                else: # Если это 4 байта небыли размером анимации
                    f.seek(posf0+1)
            else: # Если это 4 байта небыли размером анимации
                f.seek(posf0+1)
        return(List_sizes)
        
    def Save(self,f2,ss,img,block_w,block_h,w,h):
        # Виртуальный распакованный файл, Номер распакованного блока, Палитра, Большая картинка, Сколько блоков по ширине, И высоте, Где должен находится следующий блок в большой картинки по ширине w, И высоте h.
        
        f2.seek(0) # Создаём блок в виде картинки
        f_image = Image.frombuffer('P', (8,8), f2.read(8*8), 'raw', 'P', 0, 1) 

        # Накладываем блок 8 на 8 картинки на большую картинку
        img.paste(f_image,(w,h)) # Вставляем изображение по координатам Ширина и высота
        w += 8
        if ss%block_w == 0: # Значит мы переходим на на новый блок в высоту Номер блока ss
            h += 8 # Прибавляем к высоте
            w = 0  # Ставим оффсет на чало строчки

        f_image.close()
        f2.close()
        return(w,h)
            
    def decompression(self,f,flag,Pal,img):
        block_w = struct.unpack("B",f.read(1))[0] # Количество блоков в ширину
        block_h = struct.unpack("B",f.read(1))[0] # Количество блоков в высоту
        #print("Количество блоков в ширину",block_w,"Высоту",block_h,"Всего блоков",block_w*block_h)
        #print("Реальная ширина картинки",block_w*8,"Высоту",block_h*8)

        if flag == 1: # Создать картинку
            img = Image.new("P", (block_w*8,block_h*8)) # Создание новой картинки
            img.putpalette(Pal)
        w = 0 # Это оффсет наложения в ширину на одной линии по ширине картинки
        h = 0 # Это оффсет наложения в высоту

        
        control_bytes = struct.unpack("<H",f.read(2))[0]-2 # Сколько прочетать управляющих байтов
        # Биты читаются слева на права
        BIT = "" # Потоковый накопитель бит
        for i in range(control_bytes): # Количество управляющих байт
            BIT += bin(f.read(1)[0])[2:].zfill(8) # Получаем строчку Биты прибавляем к строчке
        #print("Позиция",f.tell())

        ss = 0 # Блок который распаковываем
        while True:
            ss += 1
            if ss > block_w*block_h: # Когда распакуем все блоки
                break
            #print("Находемся",f.tell(),"Блок номер",ss)   
            if BIT[:1] == "0": # Пропускаем блок
                #print("  Зашли в бит 0")
                BIT = BIT[1:]

                w += 8
                if ss%block_w == 0: # Значит мы переходим на на новый блок в высоту Номер блока ss
                    h += 8 # Прибавляем к высоте
                    w = 0  # Ставим оффсет на чало строчки
                    
            elif BIT[:4] == "1001": # Повторить 64 раза 1 байт
                #print("  Зашли в 1001")
                BIT = BIT[4:]
                fd = f.read(1)
                #print("Байт который надо повторить 64 раза",fd)
                f2 = io.BytesIO(fd*64)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            elif BIT[:4] == "1010": # Биты 0, 1
                #print("  Зашли в 1010")
                BIT = BIT[4:]
                buffer_bytes = f.read(2) # Это используемые байты для распаковки
                col_bit = 1 # Сколько бит используется чтоб опредилить байт
                control_bytes = 8 # Сколько прочетать управляющих байт
                f2 = self.Unpacking_bit(buffer_bytes,col_bit, f,control_bytes)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)
    
            elif BIT[:4] == "1011": # Биты 0, 10, 11
                #print("  Зашли в 1011")
                BIT = BIT[4:]
                f2 = io.BytesIO()

                bx1 = f.read(1) # Бит 0
                bx2 = f.read(1) # Бит 10
                bx3 = f.read(1) # Бит 11
                BIT_0 = "" # Потоковый накопитель бит
                while True:
                    if f2.tell() == 64:
                        #print("Достигли конца распаковки")
                        break

                    if len(BIT_0) == 0: # Если в списке не осталось бит
                        BIT_0 += bin(f.read(1)[0])[2:].zfill(8) # Получаем строчку Биты прибавляем к строчке
     
                    if BIT_0[:1] == "0":
                        f2.write(bx1)
                        BIT_0 = BIT_0[1:] # Срез Удаляем использованный биты
                    elif BIT_0[:2] == "10":
                        f2.write(bx2)
                        BIT_0 = BIT_0[2:] # Срез Удаляем использованный биты
                    elif BIT_0[:2] == "11":
                        f2.write(bx3)
                        BIT_0 = BIT_0[2:] # Срез Удаляем использованный биты
                    else:
                        # тут можно написать чтения управляющего байта
                        BIT_0 += bin(f.read(1)[0])[2:].zfill(8) # Получаем строчку Биты прибавляем к строчке

                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            elif BIT[:4] == "1100": # Биты 00, 01, 10, 11
                #print("  Зашли в 1100")
                BIT = BIT[4:]
                buffer_bytes = f.read(4) # Это используемые байты для распаковки
                col_bit = 2 # Сколько бит используется чтоб опредилить байт
                control_bytes = 16 # Сколько прочетать управляющих байт
                f2 = self.Unpacking_bit(buffer_bytes,col_bit, f,control_bytes)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            elif BIT[:4] == "1101": # Биты 000, 001, 010, 011, 100, 101, 110, 111
                #print("  Зашли в 1101")

                BIT = BIT[4:]
                col = f.read(1)[0] # Сколько прочетать байтов
                buffer_bytes = f.read(col) # Это используемые байты для распаковки
                col_bit = 3 # Сколько бит используется чтоб опредилить байт
                control_bytes = 24 # Сколько прочетать управляющих байт
                f2 = self.Unpacking_bit(buffer_bytes,col_bit, f,control_bytes)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            elif BIT[:4] == "1110": # Биты 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, 1111
            # Эти биты обозначают номер в списке байтов
                #print("  Зашли в 1110")
                BIT = BIT[4:]

                # Отправить строчку байт которые будут использоватся (брать срезами),
                # Отправить сколько должно бит быть в сравнении комбинаций col_bit (используется для среза),
                # Отправить файл
                # Сколько прочетать управляющих байт
                # Вернуть распакованный файл f2

                col = f.read(1)[0] # Сколько прочетать байтов
                buffer_bytes = f.read(col) # Это используемые байты для распаковки
                col_bit = 4 # Сколько бит используется чтоб опредилить байт
                control_bytes = 32 # Сколько прочетать управляющих байт
                f2 = self.Unpacking_bit(buffer_bytes,col_bit, f,control_bytes)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            elif BIT[:4] == "1111": # Просто прочетать 64 байта
                #print("  Зашли в 1111")
                BIT = BIT[4:]
                    
                fd = f.read(64) # Просто прочетать байты
                f2 = io.BytesIO(fd)
                w,h = self.Save(f2,ss,img,block_w,block_h,w,h)

            else:
                print("Ошибка непонятная команда",Cb,"Позиция",f.tell())
                return(0)

        #print("Коцец распаковки сжатия",f.tell(),"\n")
        return(img)
        
    def Unpacking_bit(self,buffer_bytes,col_bit, f,control_bytes):
        BIT = "" # Потоковый накопитель бит
        for i in range(control_bytes): # Количество управляющих байт
            BIT += bin(f.read(1)[0])[2:].zfill(8) # Получаем строчку Биты прибавляем к строчке
        
        f2 = io.BytesIO()
        while True:
            if f2.tell() == 64:
                #print("Достигли конца распаковки")
                break

            number = int(BIT[:col_bit], 2) # Делаем срез бит и превращаем строчку бит в число
            #number # Номер байта который надо записать
            fd = buffer_bytes[number:number+1] # Берём срезам нужный байт
            f2.write(fd)
            BIT = BIT[col_bit:] # Срез Удаляем использованный биты
            #print("Читаем байт номер",number)
        return(f2)