from panda3d.core import DirectionalLight, TextNode, Spotlight, PerspectiveLens, AntialiasAttrib, LVector3, AmbientLight
from direct.interval.IntervalGlobal import *
from global_stuff import base, colors as tile_colors, kaushan
from tile import Tile


class World:
    def __init__(self):
        global _instance
        _instance = self
        self.width = 9
        self.height = 9
        self.grid = [[Tile(x, y, self, 'w') for y in range(1, self.height+1)] for x in range(1, self.width+1)]
        self.urns = []
        self.shrines = []
        self.outer_wall = self.generate_wall()
        self.legend = self.generate_labels()
        self._feng_shui = 0
        self.level = None

        h = (self.width+self.height)*0.5

        self.reset()

        base.camera.set_pos(-self.width/2, -self.height/2, h)
        base.camera.look_at(self.width, self.height, -h/2)

        self.init_lighting(h)

    def init_lighting(self, h):
        sun = DirectionalLight('sun')
        sun2 = DirectionalLight('sun2')
        amb = AmbientLight('ambience')
        amb.color = (0.3, 0.3, 0.3, 0.1)
        sun2.color = (0.2, 0.2, 0.2,1)
        self.sun = base.render.attach_new_node(sun)
        self.sun2 = base.render.attach_new_node(sun2)
        self.ambient_light = base.render.attach_new_node(amb)
        self.sun.set_pos(-self.width / 2, -self.height / 2, h)
        self.sun.look_at(self.width, self.height, -h)
        self.sun2.set_pos(self.width + 1, self.height + 1, h)
        self.sun2.look_at(0, 0, -h)
        base.render.set_light(self.sun)
        base.render.set_light(self.sun2)
        base.render.set_light(self.ambient_light)
        base.render.set_shader_auto()
        sun2.set_shadow_caster(True, 4096, 4096)
        lens = PerspectiveLens()
        lens.set_fov(60)
        lens.set_near(0.01)
        lens.set_far(20)
        sun2.set_lens(lens)
        sun.set_lens(lens)
        base.render.set_antialias(AntialiasAttrib.MAuto)

    def reset(self):
        if not self.level:
            base.messenger.send('level_select')
            self.clear()
            return
        self.load(self.level)

    def clear(self):
        for tile in (t for c in self.grid for t in c):
            tile.wall = False
            tile.entity = None
            tile.color = 'w'
        for shrine in self.shrines:
            shrine.__del__()
        for urn in self.urns:
            urn.__del__()
        self.shrines.clear()
        self.urns.clear()

    def load(self, level_name):
        from entity.shrine import Shrine
        from entity.urn import Urn
        self.clear()
        doors = dict()
        with open('levels/'+level_name, 'r') as file:
            for y in range(1, 10):
                l = file.readline()
                for x, c in enumerate(l, start=1):
                    if c == '.':
                        continue
                    elif c == '!':
                        self[x][y].wall = True
                    elif c == '=':
                        self[x][y].wall = True
                        doors[self[x][y]] = False
                    elif c == '-':
                        self[x][y].wall = True
                        doors[self[x][y]] = True
                    elif c in tile_colors.keys():
                        u = Urn(self[x][y], c, self.ghost_spawn_rate(c))
                        self.urns.append(u)
                        self[x][y].entity = u
                    elif c.lower() in tile_colors.keys():
                        s = Shrine(self[x][y], c.lower())
                        self.shrines.append(s)
                        self[x][y].entity = s
            for t, o in doors.items():
                t.door = True
                t.open_door(o)
            file.readline()  # blank line for clarity
            for y in range(1, 10):
                l = file.readline()
                for x, c in enumerate(l, start=1):
                    if c in ('.', 'w'):
                        continue
                    if c not in tile_colors.keys():
                        continue
                    self[x][y].color = c
        base.messenger.send('level_loaded')

    @property
    def feng_shui(self):
        return self._feng_shui

    @feng_shui.setter
    def feng_shui(self, val:int):
        self._feng_shui = val
        for u in self.urns:
            u.ghost_spawn_rate = self.ghost_spawn_rate(u.color)

    def ghost_spawn_rate(self, color):
        fs = min(self.feng_shui, 0)
        if color == '?':
            return (-fs/50) * 1.5
        else:
            return (-fs/50) + 0.1

    def add_feng_shui(self, amount, position=None):
        self.feng_shui += amount
        self.feng_shui = round(self.feng_shui, 1)
        if position is None:
            position = LVector3(self.width+1, self.height+1, 0)
        tn = TextNode('feng shui change notification')
        tn.font = kaushan
        tn.text_color = tile_colors['r'] if amount < 0 else tile_colors['g']
        tn.text = "+" if amount > 0 else ""
        tn.text += str(amount)
        tn.text += " FS"
        tn.align = TextNode.ACenter
        tnnp = base.render.attach_new_node(tn)
        tnnp.set_hpr(-45, 0, 0)
        tnnp.set_scale(0.5)
        Sequence(
            LerpPosInterval(tnnp, 1.5, position + LVector3(0, 0, 5), startPos=position, blendType='easeIn'),
            Func(tnnp.remove_node)
        ).start()

    @property
    def ghosts(self):
        return (g for u in self.urns for g in u.ghosts)

    def start(self):
        for u in self.urns:
            u.start()

    def __getitem__(self, col):
        return [None] + self.grid.__getitem__(col - 1)

    def __iter__(self):
        return (t for y in self.grid for t in y)

    def generate_wall(self):
        parent = base.render.attach_new_node('outer wall')
        for x in range(1, self.width+1):
            n = base.loader.load_model('model/walls/10')
            n.name = 'n'+str(x)
            n.set_pos(x, self.height+1, -0.05)
            s = base.loader.load_model('model/walls/10')
            s.name = 's'+str(x)
            s.set_pos(x, 0, -0.05)
            s.reparent_to(parent)
            n.reparent_to(parent)
        for y in range(1, self.height+1):
            e = base.loader.load_model('model/walls/5')
            e.name = 'e'+str(y)
            e.set_pos(self.width+1, self.height-y+1, -0.05)
            w = base.loader.load_model('model/walls/5')
            w.name = 'w'+str(y)
            w.set_pos(0, self.height-y+1, -0.05)
            e.reparent_to(parent)
            w.reparent_to(parent)
        sw = base.loader.load_model('model/walls/3')
        sw.name = 'sw'
        sw.set_pos(0, 0, -0.05)
        sw.reparent_to(parent)

        nw = base.loader.load_model('model/walls/6')
        nw.name = 'nw'
        nw.set_pos(0, self.height+1, -0.05)
        nw.reparent_to(parent)

        se = base.loader.load_model('model/walls/9')
        se.name = 'se'
        se.set_pos(self.width+1, 0, -0.05)
        se.reparent_to(parent)

        ne = base.loader.load_model('model/walls/12')
        ne.name = 'ne'
        ne.set_pos(self.width+1, self.height+1, -0.05)
        ne.reparent_to(parent)

        return parent

    def generate_labels(self):
        parent = base.render.attach_new_node('labels')
        font = base.loader.load_font('datalagreya.otf', color=(0, 0, 0, 1))
        for x in range(1, self.width + 1):
            text = TextNode('label'+str(x))
            text.set_text(str(x))
            text.set_font(font)
            text_np = parent.attach_new_node(text)
            text_np.set_pos(x-0.25, self.height+1, 1.1)
            text.set_text_color(0, 0, 0, 1)

        for y in range(1, self.height + 1):
            c = chr(ord('A') + y - 1)
            text = TextNode('label'+c)
            text.set_text(c)
            text.set_font(font)
            text_np = parent.attach_new_node(text)
            text_np.set_pos(self.width + 1, self.height-y+1.25, 1.1)
            text_np.set_h(-90)
            text.set_text_color(0, 0, 0, 1)
        return parent

    def update_wall(self, near_tile):
        walls = []
        conns = []
        disconns = []
        if near_tile.x == 1:
            walls.append(self.outer_wall.find("**/w"+str(near_tile.y)))
            conns.append(7)
            disconns.append(5)
        if near_tile.x == self.width:
            walls.append(self.outer_wall.find("**/e"+str(near_tile.y)))
            conns.append(13)
            disconns.append(5)
        if near_tile.y == 1:
            walls.append(self.outer_wall.find("**/n"+str(near_tile.x)))
            conns.append(14)
            disconns.append(10)
        if near_tile.y == self.height:
            walls.append(self.outer_wall.find("**/s"+str(near_tile.x)))
            conns.append(11)
            disconns.append(10)

        for i, wall in enumerate(walls):
            if wall.is_empty():
                continue
            name = wall.name
            pos = wall.get_pos()
            parent = wall.get_parent()
            wall.remove_node()
            conn = conns[i] if near_tile.wall else disconns[i]

            w = base.loader.load_model('model/walls/'+str(conn))
            w.name = name
            w.set_pos(pos)
            w.reparent_to(parent)


def instance():
    return _instance


_instance = None
