'''
  _____  _                                  __  __       _ _
 |  __ \| |                                |  \/  |     | (_)
 | |  | | |__   __ _ _ __ ___   __ _ _ __  | \  / |_   _| |_  __ _
 | |  | | '_ \ / _` | '_ ` _ \ / _` | '__| | |\/| | | | | | |/ _` |
 | |__| | | | | (_| | | | | | | (_| | |    | |  | | |_| | | | (_| |
 |_____/|_| |_|\__,_|_| |_| |_|\__,_|_|    |_|  |_|\__,_|_|_|\__,_|

 Python Snake by Dhamar Mulia
'''

import curses
from curses import KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP
from random import randint


WIDTH = 120
HEIGHT = 30
MAX_X = WIDTH - 2
MAX_Y = HEIGHT - 2
SNAKE_LENGTH = 5
SNAKE_X = SNAKE_LENGTH + 1
SNAKE_Y = 3
TIMEOUT = 100


class Snake(object):
    REV_DIR_MAP = {
        KEY_UP: KEY_DOWN, KEY_DOWN: KEY_UP,
        KEY_LEFT: KEY_RIGHT, KEY_RIGHT: KEY_LEFT,
    }

    def __init__(self, x, y, window):
        self.body_list = []
        self.hit_score = 0
        self.timeout = TIMEOUT
        # buat body snake
        for i in range(SNAKE_LENGTH, 0, -1):
            self.body_list.append(Body(x - i, y))
        # buat kepala snake
        self.body_list.append(Body(x, y, '@'))
        self.window = window
        self.direction = KEY_RIGHT
        self.last_head_coor = (x, y)
        self.direction_map = {
            KEY_UP: self.move_up,
            KEY_DOWN: self.move_down,
            KEY_LEFT: self.move_left,
            KEY_RIGHT: self.move_right
        }

    @property
    def score(self):
        return 'Score : {}'.format(self.hit_score)

    def add_body(self, body_list):
        self.body_list.extend(body_list)

    def eat_food(self, food):
        food.reset()
        body = Body(self.last_head_coor[0], self.last_head_coor[1])
        self.body_list.insert(-1, body)
        self.hit_score += 1
        if self.hit_score % 3 == 0:
            self.timeout -= 5
            self.window.timeout(self.timeout)

    @property
    def nabrak(self):
        return any([body.coor == self.kepala.coor
                    for body in self.body_list[:-1]])

    def update(self):
        # merubah posisi body snake dengan body yg didepannya,
        # dimulai dari belakang
        # for idx, body in enumerate(self.body_list[:-1]):
        #    body.x = self.body_list[idx+1].x
        #    body.y = self.body_list[idx+1].y
        last_body = self.body_list.pop(0)
        last_body.x = self.body_list[-1].x
        last_body.y = self.body_list[-1].y
        self.body_list.insert(-1, last_body)

        self.last_head_coor = (self.kepala.x, self.kepala.y)
        self.direction_map[self.direction]()

    def change_direction(self, direction):
        if direction != Snake.REV_DIR_MAP[self.direction]:
            self.direction = direction

    def render(self):
        for body in self.body_list:
            self.window.addstr(body.y, body.x, body.char)

    @property
    def kepala(self):
        return self.body_list[-1]

    @property
    def coor(self):
        return self.kepala.x, self.kepala.y

    def move_up(self):
        self.kepala.y -= 1
        if self.kepala.y < 1:
            self.kepala.y = MAX_Y

    def move_down(self):
        self.kepala.y += 1
        if self.kepala.y > MAX_Y:
            self.kepala.y = 1

    def move_left(self):
        self.kepala.x -= 1
        if self.kepala.x < 1:
            self.kepala.x = MAX_X

    def move_right(self):
        self.kepala.x += 1
        if self.kepala.x > MAX_X:
            self.kepala.x = 1


class Body(object):
    def __init__(self, x, y, char='#'):
        self.x = x
        self.y = y
        self.char = char

    @property
    def coor(self):
        return self.x, self.y


class Food(object):
    def __init__(self, window, char='*'):
        self.x = randint(1, MAX_X)
        self.y = randint(1, MAX_Y)
        self.char = char
        self.window = window

    def render(self):
        self.window.addstr(self.y, self.x, self.char)

    def reset(self):
        self.x = randint(1, MAX_X)
        self.y = randint(1, MAX_Y)


if __name__ == '__main__':
    curses.initscr()
    window = curses.newwin(HEIGHT, WIDTH, 0, 0)
    window.timeout(TIMEOUT)
    window.keypad(1)
    curses.noecho()
    curses.curs_set(0)
    window.border(0)

    snake = Snake(SNAKE_X, SNAKE_Y, window)
    food = Food(window, '&')

    while True:
        window.clear()
        window.border(0)
        snake.render()
        food.render()
        window.addstr(0, 5, snake.score)
        event = window.getch()

        if event == 27:
            break

        if event in [KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT]:
            snake.change_direction(event)

        if snake.kepala.x == food.x and snake.kepala.y == food.y:
            snake.eat_food(food)

        if event == 32:
            key = -1
            while key != 32:
                key = window.getch()

        snake.update()
        if snake.nabrak:
            break

    curses.endwin()