import json
import os
import socket
from logging import getLogger

from externals.device import NetworkDevice
from nperipherals_core.model.custom_exceptions import DeviceNotConnected, PrinterDoorOpened, PrinterNotConnected
from nperipherals_core.model.device import WriterDevice, CashDrawer
from nperipherals_core.model.device_status import CashDrawerStatus, DeviceStatus
from typing import Dict, List

logger = getLogger(__name__)


class NetworkEscPosPrinterDevice(WriterDevice, NetworkDevice, CashDrawer):
    REQUEST_STATUS_CODE = b'\x10\x04\x01'
    SOCKET_BUF_SIZE = 1

    def __init__(self, device_configurations):
        # type: (Dict[unicode, unicode]) -> None

        super(NetworkEscPosPrinterDevice, self).__init__(logger, device_configurations)
        self.json_data = self.get_json_data(device_configurations.get("TagsFile", ""))  # type: Dict[str,str]
        self.pre_commands = self.parse_commands(device_configurations.get("PrePrintCmds", []))  # type: str
        self.post_commands = self.parse_commands(device_configurations.get("PostPrintCmds", []))  # type: str
        self.write_retries = int(device_configurations.get("Retries", 1))  # type: int
        self.write_retry_sleep_in_seconds = float(device_configurations.get("RetriesSleepSeconds", 1.0))  # type: float

    @staticmethod
    def get_json_data(tags_path):
        # type: (str) -> Dict[str,str]
        data = {}
        if os.path.exists(tags_path):
            with open(tags_path, 'rb') as f:
                data = json.load(f)
        return data

    def parse_commands(self, commands):
        # type: (List[str]) -> str

        parsed = b""
        for command in commands:
            parsed += self.json_data.get(command, command)
        return bytes(parsed)

    def _get_bin_status(self):
        # type: () -> str

        self.socket.send(self.REQUEST_STATUS_CODE)
        status = self.socket.recv(self.SOCKET_BUF_SIZE)
        return format(ord(status), '08b')

    def get_status(self):
        # type: () -> DeviceStatus

        bin_status = self._get_bin_status()

        if "1" == bin_status[-6]:
            raise PrinterDoorOpened()

        if "1" == bin_status[-4]:
            raise PrinterNotConnected()

        return DeviceStatus.Ok

    def get_drawer_status(self):
        # type: () -> CashDrawerStatus

        try:
            bin_status = self._get_bin_status()

            if "0" == bin_status[-3]:
                return CashDrawerStatus.Opened

            return CashDrawerStatus.Closed

        except (socket.error, AttributeError):
            raise DeviceNotConnected()

    def write(self, data):
        # type: (unicode) -> None

        try:
            if self.pre_commands:
                self.socket.sendall(self.pre_commands)

            self.socket.sendall(data)

            if self.post_commands:
                self.socket.sendall(self.post_commands)

        except (socket.error, AttributeError):
            raise DeviceNotConnected()

    def open_drawer(self):
        # type: () -> None

        try:
            self._send_open_drawer_command()
        except (socket.error, AttributeError) as _:
            raise DeviceNotConnected()

    def _send_open_drawer_command(self):
        cash_drawer_pin_2_pulse = b'\x1bp\x0022'
        cash_drawer_pin_5_pulse = b'\x1bp\x0122'

        self.socket.sendall(cash_drawer_pin_2_pulse)
        self.socket.sendall(cash_drawer_pin_5_pulse)

    def get_write_retries(self):
        # type: () -> int

        return self.write_retries

    def get_write_retry_sleep_in_seconds(self):
        # type: () -> float

        return self.write_retry_sleep_in_seconds
