class Game:
    SWITCHES = None

    BLOCK_SIZE = 32

    COLUMNS = 8
    ROWS = 8

    FPS = None

    COLORS = [
        # 0 - Black
        (0, 0, 0),
        # 1 - Green
        (0, 255, 0),
        # 2 - Red
        (255, 0, 0),
        # 3 - Purple
        (128, 0, 128),
        # 4 - Blue
        (0, 0, 255),
        # 5 - Orange
        (255, 165, 0),
        # 6 - Cyan
        (0, 255, 255),
        # 7 - Yellow
        (255, 255, 0),
        # 8 - Dark Grey
        (35, 35, 35),
        # 9 - White
        (255, 255, 255)
    ]

    SHAPES = [
        # Bird
        [
            [7],
        ],

        # Pipe
        [
            [1],
        ],
    ]

    WIDTH = None
    HEIGHT = None

    COUNTDOWN = None

    SCORE_INCREMENT = None

    SCORE = 0
    PIPES = 0

    INTERVAL = None
    INTERVAL_INCREMENT = None

    LEVEL = 1
    LEVEL_INCREMENT = None

    PAUSED = False

    GAMEOVER = False

    BACKGROUND_GRID = None

    BIRD_MOVED = True

    pygame = None
    pygame_font = None
    pygame_screen = None
    unicornhat = None
    db = None

    board = None
    bird = None
    pipe = None

    def __init__(self,
                 switches,
                 columns,
                 rows,
                 fps,
                 countdown,
                 interval,
                 score_increment,
                 level_increment,
                 interval_increment,
                 pygame_instance=None):
        self.SWITCHES = switches
        self.COLUMNS = columns
        self.ROWS = rows
        self.FPS = fps
        self.COUNTDOWN = countdown
        self.INTERVAL = interval
        self.SCORE_INCREMENT = score_increment
        self.LEVEL_INCREMENT = level_increment
        self.INTERVAL_INCREMENT = interval_increment

        if pygame_instance is None:
            self.pygame = pygame
        else:
            self.pygame = pygame_instance

        self.gpio = Gpio

        self.unicornhat = UnicornHat
        self.db = TinyDB('data/database.json')

        try:
            self.WIDTH = self.BLOCK_SIZE * self.COLUMNS + 150
            self.HEIGHT = self.BLOCK_SIZE * self.ROWS

            self.BACKGROUND_GRID = [[
                8 if x % 2 == y % 2 else 0 for x in xrange(self.COLUMNS)
            ] for y in xrange(self.ROWS)]

            self.pygame.init()
            self.pygame.key.set_repeat(0, 0)
            self.pygame_font = self.pygame.font.Font(
                self.pygame.font.get_default_font(), 12)
            self.pygame_screen = self.pygame.display.set_mode(
                (self.WIDTH, self.HEIGHT), 0, 24)
            self.pygame.event.set_blocked(self.pygame.MOUSEMOTION)

            self.unicornhat.rotation(180)
            self.unicornhat.brightness(0.4)

            self.board = Board(self.COLUMNS, self.ROWS)
            self.__generate_bird()
            self.__generate_pipe()
        except AttributeError:
            print("[Game][error] An error occurred initialising game")

    def start(self, run_once=False):
        print("[Game][info] Starting game")

        try:
            pygame_wait = True

            while pygame_wait:
                for event in self.pygame.event.get():
                    if event.type == self.pygame.KEYDOWN:
                        if event.key == self.pygame.K_RETURN:
                            pygame_wait = False
                        elif event.key == self.pygame.K_ESCAPE:
                            self.quit()

                self.pygame_screen.fill(self.COLORS[0])
                self.__display_message("Press to start")
                self.pygame.display.update()

            self.unicornhat.clear()
        except AttributeError:
            print("[Game][error] An error occurred starting game")

        self.__countdown()
        self.__loop()
        self.finish()

        if run_once is not True:
            self.start()

    def __countdown(self):
        print("[Game][info] Starting game countdown")

        try:
            seconds = 0

            while True:
                self.pygame_screen.fill(self.COLORS[0])
                remaining = (self.COUNTDOWN - seconds)

                if seconds > self.COUNTDOWN:
                    break

                if seconds == self.COUNTDOWN:
                    self.__display_message("Go!")
                else:
                    self.__display_message("%d!" % remaining)

                seconds += 1
                self.unicornhat.clear()
                self.pygame.display.update()
                self.pygame.time.wait(1000)

            self.pygame_screen.fill(self.COLORS[0])
            self.unicornhat.clear()
        except AttributeError:
            print("[Game][error] An error occurred starting game countdown")

    def __loop(self):
        print("[Game][info] Starting game loop")

        try:
            self.pygame.time.set_timer(pygame.USEREVENT + 1, self.INTERVAL)

            key_actions = {
                'ESCAPE': lambda: self.quit(),
                'UP': lambda: self.__direction_up(),
                'p': lambda: self.toggle_pause(),
            }

            pygame_clock = self.pygame.time.Clock()

            while not self.GAMEOVER:
                self.unicornhat.clear()
                self.pygame_screen.fill(self.COLORS[0])

                if self.PAUSED:
                    self.__display_message("Paused")
                else:
                    self.__draw_line(((self.BLOCK_SIZE * self.COLUMNS) + 1, 0),
                                     ((self.BLOCK_SIZE * self.COLUMNS) + 1,
                                      (self.HEIGHT - 1)), self.COLORS[9])

                    self.__display_message(
                        "Score: %d\n\nLevel: %d\n\nPipes: %d" %
                        (self.SCORE, self.LEVEL, self.PIPES),
                        ((self.BLOCK_SIZE * self.COLUMNS) + self.BLOCK_SIZE,
                         2), self.COLORS[9], self.COLORS[0], False)

                    self.__draw_matrix(self.BACKGROUND_GRID, (0, 0), None,
                                       False)
                    self.__draw_matrix(self.board.coordinates(), (0, 0), None,
                                       False)
                    self.__draw_matrix(self.pipe.coordinates(),
                                       (self.pipe.x(), self.pipe.y()))
                    self.__draw_matrix(
                        self.bird.coordinates(self.COLUMNS, self.ROWS), (0, 0))

                self.pygame.display.update()

                for event in self.pygame.event.get():
                    if event.type == self.pygame.USEREVENT + 1:
                        self.__move()
                    elif event.type == self.pygame.QUIT:
                        self.quit()
                    elif event.type == self.pygame.KEYDOWN:
                        for key in key_actions:
                            if event.key == eval("self.pygame.K_" + key):
                                key_actions[key]()

                pygame_clock.tick(self.FPS)
        except AttributeError as e:
            print("[Game][error] An error occurred during game loop")
            print(e)

    def __generate_bird(self):
        print("[Game][info] Generating bird")

        self.bird = Bird(self.SHAPES[0], 1, int(math.floor(self.ROWS / 3)))

    def __generate_pipe(self):
        print("[Game][info] Generating pipe")

        offset_x, offset_y = (self.COLUMNS - 1, 0)
        gap = randint(0, 4)
        coordinates = []

        for x in range(1):
            for y in range(self.ROWS):
                rows = []

                if (y < gap) or (y > gap + 2):
                    rows.append(self.SHAPES[1][0][0])
                else:
                    rows.append(0)

                coordinates.append(rows)

        self.pipe = Pipe(coordinates, offset_x, offset_y)

    def __display_message(self,
                          message,
                          coordinates=None,
                          color=COLORS[9],
                          background_color=COLORS[0],
                          unicornhat=True):
        print("[Game][info] Displaying message")

        if unicornhat:
            self.__scroll_message(message,
                                  text_colour=color,
                                  scroll_speed=0.05)

        for i, line in enumerate(message.splitlines()):
            message_image = self.pygame_font.render(line, False, color,
                                                    background_color)

            if coordinates is not None:
                position_x, position_y = coordinates
            else:
                message_image_center_x, message_image_center_y = message_image.get_size(
                )
                message_image_center_x //= 2
                message_image_center_y //= 2
                position_x = self.WIDTH // 2 - message_image_center_x
                position_y = self.HEIGHT // 2 - message_image_center_y

            self.pygame_screen.blit(message_image,
                                    (position_x, position_y + i * 22))

    def __scroll_message(self,
                         message,
                         text_colour=COLORS[9],
                         scroll_speed=0.05):
        r, g, b = text_colour
        scroll_rows = [[0] * 8] * 8

        for character in message:
            if character in font_dictionary:
                character_rows = font_dictionary[character]
            else:
                character_rows = font_dictionary['-']
            for i in range(8):
                scroll_rows[i] = scroll_rows[i] + character_rows[i]
                scroll_rows[i] += [0]

        for i in range(8):
            scroll_rows[i] += [0] * 8

        for scroll_position in range(len(scroll_rows[0]) - 8):
            for y in range(8):
                for x in range(8):
                    self.unicornhat.set_pixel(x, y, r, g, b)

            self.unicornhat.show()
            # time.sleep(scroll_speed)

    def __draw_line(self,
                    start_position,
                    end_position,
                    color=COLORS[9],
                    unicornhat=True):
        print("[Game][info] Drawing line")

        if unicornhat:
            start_x, start_y = start_position
            end_x, end_y = start_position

            for i in xrange(start_y, end_y):
                for j in xrange(start_x, end_x):
                    if start_x == end_x or start_y == end_y:
                        self.unicornhat.set_pixel(j, i, color)

        self.pygame.draw.line(self.pygame_screen, color, start_position,
                              end_position)

    def __draw_matrix(self, matrix, offset, color=None, unicornhat=True):
        print("[Game][info] Drawing matrix")

        offset_x, offset_y = offset

        for y, row in enumerate(matrix):
            for x, val in enumerate(row):
                if val:
                    if color is None:
                        shape_color = self.COLORS[val]
                    else:
                        shape_color = color

                    if unicornhat:
                        self.unicornhat.set_pixel((offset_x + x),
                                                  (offset_y + y), shape_color)

                    self.pygame.draw.rect(
                        self.pygame_screen, shape_color,
                        self.pygame.Rect((offset_x + x) * self.BLOCK_SIZE,
                                         (offset_y + y) * self.BLOCK_SIZE,
                                         self.BLOCK_SIZE, self.BLOCK_SIZE), 0)

    def __count_clear_pipes(self, pipes):
        print("[Game][info] Counting cleared pipes")

        if pipes > 0:
            self.PIPES += pipes
            self.SCORE += self.SCORE_INCREMENT * self.LEVEL

        if self.PIPES >= self.LEVEL * self.LEVEL_INCREMENT:
            self.LEVEL += 1
            delay = self.INTERVAL - self.INTERVAL_INCREMENT * (self.LEVEL - 1)
            delay = 100 if delay < 100 else delay
            self.pygame.time.set_timer(self.pygame.USEREVENT + 1, delay)

    def __move(self):
        print("[Game][info] Moving bird %s" % (self.bird.direction()))

        if not self.GAMEOVER and not self.PAUSED:
            new_x, new_y = self.bird.position()
            pipe_x, pipe_y = self.pipe.position()

            if self.bird.direction() == self.bird.DIRECTION_UP:
                new_y = self.bird.y() - 1
                self.bird.set_direction(self.bird.DIRECTION_DOWN)
            elif self.bird.direction() == self.bird.DIRECTION_DOWN:
                new_y = self.bird.y() + 1

            cleared_pipes = 0

            if pipe_x - 1 >= 0:
                self.pipe.set_position((pipe_x - 1, pipe_y))
            else:
                cleared_pipes += 1
                self.__generate_pipe()

            if self.board.check_collision(self.pipe, (new_x, new_y)):
                self.GAMEOVER = True

                return False

            self.bird.set_position((new_x, new_y))
            self.__count_clear_pipes(cleared_pipes)
            self.BIRD_MOVED = True

            return True

    def __direction_up(self):
        print("[Game][info] Event direction up")

        if self.BIRD_MOVED is True:
            self.bird.set_direction(self.bird.DIRECTION_UP)
            self.BIRD_MOVED = False

    def __direction_down(self):
        print("[Game][info] Event direction down")

        if self.BIRD_MOVED is True:
            self.bird.set_direction(self.bird.DIRECTION_DOWN)
            self.BIRD_MOVED = False

    def toggle_pause(self):
        print("[Game][info] Toggling paused state")

        self.PAUSED = not self.PAUSED

    def get_score(self):
        print("[Game][info] Calculating score")

        return self.SCORE

    def print_score(self, high_score=False):
        print("[Game][info] Printing score")

        score = self.get_score()

        try:
            self.unicornhat.clear()
            self.pygame_screen.fill(self.COLORS[0])

            if high_score:
                self.__display_message("Game Over!\n\nHigh score: %d" % score)
                self.pygame.display.update()
            else:
                self.__display_message("Game Over!\n\nYour score: %d!" %
                                       self.get_score())
                self.pygame.display.update()

            self.pygame.time.wait(3000)
        except AttributeError:
            print("[Game][error] An error occurred printing score")

    def finish(self):
        print("[Game][info] Finishing game")

        score = self.get_score()

        self.pygame.display.update()

        if self.db.contains(Query().score >= score):
            self.print_score()
        else:
            self.print_score(True)

        self.db.insert({'score': score})
        self.reset()

    def quit(self):
        print("[Game][info] Quitting game")

        self.pygame_screen.fill(self.COLORS[0])
        self.unicornhat.clear()
        self.__display_message("Exiting...")
        self.pygame.display.update()
        sys.exit()

    def reset(self):
        print("[Game][info] Resetting game")

        self.PAUSED = False
        self.GAMEOVER = False
        self.SCORE = 0
        self.PIPES = 0
        self.LEVEL = 1
        self.BIRD_MOVED = True

        self.board = Board(self.COLUMNS, self.ROWS)
        self.bird = None
        self.pipe = None
        self.__generate_bird()
        self.__generate_pipe()

        self.unicornhat.clear()
        self.pygame.time.set_timer(pygame.USEREVENT + 1, 0)
        self.pygame.display.update()

    def cleanup(self):
        print("[Game][info] Game clean up")

        try:
            self.unicornhat.clear()
            self.pygame_screen.fill(self.COLORS[0])
            self.pygame.display.update()
            self.pygame.quit()
        except AttributeError:
            print("[Game][error] An error occurred cleaning up")

    def __exit__(self):
        print("[Game][info] Game exit")

        self.cleanup()
def generate_floor(screen, settings, map_group, floor_group, pipe_group):
    for i in range(0, 69):
        # level 15 and 16
        f = Floor(screen, settings)
        f.rect.top = 13 * settings.floor_height
        f.rect.left = i * settings.floor_width
        fl = Floor(screen, settings)
        fl.rect.top = 14 * settings.floor_height
        fl.rect.left = i * settings.floor_width
        # print('floor top: ' + str(f.rect.top))
        # print('floor x,y: ' + str(f.rect.x) + ', ' + str(f.rect.y))
        f.add(map_group, floor_group)
        fl.add(map_group, floor_group)
    for i in range(71, 86):
        f = Floor(screen, settings)
        f.rect.top = 13 * settings.floor_height
        f.rect.left = i * settings.floor_width
        fl = Floor(screen, settings)
        fl.rect.top = 14 * settings.floor_height
        fl.rect.left = i * settings.floor_width
        f.add(map_group, floor_group)
        fl.add(map_group, floor_group)
    for i in range(89, 153):
        f = Floor(screen, settings)
        f.rect.top = 13 * settings.floor_height
        f.rect.left = i * settings.floor_width
        fl = Floor(screen, settings)
        fl.rect.top = 14 * settings.floor_height
        fl.rect.left = i * settings.floor_width
        f.add(map_group, floor_group)
        fl.add(map_group, floor_group)
    for i in range(155, 224):
        f = Floor(screen, settings)
        f.rect.top = 13 * settings.floor_height
        f.rect.left = i * settings.floor_width
        fl = Floor(screen, settings)
        fl.rect.top = 14 * settings.floor_height
        fl.rect.left = i * settings.floor_width
        f.add(map_group, floor_group)
        fl.add(map_group, floor_group)

    # Stairs
    stairs_arr = []
    # 135-138,12 block
    for i in range(134, 138):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(12, i)
        stairs_arr.append(b)
    # 136-138,11 block
    for i in range(135, 138):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(11, i)
        stairs_arr.append(b)
    # 137,10 block
    # 138,10 block
    for i in range(136, 138):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(10, i)
        stairs_arr.append(b)
    # 138,9  block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(9, 137)
    stairs_arr.append(b)

    # 141-144,12 block
    for i in range(140, 144):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(12, i)
        stairs_arr.append(b)

    # 141-143,11 block
    for i in range(140, 143):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(11, i)
        stairs_arr.append(b)
    # 141-142,10 block
    for i in range(140, 142):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(10, i)
        stairs_arr.append(b)
    # 141,9  block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(9, 140)
    stairs_arr.append(b)

    # 149,12 block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(12, 148)
    stairs_arr.append(b)

    # 150,12 block
    # 150,11 block
    for i in range(11, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 149)
        stairs_arr.append(b)
    # 151,12 block
    # 151,11 block
    # 151,10 block
    for i in range(10, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 150)
        stairs_arr.append(b)
    # 152,12 block
    # 152,11 block
    # 152,10 block
    # 152,9  block
    for i in range(9, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 151)
        stairs_arr.append(b)
    # 153,12 block
    # 153,11 block
    # 153,10 block
    # 153,9  block
    for i in range(9, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 152)
        stairs_arr.append(b)

    # 156,12 block
    # 156,11 block
    # 156,10 block
    # 156,9  block
    for i in range(9, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 155)
        stairs_arr.append(b)
    # 157,12 block
    # 157,11 block
    # 157,10 block
    for i in range(10, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 156)
        stairs_arr.append(b)
    # 158,12 block
    # 158,11 block
    for i in range(11, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 157)
        stairs_arr.append(b)
    # 159,12 block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(12, 158)
    stairs_arr.append(b)

    # 182,12 block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(12, 181)
    stairs_arr.append(b)
    # 183,12 block
    # 183,11 block
    for i in range(11, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 182)
        stairs_arr.append(b)
    # 184,12 block
    # 184,11 block
    # 184,10 block
    for i in range(10, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 183)
        stairs_arr.append(b)
    # 185,12 block
    # 185,11 block
    # 185,10 block
    # 185,9 block
    for i in range(9, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 184)
        stairs_arr.append(b)
    # 186,12 block
    # 186,11 block
    # 186,10 block
    # 186,9 block
    # 186,8 block
    for i in range(8, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 185)
        stairs_arr.append(b)
    # 187,12 block
    # 187,11 block
    # 187,10 block
    # 187,9 block
    # 187,8 block
    # 187,7 block
    for i in range(7, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 186)
        stairs_arr.append(b)
    # 188,12 block
    # 188,11 block
    # 188,10 block
    # 188,9 block
    # 188,8 block
    # 188,7 block
    # 188,6 block
    for i in range(6, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 187)
        stairs_arr.append(b)
    # 189,12 block
    # 189,11 block
    # 189,10 block
    # 189,9 block
    # 189,8 block
    # 189,7 block
    # 189,6 block
    # 189,5 block
    for i in range(5, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 188)
        stairs_arr.append(b)
    # 190,12 block
    # 190,11 block
    # 190,10 block
    # 190,9 block
    # 190,8 block
    # 190,7 block
    # 190,6 block
    # 190,5 block
    for i in range(5, 13):
        b = Block(screen, settings, is_stairs=True)
        b.set_position(i, 189)
        stairs_arr.append(b)
    #
    # 199,12 block
    b = Block(screen, settings, is_stairs=True)
    b.set_position(12, 198)
    stairs_arr.append(b)

    for s in stairs_arr:
        s.add(map_group, floor_group)

    # Overworld Pipes
    p = Pipe(screen, settings)
    p.set_position(11, 28)
    p.add(map_group, pipe_group)
    print('pipe x,y' + str(p.rect.x) + ', ' + str(p.rect.y))

    p = Pipe(screen, settings, height_factor=3)
    p.set_position(10, 38)
    p.add(map_group, pipe_group)

    p = Pipe(screen, settings, height_factor=4)
    p.set_position(9, 46)
    p.add(map_group, pipe_group)

    # 58,12 pipe
    p = Pipe(screen, settings, height_factor=4)
    p.set_position(9, 57)
    p.add(map_group, pipe_group)

    p = Pipe(screen, settings)
    p.set_position(11, 163)
    p.add(map_group, pipe_group)

    p = Pipe(screen, settings)
    p.set_position(11, 179)
    p.add(map_group, pipe_group)