"""Core module for HCI operations.
"""
import logging
import multiprocessing as mp
import os
import select
import signal

from . import bluez
from . import command as btcmd
from .command import HCICommand, HCIReadBDAddr
from .data import HCIACLData, HCISCOData
from .error import HCICommandError, HCIParseError, HCITimeoutError
from .event import HCIEvent
from .utils import letoh8


_pkt_table = {
    bluez.HCI_COMMAND_PKT: HCICommand,
    bluez.HCI_ACLDATA_PKT: HCIACLData,
    bluez.HCI_SCODATA_PKT: HCISCOData,
    bluez.HCI_EVENT_PKT: HCIEvent,
}


def get_hci_pkt_size(buf, offset=0):
    ptype = letoh8(buf, offset)
    offset += 1
    return 1 + _pkt_table[ptype].get_pkt_size(buf, offset)


def parse_hci_pkt(buf, offset=0):
    ptype = letoh8(buf, offset)
    offset += 1
    pkt = _pkt_table[ptype].parse(buf, offset)
    return (ptype, pkt)


class HCISock(object):
    def __init__(self, dev_id):
        super(HCISock, self).__init__()
        self.sock = bluez.hci_new_user_channel(dev_id)
        self.poll = select.poll()
        self.poll.register(self.sock, (select.POLLIN | select.POLLPRI))
        self.rbuf = ''

    def __del__(self):
        self.poll.unregister(self.sock)
        self.sock.close()

    def fileno(self):
        return self.sock.fileno()

    def send_hci_cmd(self, cmd):
        param = cmd.pack_param()
        if param is not None:
            bluez.hci_send_cmd(self.sock, cmd.ogf, cmd.ocf, param)
        else:
            bluez.hci_send_cmd(self.sock, cmd.ogf, cmd.ocf)

    def send_acl_data(self, acl):
        bluez.hci_send_acl(self.sock, acl.conn_handle, acl.pb_flag,
                           acl.bc_flag, acl.data)

    def recv_hci_pkt(self, timeout=None):
        """Receive a HCI packet.

        This method waits for timeout milliseconds to receive a packet. If the
        time is out, it raises HCITimeoutError exception. timeout is ignored
        if timeout is None.
        """
        while True:
            if len(self.rbuf) > 0:
                pkt_size = get_hci_pkt_size(self.rbuf)
                if pkt_size <= len(self.rbuf):
                    break
            if timeout is not None:
                if len(self.poll.poll(timeout)) == 0:
                    raise HCITimeoutError
            buf = self.sock.recv(1024)
            self.rbuf = ''.join((self.rbuf, buf))

        ptype_pkt = parse_hci_pkt(self.rbuf)
        self.rbuf = self.rbuf[pkt_size:]
        return ptype_pkt

    def recv_hci_evt(self, timeout=None):
        ptype, evt = self.recv_hci_pkt(timeout)
        if ptype != bluez.HCI_EVENT_PKT:
            raise HCIParseError('not an event: ptype: {}'.format(ptype))
        return evt


class HCITask(object):
    def __init__(self, hci_sock):
        super(HCITask, self).__init__()
        self.sock = hci_sock
        self.log = logging.getLogger(
            '{}.{}'.format(__name__, self.__class__.__name__))

    def send_hci_cmd(self, cmd):
        self.sock.send_hci_cmd(cmd)

    def send_acl_data(self, data):
        self.sock.send_acl_data(data)

    def recv_hci_pkt(self, timeout=None):
        return self.sock.recv_hci_pkt(timeout)

    def recv_hci_evt(self, timeout=None):
        return self.sock.recv_hci_evt(timeout)

    def wait_hci_evt(self, evt_matcher, timeout=None):
        while True:
            evt = self.recv_hci_evt(timeout)
            if evt_matcher(evt):
                return evt
            self.log.info('ignore event: {}'.format(str(evt)))

    def send_hci_cmd_wait_cmd_complt(self, cmd):
        self.send_hci_cmd(cmd)
        evt = self.wait_hci_evt(
            lambda evt: (
                evt.code == bluez.EVT_CMD_COMPLETE
                and evt.cmd_opcode == cmd.opcode()))
        return evt

    def send_hci_cmd_wait_cmd_status(self, cmd):