import badge
import ugfx
import utime
import urandom
import math
import wifi
import usocket
from umqtt.simple import MQTTClient
import sys


class PixelSnake:
    UP = 0
    RIGHT = 1
    DOWN = 2
    LEFT = 3
    SELECT = 4

    # TODO: indexes in list/tuple should be replaced with named tuple.
    X = 0
    Y = 1
    COLOR = 2

    name_to_color = {
        "blue": 0x3030ff,
        "green": 0x30ff30,
        "red": 0xff3030,
        "yellow": 0xffbc1c
    }
    # TODO: grow snake using food
    FOOD_COLOR = 0xff0102

    colors = list(name_to_color.keys())
    color_to_name = {color: name for name, color in name_to_color.items()}

    def __init__(self):
        self.name =  badge.nvs_get_str("owner","name", "lazyDude")
        self.color = self.name_to_color[self.colors[0]]
        self.tail_length = 3
        self.speed = 1
        self._position = [0,0] # init random
        self.direction = 0
        self.grid = [[80+240+80, 0],[80+240+80+80, 80]]
        self.tail = [] # stores displaced pixels and restores them afterwards TODO: replace with named tuples
        self.aborted = False
        self.time_started = None
        self.dead = False
        self.readable_pixels = False # was too buggy apparently

        self.action_scheduled = None

        wifi.init()
        badge.init()
        ugfx.init()
        ugfx.input_init()

        ugfx.clear(ugfx.WHITE)
        ugfx.string(10, 10, "Waiting for wifi...", "Roboto_Regular12", 0)
        ugfx.flush()

        # Wait for WiFi connection
        while not wifi.sta_if.isconnected():
            utime.sleep(0.1)
            pass

        self.address = usocket.getaddrinfo('barflood.sha2017.org', 2342)[0][-1]

        ugfx.clear(ugfx.BLACK)
        ugfx.flush()
        ugfx.clear(ugfx.WHITE)
        ugfx.flush()

        ugfx.input_attach(ugfx.BTN_START, self.action_home)
        ugfx.input_attach(ugfx.JOY_UP, self.action_up)
        ugfx.input_attach(ugfx.JOY_DOWN, self.action_down)
        ugfx.input_attach(ugfx.JOY_LEFT, self.action_left)
        ugfx.input_attach(ugfx.JOY_RIGHT, self.action_right)
        ugfx.input_attach(ugfx.BTN_A, self.action_select)

        self.socket = None
    def action_home(self, pressed):
        if pressed:
            self.aborted = True

    def action_up(self, pressed):
        if pressed:
            self.action_scheduled = self.UP

    def action_down(self, pressed):
        if pressed:
            self.action_scheduled = self.DOWN

    def action_left(self, pressed):
        if pressed:
            self.action_scheduled = self.LEFT

    def action_right(self, pressed):
        if pressed:
            self.action_scheduled = self.RIGHT

    def action_select(self, pressed):
        if pressed:
            self.action_scheduled = self.SELECT

    def pick_color(self):
        color_index = 0
        while not self.aborted:
            ugfx.clear(ugfx.WHITE)
            ugfx.string(0, 0, "{} choose a color ".format(self.name), "Roboto_Black22", ugfx.BLACK)
            ugfx.string(0, 30, "color: {}".format(self.colors[color_index]), "Roboto_Black22", ugfx.BLACK)
            ugfx.string(0, 50, "A: select color | start = home", "Roboto_Black22", ugfx.BLACK)
            ugfx.string(0, 90, "up/down = rotate color", "Roboto_Black22", ugfx.BLACK)
            ugfx.flush()
            utime.sleep(0.02)
            if self.action_scheduled == self.UP:
                color_index += 1
                self.action_scheduled = None
                if color_index >= len(self.colors):
                    color_index = 0
            if self.action_scheduled == self.DOWN:
                color_index -= 1
                if color_index < 0:
                    color_index = len(self.colors)-1
                self.action_scheduled = None
            if  self.action_scheduled == self.SELECT:
                return self.colors[color_index]

    def run(self):
        self.color = self.name_to_color.get(self.pick_color(), 0xffffff)
        ugfx.clear(ugfx.WHITE)
        ugfx.string(0, 0, "Physically be in the bar!", " Roboto_Regular12", ugfx.BLACK)
        ugfx.string(0, 30, "Get ready and press A to start Snake Combat!", " Roboto_Regular12", ugfx.BLACK)
        ugfx.string(0, 50, "Or slither away home by pressing start", " Roboto_Regular12", ugfx.BLACK)
        ugfx.string(0, 90, "Have Fun!", " Roboto_Regular12", ugfx.BLACK)
        ugfx.flush()


        while not self.aborted and self.action_scheduled != self.SELECT:
            utime.sleep(0.1)
        if self.aborted:
            return
        self.log_start()
        self.connect()

        ugfx.clear(ugfx.WHITE)
        ugfx.string(0, 0, "creating initial snake", " Roboto_Regular12", ugfx.BLACK)
        ugfx.flush()

        self.create_initial_snake()

        ugfx.clear(ugfx.WHITE)
        ugfx.string(0, 0, "Go!", "Roboto_Black22", ugfx.BLACK)
        ugfx.string(0, 20, "use the arrows to steer your snake", " Roboto_Regular12", ugfx.BLACK)
        ugfx.string(0, 50, "Or slither away home by pressing start", " Roboto_Regular12", ugfx.BLACK)
        ugfx.string(0, 90, "Have Fun!", " Roboto_Regular12", ugfx.BLACK)
        ugfx.flush()

        while not self.aborted:
            if self.action_scheduled in [self.UP, self.DOWN, self.LEFT, self.RIGHT]:
                self.direction = self.action_scheduled
                self.action_scheduled = None
                print("changed direction to: {}\n".format(self.direction))

            print("tick\n")
            self.tick()
            print("clean tail")
            self.clean_up_tail()
            #utime.sleep(1 / self.speed)
            print("writeing/sleep")
            self.keep_writing_snake_in_pause(1/self.speed)

            time_elapsed = utime.time() - self.time_started
            self.speed = 1 + 0.3 * (time_elapsed /60)

        if  not self.dead:
            self.log_score("QUIT")

    def tick(self): # main game progression
        x, y = self.tail[-1][self.X:self.Y + 1]
        if self.direction == self.UP:
            x -= 1
        elif self.direction == self.DOWN:
            x -= 1
        elif self.direction == self.RIGHT:
            y += 1
        elif self.direction == self.LEFT:
            y -= 1

        if x < self.grid[0][self.X] or  y < self.grid[0][self.Y] or x > self.grid[1][self.X] or  y > self.grid[1][self.Y]:
            self.die("WALL")
        pixel = self.get_pixel(x,y)
        if pixel[self.COLOR] in self.colors:
            self.die(self.color_to_name[pixel[self.COLOR]])
            return
        self.writePixel(pixel, self.color)
        self.tail.append(pixel)

    def keep_writing_snake_in_pause(self, time_delta):
        start = utime.time()
        #while utime.time() - start < time_delta:
        for pixel in self.tail:
            self.writePixel(pixel, self.color, connect=False)
        utime.sleep(time_delta - (utime.time() - start))

    def clean_up_tail(self):
        if len(self.tail) > self.tail_length:
            pixel = self.tail.pop(0)
            self.writePixel(pixel)

    def die(self, reason):
        for pixel in self.tail:
            self.writePixel(pixel)
        self.aborted = True
        self.dead = True
        self.log_score(reason)

        ugfx.clear(ugfx.WHITE)
        ugfx.string(0, 0, "you died by running into {}".format(reason), "Roboto_Black22", ugfx.BLACK)
        ugfx.flush()

    def create_initial_snake(self):
        found = False
        # find a free location facing away from the wall.
        direction = self.UP
        tail = None # type: Optional[List[Tuple[int, int, int]]]

        while not found:
            x = self.grid[0][self.X] + 30 #urandom.getrandbits(16) % self.grid[1][self.X]
            y = self.grid[0][self.Y] + 30 #urandom.getrandbits(16) % self.grid[1][self.Y]
            tail = [self.get_pixel(x, y)]
            if  self.color_to_name.get(tail[0][self.COLOR]):
                continue
            direction = self.RIGHT
            x_dir = 1
            if x - self.grid[0][self.X] < 10:
                direction = self.LEFT
                x_dir = -1

            x += x_dir
            tail.append(self.get_pixel(x, y))
            if  self.color_to_name.get(tail[1][self.COLOR]):
                continue

            x += x_dir
            tail.append(self.get_pixel(x, y))
            if self.color_to_name.get(tail[1][self.COLOR]):
                continue

            found = True

        self.direction = direction
        self.tail = tail

        for pixel in tail:
            self.writePixel(pixel, self.color)

    def get_pixel(self, x, y):
        if self.readable_pixels:
            self.connect()
            self.socket.write("PX {} {}\n".format(x, y))
            pixel = (x, y, int(self.socket.readline(), 16))
            return pixel
        else: # no output from file, no info on this
            for pixel in self.tail:
                if pixel[self.X] == x and pixel[self.Y]:
                    return (x,y,self.color)
            return (x,y, 0x000000)

    # if color is None it restores the pixel
    def writePixel(self, pixel, color=None, connect=True):
        if connect:
            self.connect()
        print("writing pixel")
        self.socket.write("PX {} {} {}\n".format(pixel[self.X], pixel[self.Y], hex(pixel[2] if color == None else color)))


    def connect(self):
        if self.socket is not None:
            print("closing socket")
            self.socket.close()
        self.socket = usocket.socket()
        print("opening socket")
        self.socket.connect(self.address)

        #self.socket.send("size\n")
        #utime.sleep(0.1)
        #self.grid = [int(i) for i in self.socket.recv(80).split(" ")]

    def log_score(self, death_reason):
        #c = MQTTClient("pixelSnake", "mqtt.sha2017.org", clean_session=False)
        #c.publish("python {} playing as {} died by {} after {} seconds with tail length {} and speed {}".format(
        #    self.name, self.color, death_reason, utime.time() - self.time_started, self.tail_length, self.speed
        #))
        #c.disconnect()
        self.dead = True

    def log_start(self):
        #c = MQTTClient("pixelSnake", "mqtt.sha2017.org", clean_session=False)
        #c.publish("python {} playing as {} joined the arena".format(
        #    self.name, self.color
        #))
        #c.disconnect()
        self.time_started = utime.time()

try:
    PixelSnake().run()
except BaseException as exc:
    sys.print_exception(exc, file=sys.stdout)
    print("here")