from os import listdir
import struct
import io
import zlib

from .buffer import Buffer
from .i18n import splitFileName, getScore, locale

class ResourceError(Exception):
    def __init__(self, code=None):
        self.code = code

class ResVariant(object):
    def __init__(self, resFile, i18nScore, dirEntry):
        self.__file = resFile
        self.__i18nScore = i18nScore
        self.__offset = dirEntry['offset']
        self.__unpackedSize = dirEntry['unpackedSize']
        self.__packedSize = dirEntry['packedSize']
        self.__level = dirEntry['level']
        self.__refCnt = 0
        self.__data = None

    @property
    def i18nScore(self):
        return self.__i18nScore

    def getData(self):
        buf = Buffer()

        buf.setSize(self.__unpackedSize)

        if self.__refCnt == 0:
            self.__data = self.__file.load(self.__offset, self.__packedSize, self.__unpackedSize, self.__level)

        return self.__data

class Resource(object):
    def __init__(self, file, i18nScore, entry, name):
        self.__variants = []
        self.__name = name

        self.addVariant(file, i18nScore, entry)

    def addVariant(self, file, i18nScore, entry):
        if len(self.__variants) == 0:
            self.__variants.append(ResVariant(file, i18nScore, entry))

            return

        try:
            best_variant = [idx for idx, variant in enumerate(self.__variants) \
                                    if variant.i18nScore == i18nScore][0]

            self.__variants[best_variant] = ResVariant(file, i18nScore, entry)
        except KeyError:
            self.__variants.append(ResVariant(file, i18nScore, entry))
            self.__variants.sort(key=lambda variant: variant.getI18nScore())

    def getVariantsCount(self):
        return len(self.__variants)

    @property
    def variants(self):
        return self.__variants

    def getName(self):
        return self.__name

class ResourceFile(object):
    def __init__(self, fileName, buf=None):
        self.__name = fileName

        if buf is not None:
            self.__buffer = buf
            self.__ownBuffer = False
        else:
            self.__buffer = Buffer()
            self.__ownBuffer = True

        try:
            self.__stream = open(self.__name, 'rb')
            sign = self.__stream.read(4)
            if sign != b'CRF\x00':
                raise ResourceError(code='invalid')

            major = self.readInt()
            minor = self.readInt()
            priority = self.readInt()

            if major != 2 or minor < 0:
                raise ResourceError(code='incompatible')
        except ResourceError as e:
            raise Exception("Invalid resource file: {}".format(e.code))
        except IOError as e:
            raise Exception("Error loading resource file '{}': {}".format(self.__name, e.message))

    def load(self, offset, packedSize, unpackedSize, level):
        try:
            if level == 0:
                self.__stream.seek(offset, io.SEEK_SET)
                self.__stream.read()

                return self.__stream.read(unpackedSize)

            self.__stream.seek(offset, io.SEEK_SET)
            data = self.__stream.read(packedSize)

            return self.unpack(data)
        except Exception as e:
            print("Eror loading resource: {}".format(e))

            raise

    # void ResourceFile::unpack(char *in, int inSize, char *out, int outSize)
    def unpack(self, data):
        return zlib.decompress(data)

    def readInt(self):
        return struct.unpack('i', self.__stream.read(4))[0]

    def readString(self):
        string = b''

        while True:
            b = self.__stream.read(1)

            if b == b'\x00' or b == b'':
                break

            string += b

        return string.decode('ascii')

    def getDirectory(self):
        try:
            self.__stream.seek(-8, io.SEEK_END)
        except IOError:
            raise Exception("Error reading {} directory".format(self.__name))

        start = self.readInt()
        count = self.readInt()

        try:
            self.__stream.seek(start, io.SEEK_SET)
        except IOError:
            raise Exception("Error reading {} directory".format(self.__name))

        directory = []

        try:
            for i in range(0, count):
                directory.append({
                    "name": self.readString(),
                    "unpackedSize": self.readInt(),
                    "offset": self.readInt(),
                    "packedSize": self.readInt(),
                    "level": self.readInt(),
                    "group": self.readString(),
                })
        except IOError:
            raise Exception("Error reading {} directory".format(self.__name))

        return directory

class ResourcesCollection:
    def __init__(self, directories):
        self.__files = []
        self.__buffer = None
        self.__resources = {}
        self.__groups = {}
        self.loadResourceFiles(directories)
        self.processFiles()

    def __iter__(self):
        return iter(self.__resources.values())

    def loadResourceFiles(self, directories):
        for directory in directories:
            try:
                for name in listdir(directory):
                    if not name.startswith('.') and name.endswith('.res'):
                        self.__files.append(
                            ResourceFile(directory + '/' + name,
                                         buf=self.__buffer))
            except OSError:
                pass

    def processFiles(self):
        for res_file in self.__files:
            directory = res_file.getDirectory()

            for de in directory:
                name, ext, language, country = splitFileName(de['name'])
                score = getScore(language, country, locale)

                if score > 0:
                    resName = name + '.' + ext
                    res = self.__resources.get(resName, None)

                    if not res:
                        res = Resource(res_file, score, de, resName)
                        self.__resources[resName] = res

                        if len(de['group']) > 0:
                            if de['group'] not in self.__groups:
                                self.__groups[de['group']] = []

                            self.__groups[de['group']].append(res)
                    else:
                        res.addVariant(res_file, score, de)

            directory.clear()

    def forEachInGroup(self, group, func):
        for resource in self.__groups[group]:
            func(resource)

resources = None
