#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from __future__ import absolute_import, division, print_function

import struct
import logging
import sys
import os
from threading import Thread
from queue import Queue, Empty
from .ratpfs import RatpFSServer
from .messages import *
from .ratp import RatpError

try:
    from time import monotonic
except:
    from .missing import monotonic


def unpack(data):
    p_type, = struct.unpack("!H", data[:2])
    logging.debug("unpack: %r data=%r", p_type, repr(data))
    if p_type == BBType.command:
        logging.debug("received: command")
        return BBPacketCommand(raw=data)
    elif p_type == BBType.command_return:
        logging.debug("received: command_return")
        return BBPacketCommandReturn(raw=data)
    elif p_type == BBType.consolemsg:
        logging.debug("received: consolemsg")
        return BBPacketConsoleMsg(raw=data)
    elif p_type == BBType.ping:
        logging.debug("received: ping")
        return BBPacketPing(raw=data)
    elif p_type == BBType.pong:
        logging.debug("received: pong")
        return BBPacketPong(raw=data)
    elif p_type == BBType.getenv_return:
        logging.debug("received: getenv_return")
        return BBPacketGetenvReturn(raw=data)
    elif p_type == BBType.fs:
        logging.debug("received: fs")
        return BBPacketFS(raw=data)
    elif p_type == BBType.fs_return:
        logging.debug("received: fs_return")
        return BBPacketFSReturn(raw=data)
    elif p_type == BBType.md:
        logging.debug("received: md")
        return BBPacketMd(raw=data)
    elif p_type == BBType.md_return:
        logging.debug("received: md_return")
        return BBPacketMdReturn(raw=data)
    elif p_type == BBType.mw:
        logging.debug("received: mw")
        return BBPacketMw(raw=data)
    elif p_type == BBType.mw_return:
        logging.debug("received: mw_return")
        return BBPacketMwReturn(raw=data)
    elif p_type == BBType.reset:
        logging.debug("received: reset")
        return BBPacketReset(raw=data)
    elif p_type == BBType.i2c_read:
        logging.debug("received: i2c_read")
        return BBPacketI2cRead(raw=data)
    elif p_type == BBType.i2c_read_return:
        logging.debug("received: i2c_read_return")
        return BBPacketI2cReadReturn(raw=data)
    elif p_type == BBType.i2c_write:
        logging.debug("received: i2c_write")
        return BBPacketI2cWrite(raw=data)
    elif p_type == BBType.i2c_write_return:
        logging.debug("received: i2c_write_return")
        return BBPacketI2cWriteReturn(raw=data)
    elif p_type == BBType.gpio_get_value:
        logging.debug("received: gpio_get_value")
        return BBPacketGpioGetValue(raw=data)
    elif p_type == BBType.gpio_get_value_return:
        logging.debug("received: gpio_get_value_return")
        return BBPacketGpioGetValueReturn(raw=data)
    elif p_type == BBType.gpio_set_value:
        logging.debug("received: gpio_set_value")
        return BBPacketGpioSetValue(raw=data)
    elif p_type == BBType.gpio_set_value_return:
        logging.debug("received: gpio_set_value_return")
        return BBPacketGpioSetValueReturn(raw=data)
    elif p_type == BBType.gpio_set_direction:
        logging.debug("received: gpio_set_direction")
        return BBPacketGpioSetDirection(raw=data)
    elif p_type == BBType.gpio_set_direction_return:
        logging.debug("received: gpio_set_direction_return")
        return BBPacketGpioSetDirectionReturn(raw=data)
    else:
        logging.debug("received: UNKNOWN")
        return BBPacket(raw=data)


class Controller(Thread):
    def __init__(self, conn):
        Thread.__init__(self)
        self.daemon = True
        self.conn = conn
        self.fsserver = None
        self.rxq = None
        self.conn.connect(timeout=5.0)
        self._txq = Queue()
        self._stopit = False
        self.fsserver = RatpFSServer()

    def _send(self, bbpkt):
        self.conn.send(bbpkt.pack())

    def _handle(self, bbpkt):
        if isinstance(bbpkt, BBPacketConsoleMsg):
            os.write(sys.stdout.fileno(), bbpkt.text)
        elif isinstance(bbpkt, BBPacketPong):
            print("pong",)
        elif isinstance(bbpkt, BBPacketFS):
            if self.fsserver != None:
                self._send(self.fsserver.handle(bbpkt))

    def _expect(self, bbtype, timeout=1.0):
        if timeout is not None:
            limit = monotonic()+timeout
        while timeout is None or limit > monotonic():
            pkt = self.conn.recv(0.1)
            if not pkt:
                continue
            bbpkt = unpack(pkt)
            if isinstance(bbpkt, bbtype):
                return bbpkt
            else:
                self._handle(bbpkt)

    def export(self, path):
        self.fsserver = RatpFSServer(path)

    def ping(self):
        self._send(BBPacketPing())
        r = self._expect(BBPacketPong)
        logging.info("Ping: %r", r)
        if not r:
            return 1
        else:
            print("pong")
            return 0

    def command(self, cmd):
        self._send(BBPacketCommand(cmd=cmd.encode()))
        r = self._expect(BBPacketCommandReturn, timeout=None)
        logging.info("Command: %r", r)
        return r.exit_code

    def getenv(self, varname):
        self._send(BBPacketGetenv(varname=varname.encode()))
        r = self._expect(BBPacketGetenvReturn)
        return r.text

    def md(self, path, addr, size):
        self._send(BBPacketMd(path=path.encode(), addr=addr, size=size))
        r = self._expect(BBPacketMdReturn)
        logging.info("Md return: %r", r)
        return (r.exit_code,r.data)

    def mw(self, path, addr, data):
        self._send(BBPacketMw(path=path.encode(), addr=addr, data=data))
        r = self._expect(BBPacketMwReturn)
        logging.info("Mw return: %r", r)
        return (r.exit_code,r.written)

    def i2c_read(self, bus, addr, reg, flags, size):
        self._send(BBPacketI2cRead(bus=bus, addr=addr, reg=reg, flags=flags, size=size))
        r = self._expect(BBPacketI2cReadReturn)
        logging.info("i2c read return: %r", r)
        return (r.exit_code,r.data)

    def i2c_write(self, bus, addr, reg, flags, data):
        self._send(BBPacketI2cWrite(bus=bus, addr=addr, reg=reg, flags=flags, data=data))
        r = self._expect(BBPacketI2cWriteReturn)
        logging.info("i2c write return: %r", r)
        return (r.exit_code,r.written)

    def gpio_get_value(self, gpio):
        self._send(BBPacketGpioGetValue(gpio=gpio))
        r = self._expect(BBPacketGpioGetValueReturn)
        logging.info("gpio get value return: %r", r)
        return r.value

    def gpio_set_value(self, gpio, value):
        self._send(BBPacketGpioSetValue(gpio=gpio, value=value))
        r = self._expect(BBPacketGpioSetValueReturn)
        logging.info("gpio set value return: %r", r)
        return 0

    def gpio_set_direction(self, gpio, direction, value):
        self._send(BBPacketGpioSetDirection(gpio=gpio, direction=direction, value=value))
        r = self._expect(BBPacketGpioSetDirectionReturn)
        logging.info("gpio set direction return: %r", r)
        return r.exit_code

    def reset(self, force):
        self._send(BBPacketReset(force=force))

    def close(self):
        self.conn.close()

    def run(self):
        assert self.rxq is not None
        try:
            while not self._stopit:
                # receive
                pkt = self.conn.recv()
                if pkt:
                    bbpkt = unpack(pkt)
                    if isinstance(bbpkt, BBPacketConsoleMsg):
                        self.rxq.put((self, bbpkt.text))
                    else:
                        self._handle(bbpkt)
                # send
                try:
                    pkt = self._txq.get(block=False)
                except Empty:
                    pkt = None
                if pkt:
                    self._send(pkt)
        except RatpError as detail:
            print("Ratp error:", detail, file=sys.stderr);
            self.rxq.put((self, None))
            return

    def start(self, queue):
        assert self.rxq is None
        self.rxq = queue
        Thread.start(self)

    def stop(self):
        self._stopit = True
        self.join()
        self._stopit = False
        self.rxq = None

    def send_async(self, pkt):
        self._txq.put(pkt)

    def send_async_console(self, text):
        assert isinstance(text, bytes)
        self._txq.put(BBPacketConsoleMsg(text=text))

    def send_async_ping(self):
        self._txq.put(BBPacketPing())


def main():
    import serial
    from .ratp import SerialRatpConnection
    url = "rfc2217://192.168.23.176:3002"
    port = serial.serial_for_url(url, 115200)
    conn = SerialRatpConnection(port)
    ctrl = Controller(conn)
    return ctrl

if __name__ == "__main__":
    C = main()
