from __future__ import generators
import os, math, random
import images, gamesrv
from images import ActiveSprite
from boards import CELL, HALFCELL, bget
from mnstrmap import GreenAndBlue
from bubbles import BubblingEyes
from bonuses import Bonus, points

LocalDir = os.path.basename(os.path.dirname(__file__))


localmap = {
    't-brick1':  ('image1-%d.ppm', (0, 0, 16, 16)),
    't-brick2':  ('image1-%d.ppm', (16, 0, 16, 16)),
    }

music = gamesrv.getmusic(os.path.join(LocalDir, 'music.wav'))


class BrickEyes(BubblingEyes):

    Patterns = [[(-2,0), (-1,0), (0,0), (1,0)],
                [(-2,0), (-1,0), (0,0), (0,-1)],
                [(-1,-1), (-1,0), (0,0), (1,0)],
                [(-1,0), (0,0), (0,-1), (1,0)],
                [(-1,-1), (0,-1), (0,0), (1,0)],
                [(-2,0), (-1,0), (-1,-1), (0,-1)],
                [(-1,-1), (-1,0), (0,-1), (0,0)]]

    def __init__(self, tetris, bubber, saved_caps, olddragon):
        BubblingEyes.__init__(self, bubber, saved_caps, olddragon)
        self.tetris = tetris
        self.bricks = []

    def playing_bubble(self, oldsprite):
        import boards
        self.pat = random.choice(self.Patterns)
        self.orientation = 1,0
        xmin = 2              - min([x for x,y in self.pat])
        xmax = boards.width-3 - max([x for x,y in self.pat])
        x = int(random.normalvariate(oldsprite.x, boards.bwidth/4))
        x = (x+HALFCELL) // CELL
        if x<xmin: x=xmin
        if x>xmax: x=xmax
        y = -1
        self.tx = x
        self.ty = y
        self.move((x-1)*CELL, (y-1)*CELL)
        for i in range(5):
            yield None
        self.bricks = [Brick(self.bubber, px, py)
                       for px, py in self.brick_positions()]
        self.gen.append(self.step_control())
        self.gen.append(self.rotate_control())
        self.gen.append(self.fall_control())
        self.gen.append(self.move_eyes())

    def bottom_up(self):
        return 0

    def kill(self):
        for b in self.bricks:
            b.stop(self.tetris)
            b.remove()
        self.bricks = []
        BubblingEyes.kill(self)

    def brick_positions(self):
        ox, oy = self.orientation
        result = []
        cx = self.tx*CELL - HALFCELL
        cy = self.ty*CELL - HALFCELL
        for px, py in self.pat:
            px = px*CELL + HALFCELL
            py = py*CELL + HALFCELL
            result.append((cx+px*ox-py*oy, cy+px*oy+py*ox))
        return result

    def save_position(self):
        return self.tx, self.ty, self.orientation

    def restore_position(self, p):
        self.tx, self.ty, self.orientation = p

    def moved(self, old_position):
        for b in self.bricks:
            b.set(' ')
        try:
            for px, py in self.brick_positions():
                if bget(px//CELL, py//CELL) != ' ':
                    self.restore_position(old_position)
                    return 0
            for b, (px, py) in zip(self.bricks, self.brick_positions()):
                b.follow(px, py)
        finally:
            for b in self.bricks:
                b.set('!')   # note: we need '!' < '#'
        return 1

    def onground(self):
        for b in self.bricks:
            if b.gen:    # brick still moving
                return 0
        for px, py in self.brick_positions():
            if bget(px//CELL, py//CELL+1) >= '#':
                return 1
        return 0

    def step_control(self):
        while 1:
            while not self.bubber.wannago(self.dcap):
                yield None
            pos = self.save_position()
            self.tx += self.bubber.wannago(self.dcap)
            if self.moved(pos):
                for i in range(4):
                    yield None
            yield None

    def fall_control(self):
        delay = 1
        while 1:
            for i in range(delay and 14):
                if self.bubber.key_fire:
                    break
                yield None
            pos = self.save_position()
            self.ty += 1
            delay = self.moved(pos)
            if delay:
                for i in range(3):
                    yield None
            elif self.onground() and self.tetris.ready:
                self.gen = [self.stopping()]
            yield None

    def rotate_control(self):
        while 1:
            while not self.bubber.key_jump:
                yield None
            pos = self.save_position()
            ox, oy = self.orientation
            self.orientation = oy, -ox
            if self.moved(pos):
                for i in range(7):
                    yield None
            yield None

    def stopping(self):
        self.move(self.x, -self.ico.h)
        positions = [(py//CELL, px//CELL) for px, py in self.brick_positions()
                     if py >= 0]
        positions.sort()
        positions = [(px, py) for py, px in positions]
        for b in self.bricks:
            b.stop(self.tetris)
            if b.ty < 0:
                b.remove()
        self.bricks = []
        staticbricks = self.tetris.staticbricks
        pts = 500
        while 1:
            for px, py in positions:
                y = py
                x1 = px
                while (x1-1, y) in staticbricks:
                    x1 -= 1
                if bget(x1-1, y) != '#':
                    continue
                x2 = px
                while (x2, y) in staticbricks:
                    x2 += 1
                if bget(x2, y) != '#':
                    continue
                if x2-x1 < 2:
                    continue
                # full line
                from bubbles import Bubble
                ico = images.sprget(Bubble.exploding_bubbles[0])
                self.tetris.score[self.bubber] = self.tetris.score.get(
                    self.bubber, 0) + 1
                xlist = range(x1, x2)
                for x in xlist:
                    s = ActiveSprite(ico,
                                     x*CELL + random.randrange(CELL) - CELL,
                                     y*CELL + random.randrange(CELL) - CELL)
                    s.gen.append(s.die(Bubble.exploding_bubbles))
                    s = staticbricks[x, y]
                    points(x*CELL + HALFCELL, y*CELL + HALFCELL, s, pts)
                    s.remove()
                if pts == 500:
                    self.play(images.Snd.Fruit)
                elif pts == 4000:
                    self.play(images.Snd.Extralife)
                else:
                    self.play(images.Snd.Extra)
                pts *= 2
                for y in range(py-1, -1, -1):
                    if not [x for x in xlist if (x, y) in staticbricks]:
                        break
                    for t in range(4):
                        yield None
                    if [x for x in xlist if (x, y+1) in staticbricks]:
                        break
                    for x in xlist:
                        if (x, y) in staticbricks:
                            staticbricks[x, y].shiftdown()
                yield None
                break
            else:
                break
        if self.tetris.ready < 2:
            self.gen.append(self.playing_bubble(self))

    def move_eyes(self):
        while 1:
            tx = (self.tx-1) * CELL
            ty = (self.ty-1) * CELL
            for i in range(3):
                if tx < self.x:
                    dx = -1
                elif tx > self.x:
                    dx = +1
                else:
                    dx = 0
                if ty > self.y:
                    dy = +1
                else:
                    dy = 0
                self.step(2*dx, 2*dy)
            key = ('eyes', dx, 0)
            self.seticon(images.sprget(key))
            yield None


class Brick(ActiveSprite):

    def __init__(self, bubber, x, y):
        ico = images.sprget(('t-brick1', bubber.pn))
        ActiveSprite.__init__(self, ico, x, y)
        self.tx = x//CELL
        self.ty = y//CELL
        self.bubber = bubber

    def follow(self, x, y):
        self.tx = x//CELL
        self.ty = y//CELL
        self.gen = [self.following(x, y)]

    def following(self, nx, ny):
        dx = (nx - self.x) / 7.0
        dy = (ny - self.y) / 7.0
        for i in range(6, 0, -1):
            self.move(nx - int(i*dx), ny - int(i*dy))
            yield None
        self.move(nx, ny)

    def set(self, c):
        x, y = self.tx, self.ty
        if 0 <= x < curboard.width and 0 <= y < curboard.height:
            line = curboard.walls[y]
            curboard.walls[y] = line[:x] + c + line[x+1:]

    def stop(self, tetris):
        self.set('X')
        self.seticon(images.sprget(('t-brick2', self.bubber.pn)))
        images.ActiveSprites.remove(self)
        tetris.staticbricks[self.tx, self.ty] = self
        self.staticbricks = tetris.staticbricks

    def remove(self):
        del self.staticbricks[self.tx, self.ty]
        self.set(' ')
        gamesrv.Sprite.kill(self)

    def shiftdown(self):
        del self.staticbricks[self.tx, self.ty]
        self.set(' ')
        self.ty += 1
        self.set('X')
        self.staticbricks[self.tx, self.ty] = self
        self.step(0, CELL)


class Tetris:
    
    def bgen(self, limittime = 90.1): # 1:30
        import boards
        from player import BubPlayer

        for t in boards.exit_board(0, repeatmusic=[music]):
            yield t
        for t in curboard.clean_gen_state():
            yield t

        tc = boards.TimeCounter(limittime)
        self.ready = 0
        self.staticbricks = {}
        self.score = {}
        finished = 0
        for t in self.frame():
            t = boards.normal_frame()
            self.build_eyes()
            yield t
            tc.update(t)
            if tc.time == 0.0:
                self.ready = 2
                finished += not self.still_playing()
                if finished > 16:
                    break

        tc.restore()
        for t in boards.result_ranking(self.score):
            self.remove_eyes()
            yield t
        for s in self.staticbricks.values():
            s.remove()

    def frame(self):
        heights = {1: curboard.height,
                   curboard.width-2: curboard.height}
        ymax = curboard.height-1
        maxheight = curboard.height*3//4
        for x in range(2, curboard.width-2):
            if bget(x, ymax) == ' ':
                curboard.putwall(x, ymax)
            height = 1
            for y in range(ymax-1, -1, -1):
                if bget(x, y) == '#':
                    if height == maxheight:
                        curboard.killwall(x, y)
                    else:
                        height += 1
            heights[x] = height
        xlist = range(2, curboard.width-2)
        random.shuffle(xlist)
        for x in xlist:
            h = heights[x]
            x1 = x2 = x
            while heights[x1-1] == h:
                x1 -= 1
            while heights[x2] == h:
                x2 += 1
            parts = (x2-x1) // 8
            if not parts:
                continue
            left = 0
            if heights[x1-1] > h:
                x1 -= 1
                left += 1
            right = parts+1
            if heights[x2] > h:
                x2 += 1
                right -= 1
            for p in range(left, right):
                x = x1 + ((x2-x1-1)*p+parts//2)//parts
                y = ymax
                for i in range(2):
                    while bget(x, y) == '#':
                        y -= 1
                    if y >= 3:
                        curboard.putwall(x, y)
                        heights[x] += 1
        curboard.reorder_walls()
        
        walls_by_pos = curboard.walls_by_pos
        moves = 1
        s = 8.0
        while moves:
            moves = 0
            for y in range(curboard.height-3, -1, -1):
                for x in range(2, curboard.width-2):
                    if ((y,x) in walls_by_pos and
                        (y+1,x) not in walls_by_pos):
                        y0 = y
                        while (y0-1,x) in walls_by_pos:
                            y0 -= 1
                        w = curboard.killwall(x, y0, 0)
                        curboard.putwall(x, y+1, w)
                        moves = 1
            curboard.reorder_walls()
            for i in range(int(s)+2):
                yield None
            s *= 0.95
        self.ready = 1
        while 1:
            yield None

    def build_eyes(self):
        from player import BubPlayer
        for p in BubPlayer.PlayerList:
            dragons = [d for d in p.dragons if not isinstance(d, BrickEyes)]
            if dragons and len(p.dragons) == len(dragons):
                dragon = random.choice(dragons)
                eyes = BrickEyes(self, p, dragon.dcap, dragon)
                p.dragons.append(eyes)
                #p.emotic(dragon, 4)
            for d in dragons:
                d.kill()

    def still_playing(self):
        from player import BubPlayer
        for p in BubPlayer.PlayerList:
            for d in p.dragons:
                if d.gen:
                    return 1
        return 0

    def remove_eyes(self):
        from player import BubPlayer
        for p in BubPlayer.PlayerList:
            for d in p.dragons:
                d.kill()


def run():
    global curboard
    import boards
    from boards import curboard
    boards.replace_boardgen(Tetris().bgen())

def setup():
    from player import BubPlayer
    for key, (filename, rect) in localmap.items():
        filename = os.path.join(LocalDir, filename)
        if filename.find('%d') >= 0:
            for p in BubPlayer.PlayerList:
                images.sprmap[key, p.pn] = (filename % p.pn, rect)
        else:
            images.sprmap[key] = (filename, rect)
setup()
