#!/usr/bin/python

import pygame
import serial
import time
import re
import Queue
import logging
from logging.handlers import WatchedFileHandler
import threading
import sys
import signal
import subprocess
import os
import websocket
from websocket import create_connection

FPS = 30
DRAW_WINDOW_SIZE = (1280, 1024)
pressed_queue = Queue.Queue(maxsize=20)
send_queue = Queue.Queue()
SERIAL_DEV = '/dev/ttyACM0'
stop_read = threading.Event()
begin_draw = threading.Event()
help_pressed = threading.Event()
conn_ok = threading.Event()
ok_get = threading.Event()
stop_read.clear()
begin_draw.clear()
help_pressed.clear()
conn_ok.clear()
ok_get.clear()

threads = []

LOG_FORMAT = '[%(asctime)s] %(levelname)s: %(funcName)s: %(message)s'
log = logging.getLogger('main_log')
log.setLevel(logging.DEBUG)
log_handler = WatchedFileHandler('screen.log')
log_fmt = logging.Formatter(fmt=LOG_FORMAT)
log_handler.setFormatter(log_fmt)
log.addHandler(log_handler)

# workaround for wrong buttons numbering
btn_table = {
    '0': '9',
    '1': '8',
    '2': '7',
    '3': '6',
    '4': '5',
    '5': '4',
    '6': '3',
    '7': '2',
    '8': '1',
    '9': '0'
}

choose_imgs = {
    '0': 'imgs/sim 0.png',  # 0
    '1': 'imgs/sim 1.png',  # 1
    '2': 'imgs/sim 2.png',  # 2
    '3': 'imgs/sim 3.png',  # 3
    '4': 'imgs/sim 4.png',  # 4
    '5': 'imgs/sim 5.png',  # 5
    '6': 'imgs/sim 6.png',  # 6
    '7': 'imgs/sim 7.png',  # 7
    '8': 'imgs/sim 8.png',  # 8
    '9': 'imgs/sim 9.png'  # 9
}

choose_imgs_surf = {}

answer_img = {
    (1, 1, 0): 'imgs/110-mercury2.jpg',
    (1, 2, 0): 'imgs/120-venera2.jpg',
    (1, 2, 3): 'imgs/123-1-hz2.jpg',
    (1, 3, 1): 'imgs/131-earth2.jpg',
    (1, 4, 2): 'imgs/142-mars2.jpg',
    (1, 5, 1): 'imgs/151-jupiter2.jpg',
    (1, 6, 0): 'imgs/160-saturn2.jpg',
    (1, 7, 2): 'imgs/172-uran2.jpg',
    (1, 8, 6): 'imgs/186-neptun2.jpg',
    (1, 9, 1): 'imgs/191-pluton2.jpg',
    (2, 3, 4): 'imgs/234-2-hz2.jpg',
    (2, 4, 2): 'imgs/242-3-hz2.jpg',
    (3, 3, 1): 'imgs/331-4-hz2.jpg',
    (3, 4, 1): 'imgs/341-5-hz2.jpg',
    (3, 4, 4): 'imgs/344-6-hz2.jpg',
    (3, 4, 5): 'imgs/345-7-hz2.jpg',
    (3, 5, 3): 'imgs/353-8-hz2.jpg',
    (4, 5, 6): 'imgs/456-9-hz2.jpg',
    (4, 6, 2): 'imgs/462-10-hz2.jpg',
    (5, 6, 7): 'imgs/567-11-hz2.jpg',
    (5, 7, 5): 'imgs/575-12-hz2.jpg',
    (6, 7, 8): 'imgs/678-13-hz2.jpg',
    (6, 8, 6): 'imgs/686-14-hz2.jpg',
    (7, 7, 5): 'imgs/775-15-hz2.jpg',
    (7, 8, 5): 'imgs/785-16-hz2.jpg',
    (7, 8, 8): 'imgs/788-17-hz2.jpg',
    (7, 8, 9): 'imgs/789-18-hz2.jpg',
    (7, 9, 7): 'imgs/797-19-hz2.jpg',
    (8, 8, 6): 'imgs/886-22-hz2.jpg',
    (8, 9, 0): 'imgs/890-20-hz2.jpg',
    (8, 9, 6): 'imgs/896-23-hz2.jpg',
    (8, 9, 9): 'imgs/899-21-hz2.jpg',
    (9, 1, 9): 'imgs/919-24-hz2.jpg'
}

not_found = False


class AskEntry(object):
    def __init__(self, xval, yval, padding, img_path):
        super(AskEntry, self).__init__()
        self.coord = (xval, yval)
        self.padding = padding
        self.img = pygame.image.load(img_path).convert_alpha()
        self.cent_img = pygame.image.load('imgs/sim D.png').convert_alpha()
        self.x_size, _ = DRAW_WINDOW_SIZE
        self.y_size = 200
        self.surf = pygame.Surface(
            (self.x_size, self.y_size),
            flags=pygame.SRCALPHA,
            depth=32
        )
        self.surf.blit(self.img, (self.padding, 0))
        self.surf.blit(
            self.cent_img,
            (self.x_size / 2 - 70, self.y_size / 2 - 70)
        )
        self.answer = None

    def draw_choosed(self, choose):
        self.answer = choose
        self.img_ok = choose_imgs_surf[choose]
        self.append_answer()

    def reset_answer(self):
        self.answer = None

    def append_answer(self):
        self.surf.blit(
            self.img_ok,
            (self.x_size - self.padding - 200, 0)
        )

    def redraw(self):
        self.surf.fill((0, 0, 0, 0))
        self.surf.blit(self.img, (self.padding, 0))
        self.surf.blit(
            self.cent_img,
            (self.x_size / 2 - 70, self.y_size / 2 - 70)
        )

    def full_reset(self):
        self.reset_answer()
        self.redraw()


class AskLayer(object):
    def __init__(self, size):
        super(AskLayer, self).__init__()
        self.size = size
        self.surf = pygame.Surface(size, flags=pygame.SRCALPHA, depth=32)

    def draw(self, ask_entry):
        self.surf.blit(
            ask_entry.surf,
            ask_entry.coord
        )

    def reset(self):
        self.surf.fill((0, 0, 0, 0))


def answer_img_load(answers_list):
    global not_found
    if tuple(answers_list) in answer_img:
        img_for_show = pygame.image.load(
            answer_img[tuple(answers_list)]
        ).convert_alpha()
    elif (answers_list[0] == 0) or (answers_list[1] == 0):
        img_for_show = pygame.image.load(
            'imgs/00-DeathStar2.jpg'
        ).convert_alpha()
    else:
        img_for_show = pygame.image.load(
            'imgs/allother-asteroids2.jpg'
        ).convert_alpha()
        not_found = True

    return img_for_show


def preload_choose_imgs():
    for key, path in choose_imgs.iteritems():
        choose_imgs_surf[key] = pygame.image.load(path).convert_alpha()


def main():
    global not_found
    signal.signal(signal.SIGINT, ctrl_c_handler)
    pygame.init()
    clock = pygame.time.Clock()
    screen = pygame.display.set_mode(
        DRAW_WINDOW_SIZE,
        pygame.NOFRAME
    )
    pygame.mouse.set_visible(False)
    ask_layer = AskLayer(DRAW_WINDOW_SIZE)
    barrel_test = pygame.image.load('imgs/barrel view.png').convert_alpha()
    lines = pygame.image.load('imgs/Navig sreen.png').convert_alpha()
    fin_img = pygame.image.load('fin.png').convert_alpha()
    not_found_img = pygame.image.load('imgs/not_found2.jpg').convert_alpha()
    preload_choose_imgs()
    quest_list = []
    quest_list.append(AskEntry(0, 127, 300, 'Ps1-sun.png'))
    quest_list.append(AskEntry(0, 412, 230, 'Ps2-place.png'))
    quest_list.append(AskEntry(0, 693, 300, 'Ps3-sputnik.png'))

    ser_t = threading.Thread(
        target=mon_serial,
        args=(SERIAL_DEV,)
    )
    ser_t.daemon = True
    ser_t.start()
    threads.append(ser_t)

    conn_ok.wait(timeout=5)
    if not conn_ok.isSet():
        safe_quit()
    send_queue.put('OK\n')
    ok_get.wait(timeout=1)
    if not ok_get.isSet():
        safe_quit()
    else:
        ok_get.clear()

    first_run = True
    running = True
    playing = True
    ask = False
    ask_update = True
    quest_numb = 0
    draw_answer = False
    send_resume = False
    repeat_cnt = 0
    screen_update = True
    show_answered = False

    begin_draw.wait()
    screen.fill((255, 255, 255))
    screen.blit(barrel_test, (0, 0))
    pygame.display.update()
    screen.blit(lines, (0, 0))

    while running:
        begin_draw.wait()
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                safe_quit()

        if help_pressed.isSet():
            playing = True

        if playing:
            log.debug('Start omxplayer')
            subprocess.call([
                'omxplayer',
                '--no-keys',
                '--layer', '2',
                'video.avi'
            ])
            log.debug('Video played')
            playing = False
            send_resume = True
            help_pressed.clear()
            continue

        if send_resume:
            if first_run:
                ask = True
                first_run = False
            if not ok_get.isSet():
                if repeat_cnt > 10:
                    safe_quit()
                send_queue.put('RESUME')
                time.sleep(0.2)
                repeat_cnt += 1
            else:
                ok_get.clear()
                help_pressed.clear()
                send_resume = False
                log.debug('Resuming after video played')
                repeat_cnt = 0
            continue

        if ask:
            try:
                btn = pressed_queue.get(True, 0.1)
                log.debug('Get from queue' + str(btn))
            except Queue.Empty:
                pass
            else:
                log.debug('Pressed BUTTON ' + btn)
                quest_list[quest_numb].draw_choosed(
                    btn_table[btn.lstrip('B')]
                )
                ask_layer.draw(quest_list[quest_numb])
                quest_numb += 1
                ask_update = True

            if quest_numb == 3:
                ask = False
                ask_update = False
                draw_answer = True
                show_answered = True
                screen.blit(ask_layer.surf, (0, 0))
                pygame.display.update()

            if ask_update:
                ask_update = False
                ask_layer.draw(quest_list[quest_numb])
                screen.blit(ask_layer.surf, (0, 0))
                pygame.display.update()

        if show_answered:
            answers = map(lambda answ: int(answ.answer), quest_list)
            log.debug(answers)
            show_img = answer_img_load(answers)
            # time.sleep(0.5)
            show_answered = False
            screen.blit(show_img, (0, 0))
            pygame.display.update()

        if draw_answer:
            if not pressed_queue.empty():
                btn = pressed_queue.get()
                log.debug('Pressed BUTTON: ' + btn)
                if btn == 'B_YES':
                    if not_found:
                        screen.blit(not_found_img, (0, 0))
                    else:
                        if tuple(answers) in answer_img:
                            fin_img_path = 'coord/' + answer_img[tuple(answers)]
                            fin_img = pygame.image.load(fin_img_path).convert_alpha()
                            screen.blit(fin_img, (0, 0))
                        elif (answers[0] == 0) or (answers[1] == 0):
                            fin_img_path = 'coord/imgs/00-DeathStar2.jpg'
                            fin_img = pygame.image.load(fin_img_path).convert_alpha()
                            screen.blit(fin_img, (0, 0))
                             
                            ws = create_connection("ws://192.168.2.6:8080/ws")
                            ws.send('DEATH')
                            ws.close()
                            
                        else:
                            screen.blit(not_found_img, (0, 0))
                    screen.blit(barrel_test, (0, 0))
                    screen_update = True
                elif btn == 'B_NO' or btn == 'B_RES':
                    ask_layer.reset()
                    for x in quest_list:
                        x.full_reset()
                    quest_numb = 0
                    draw_answer = False
                    ask = True
                    ask_update = True
                    screen.fill((255, 255, 255))
                    screen.blit(lines, (0, 0))
                    screen.blit(barrel_test, (0, 0))
                    # screen_update = True
                not_found = False

        if screen_update:
            pygame.display.update()
            screen_update = False

        clock.tick(FPS)

    safe_quit()


def mon_serial(serial_dev):
    ser = serial_connect(serial_dev, 115200)

    button_patt = re.compile(r'B{1,1}[0-9]{1,2}')
    yes_patt = re.compile(r'B_YES')
    no_patt = re.compile(r'B_NO')
    ask_patt = re.compile(r'B_ASK')
    res_patt = re.compile(r'B_RES')
    begin_patt = re.compile(r'CARD_PRESENT')
    conn_patt = re.compile(r'CONNECTED')
    ok_patt = re.compile(r'OK')

    while not stop_read.is_set():
        if not send_queue.empty():
            try:
                ser_wr = send_queue.get()
                ser.write(ser_wr)
                log.debug('Write to serial:' + ser_wr)
            except serial.SerialException as e:
                log.error('Serial write error: ' + str(e))

        try:
            curr = ser.readline().rstrip('\r\n')
        except serial.SerialException as e:
            log.error('Serial error: ' + str(e))
            time.sleep(1)
            ser = serial_connect(serial_dev, 115200)
            continue

        if len(curr) == 0:
            continue

        button_match = re.match(button_patt, curr)
        yes_match = re.match(yes_patt, curr)
        no_match = re.match(no_patt, curr)
        ask_match = re.match(ask_patt, curr)
        res_match = re.match(res_patt, curr)
        begin_match = re.match(begin_patt, curr)
        conn_match = re.match(conn_patt, curr)
        ok_match = re.match(ok_patt, curr)

        if button_match or yes_match or no_match or res_match:
            pressed_queue.put(curr)
            log.debug('Pressed ' + curr + ' button')
        elif begin_match:
            begin_draw.set()
            log.debug('Begin DRAW')
        elif ask_match:
            help_pressed.set()
            log.debug('B_ASK Pressed')
        elif conn_match:
            conn_ok.set()
            log.debug('Connection established')
        elif ok_match:
            ok_get.set()
            log.debug('Get OK msg')
        else:
            log.warning('Get unknown from serial: ' + curr)

    ser.close()


def serial_connect(dev, speed):
    try:
        ser = serial.Serial(dev, speed, timeout=0.1)
    except serial.SerialException as e:
        log.error('Serial Connect Error: ' + str(e))
        print 'Connection Error'
    else:
        log.info('Connected successfully')
        ser.writeTimeout = 0.1
        return ser

    log.critical('FAIL to connect. Exit.')
    safe_quit()


def ctrl_c_handler(signum, frame):
    log.info('Get Ctrl-C.')
    safe_quit()


def safe_quit():
    print 'Exit.'
    log.info('Exit.')
    logging.shutdown()
    pygame.quit()
    stop_read.set()
    for thr in threads:
        thr.join(2)
    sys.exit(0)


if __name__ == '__main__':
    main()