from logging import getLogger
from typing import Optional, Dict, Any, List

from nperipherals_core.model.custom_exceptions import DeviceNotConnected
from nperipherals_core.model.device import Device, WriterDevice

logger = getLogger(__name__)

try:
    import pywin32_bootstrap
    # noinspection PyUnresolvedReferences
    import win32gui
    # noinspection PyUnresolvedReferences
    import win32print
    # noinspection PyUnresolvedReferences
    import win32ui
    # noinspection PyUnresolvedReferences
    import win32con
except ImportError:
    logger.warning("Error importing python win32 libs", exc_info=True)


class WindowsApiPrinterDevice(WriterDevice, Device):
    FONTSIZE = 11
    HORIZONTAL_RESOLUTION = 8
    VERTICAL_RESOLUTION = 10
    LOG_PIXELS_X = 88
    LOG_PIXELS_Y = 90
    PHYSICAL_WIDTH = 110
    PHYSICAL_HEIGHT = 111
    scale_factor = 20
    printer_dict = None

    # noinspection SpellCheckingInspection
    paper_sizes = {
        "letter": 1,
        "lettersmall": 2,
        "tabloid": 3,
        "ledger": 4,
        "legal": 5,
        "statement": 6,
        "executive": 7,
        "a3": 8,
        "a4": 9,
        "envelope9": 19,
        "envelope10": 20,
        "envelope11": 21,
        "envelope12": 22,
        "envelope14": 23,
        "fanfold": 39,
    }

    orientations = {
        "portrait": 1,
        "landscape": 2,
    }

    duplexes = {
        "normal": 1,
        "none": 1,
        "long": 2,
        "short": 3,
    }

    def __init__(self, device_configurations):
        # type: (Dict[unicode, Any]) -> None
        self.get_available_devices = None  # type: Optional[List]
        self.name = device_configurations["Name"]  # type: unicode
        self.paper_size = device_configurations.get("PaperSize", 0)  # type:  Optional[unicode]
        self.duplex = device_configurations.get("Duplex", None)  # type: Optional[unicode]
        self.orientation = device_configurations.get("Orientation", None)  # type:  Optional[unicode]
        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

        self.dc = None
        self.font = None
        self.printer = None
        self.hdc = None
        self.pen = None

        self.page = 0
        self.printer_dict = {}

    def initialize(self):
        return

    def connect(self):
        return

    def disconnect(self):
        return

    def write(self, data):
        # type: (unicode) -> None
        try:
            data = data.splitlines()
            self.start_document()
            self.set_font("Courier New", self.FONTSIZE)
            for i in range(len(data)):
                self.text((72, 72 + i * self.FONTSIZE + 16), data[i])

            self.end_document()

            logger.debug("Device [{}] sent data [{}]".format(self.name, data))

        except Exception as e:
            logger.exception("Error getting socket data with error [{}]".format(repr(e)))

        raise DeviceNotConnected()

    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

    def start_document(self, desc="POS:WindowsApiManager"):
        self.name = win32print.GetDefaultPrinter()
        self.printer = win32print.OpenPrinter(self.name)

        dev_mode = win32print.GetPrinter(self.printer, 8)["pDevMode"]

        if self.paper_size is not None:
            if type(self.paper_size) is int:
                dev_mode.PaperSize = self.paper_size
            else:
                dev_mode.PaperSize = self.paper_sizes[self.paper_size]
        if self.orientation is not None:
            dev_mode.Orientation = self.orientations[self.orientation]
        if self.duplex is not None:
            dev_mode.Duplex = self.duplexes[self.duplex]

        self.hdc = win32gui.CreateDC("WINSPOOL", self.name, dev_mode)
        self.dc = win32ui.CreateDCFromHandle(self.hdc)

        self.dc.SetMapMode(win32con.MM_TWIPS)  # hundredths of inches

        try:
            self.dc.StartDoc(desc)
        except win32ui.error as ex:
            logger.debug(ex)

        self.dc.SetBkMode(win32con.TRANSPARENT)
        self.pen = win32ui.CreatePen(0, int(self.scale_factor), 0L)
        self.dc.SelectObject(self.pen)
        win32gui.SetBkMode(self.hdc, 1)  # transparent
        self.page = 1

    def end_document(self):
        if self.page == 0:
            return
        self.dc.EndDoc()
        del self.dc

    def getsize(self):
        if self.page == 0:
            self.start_document()
        horizontal = float(self.dc.GetDeviceCaps(self.HORIZONTAL_RESOLUTION))
        vertical = float(self.dc.GetDeviceCaps(self.VERTICAL_RESOLUTION))
        width = horizontal * (72.0 / self.dc.GetDeviceCaps(self.LOG_PIXELS_X))
        height = vertical * (72.0 / self.dc.GetDeviceCaps(self.LOG_PIXELS_Y))
        return width, height

    def text(self, position, text):
        if self.page == 0:
            self.start_document()
        self.dc.TextOut(int(self.scale_factor * position[0]),
                        int(-1 * self.scale_factor * position[1]), text)

    def set_font(self, name, size, bold=None, italic=None):
        if self.page == 0:
            self.start_document()
        wt = 400
        if bold:
            wt = 700
        if italic:
            italic = 1
        else:
            italic = 0
        self.font = self.get_font(name, size, wt, italic)
        self.dc.SelectObject(self.font)

    def get_font(self, name, size, weight=400, italic=0):
        if italic:
            return win32ui.CreateFont({
                "name": name,
                "height": self.scale_factor * size,
                "weight": weight,
                "italic": italic,
            })
        else:
            return win32ui.CreateFont({
                "name": name,
                "height": self.scale_factor * size,
                "weight": int(weight),
            })

    def list_printers(self):
        dft = win32print.GetDefaultPrinter()
        printers = {'keys': None}
        if printers['keys'] is None:
            self.build_dict()
        keys = printers.keys()
        keys.sort()
        rc = [dft]
        for k in keys:
            if k != dft:
                rc.append(k)
        return rc

    def is_device_available(self):
        return self.name in self.list_printers()

    def get_name(self):
        # type: () -> unicode
        return self.name

    def get_printer_jobs_count(self):
        printer = win32print.OpenPrinter(self.name)
        jobs = win32print.EnumJobs(printer, 0, -1, 1)
        win32print.ClosePrinter(printer)

        return len(jobs)

    def build_dict(self):
        lst = win32print.EnumPrinters(
            win32print.PRINTER_ENUM_CONNECTIONS
            + win32print.PRINTER_ENUM_LOCAL)
        self.printer_dict = {}
        for flags, description, name, comment in lst:
            self.printer_dict[name] = {}
            self.printer_dict[name]["flags"] = flags
            self.printer_dict[name]["description"] = description
            self.printer_dict[name]["comment"] = comment
