from panda3d.core import Texture as PandaTexture
from panda3d.core import SamplerState
from panda3d.core import Filename
from panda3d.core import PNMImage
from pathlib import Path
from ursina.vec2 import Vec2
from ursina import color
from ursina.ursinamath import clamp


class Texture():

    default_filtering = None      # options: None / 'bilinear' / 'mipmap'

    def __init__(self, value, filtering='default'):

        if isinstance(value, str):
            value = Path(value)

        if isinstance(value, Path):
            self.path = Path(value)
            self._texture = loader.loadTexture(Filename.fromOsSpecific(str(value)))
            self._cached_image = None   # for get_pixel() method

        elif isinstance(value, PandaTexture):
            self._texture = value

        else:
            from PIL import Image
            image = value
            self._texture = PandaTexture()
            self._texture.setup2dTexture(image.width, image.height, PandaTexture.TUnsignedByte, PandaTexture.FRgba)
            self._texture.setRamImageAs(image.transpose(Image.FLIP_TOP_BOTTOM).tobytes(), image.mode)
            self._cached_image = image   # for get_pixel() method
            # self._cached_image = image.transpose(Image.FLIP_TOP_BOTTOM)
            self.path = None

        if filtering == 'default':
            filtering = Texture.default_filtering      # None/'bilinear'/'mipmap' default: 'None'
        self.filtering = filtering


    @staticmethod
    def new(size, color=(255,255,255)):
        img = PNMImage(*size)
        if len(color) == 4:
            img.addAlpha()

        img.fill(*color)
        panda_tex = PandaTexture('texture')
        panda_tex.load(img)
        return Texture(panda_tex)


    @property
    def name(self):
        try:
            return self.path.name
        except:
            return f'PIL_texture_{self.size}'

    def __str__(self):
        return self.name


    @property
    def size(self):
        return Vec2(self.width, self.height)

    @property
    def width(self):
        if self._cached_image:
            return self._cached_image.size[0]
        elif self._texture.getOrigFileXSize() > 0:
            return self._texture.getOrigFileXSize()
        return 0

    @property
    def height(self):
        if self._cached_image:
            return self._cached_image.size[1]
        elif self._texture.getOrigFileYSize() > 0:
            return self._texture.getOrigFileYSize()
        return 0

    @property
    def pixels(self):
        from numpy import asarray, flip
        from PIL import Image

        if self._cached_image:
            return asarray(self._cached_image)

        pixels = asarray(Image.open(self.path))
        pixels = flip(pixels, axis=0)
        return pixels


    @property
    def filtering(self):
        return self._filtering

    @filtering.setter
    def filtering(self, value):
        # print('setting filtering:', value)
        if value in (None, False, 'nearest', 'nearest neighbor', 'point'):
            self._texture.setMagfilter(SamplerState.FT_nearest)
            self._texture.setMinfilter(SamplerState.FT_nearest)
            self._filtering = False
        elif value in (True, 'linear', 'bilinear'):
            self._texture.setMagfilter(SamplerState.FT_linear)
            self._texture.setMinfilter(SamplerState.FT_linear)
            self._filtering = True
        elif value == 'mipmap':
            self._texture.setMinfilter(SamplerState.FT_linear_mipmap_linear)
            self._filtering = 'mipmap'

    @property
    def repeat(self):
        return self._repeat

    @repeat.setter
    def repeat(self, value):
        self._repeat = value
        self._texture.setWrapU(value)
        self._texture.setWrapV(value)


    def get_pixel(self, x, y):
        try:
            if not self._cached_image:
                from PIL import Image
                self._cached_image = Image.open(self.path)


            col = self._cached_image.getpixel((x, self.height-y-1))
            if self._cached_image.mode == 'LA':
                col = (col[0], col[0], col[0], col[1])

            if self._cached_image.mode == 'L':
                col = (col[0], col[0], col[0])

            return color.rgba32(*col)
        except Exception as e:
            print(e)
            return None


    def get_pixels(self, start, end):
        start = (clamp(start[0], 0, self.width), clamp(start[1], 0, self.width))
        end = (clamp(end[0], 0, self.width), clamp(end[1], 0, self.width))
        pixels = []

        for y in range(start[1], end[1]):
            for x in range(start[0], end[0]):
                pixels.append(self.get_pixel(x,y))

        return pixels

    def set_pixel(self, x, y, color):
        if not self._cached_image:
            from PIL import Image
            self._cached_image = Image.open(self.path)

        self._cached_image.putpixel((x, self.height-y-1), tuple(int(e*255) for e in color))

    def apply(self):
        from PIL import Image
        if not self._cached_image:
            self._cached_image = Image.open(self.path)

        self._texture.setRamImageAs(self._cached_image.transpose(Image.FLIP_TOP_BOTTOM).tobytes(), self._cached_image.mode)
        # self._texture.setRamImageAs(self._cached_image.tobytes(), self._cached_image.mode)

    def save(self, path):
        if not self._cached_image:
            from PIL import Image
            self._cached_image = Image.open(self.path)

        self._cached_image.save(path)

    def __repr__(self):
        return self.name


    def __del__(self):
        # self._texture.releaseAll()
        del self._cached_image



if __name__ == '__main__':
    from ursina import *
    from ursina import texture_importer
    app = Ursina()
    '''
        The Texture class rarely used manually but usually instantiated
        when assigning a texture to an Entity
        texture = Texture(path / PIL.Image / panda3d.core.Texture)

        A texture file can be a .png, .jpg or .psd.
        If it's a .psd it and no compressed version exists, it will compress it automatically.
    '''
    e = Entity(model='quad', texture='brick')
    e.texture.set_pixel(0, 2, color.blue)
    e.texture.apply()
    #
    # for y in range(e.texture.height):
    #     for x in range(e.texture.width):
    #         if e.texture.get_pixel(x,y) == color.blue:
    #             print('found blue pixel at:', x, y)

    # test
    application.asset_folder = Path(r'C:\sync\high resolution images')
    e = Entity(model='quad')
    # from PIL import Image

    # from ursina.prefabs.memory_counter import MemoryCounter
    # MemoryCounter()
    def input(key):
        if key == 'a':
            # img = Image.open(r'C:\sync\high resolution images\tesla_city.png')
            # print('-------', img)
            # e.texture = Texture(img)
            e.texture = 'tesla_city'

        if key == 'space':
            # if not 'tesla_city' in texture_importer.imported_textures:
            #     return
            # print('del texture')
            # del texture_importer.imported_textures['tesla_city']
            # del e.texture
            # tex = e.texture._texture
            t = e.texture._texture
            # e.texture._texture = None
            e.texture = None
            t.releaseAll()
            # print(t.releaseAll())
            t.clearRamImage()
            # e.texture._texture.clear()
            # e.texture._texture = None
            # e.texture = None
            # destroy(e)
        if key == 'p':
            for key, value in texture_importer.imported_textures.items():
                print(key, value)

    e.texture = 'brick'
    # e.texture.set_pixels([e for e in e.texture.get_pixels()])
    e.texture.apply()
    tex = Texture.new((512,512), (255,0,0))

    app.run()
