# -*- coding: utf-8 -*-

from typing import Optional
from xml.etree import cElementTree as eTree

from custom_sysactions import block_action_if_pos_is_blocked
from sysactions import (
    StopAction,
    action,
    get_model,
    get_posot,
    show_messagebox,
    show_keyboard,
    send_message,
    close_asynch_dialog,
)
from msgbus import (
    TK_SYS_ACK,
    FM_PARAM,
    MBException,
    TK_DRV_IOCTL,
)
from systools import sys_log_exception

from peripherals.scale import (
    PosScaleEvents,
    ScaleEvent,
)
from application.interactor import OrderInteractor
from peripherals.scale import WeightComparer
from .drive_thru import validate_can_change_order
from utilfunctions import get_device_service


waiting_scale = False
pos_scale_events = PosScaleEvents()


@action
def doCompleteOption(
    pos_id,                # type: str
    context,               # type: str
    part_code,             # type: str
    qty="1",               # type: str
    line_number="",        # type: str
    sale_type="EAT_IN",    # type: str
    subst="",              # type: str
    seat="",               # type: str
    service_period="",     # type: str
    pricelist="",          # type: str
    comment=None,          # type: Optional[str]
    *args
):
    # type: (...) -> None

    from posactions import (
        is_table_service,
        is_a_product_with_measure_unit,
        is_changing_order,
    )

    global waiting_scale

    model = get_model(posid=pos_id)
    is_order_table_service = is_table_service(model=model)
    if is_order_table_service and is_changing_order(model=model):
        show_messagebox(
            posid=pos_id,
            message="$CANNOT_ADD_ITEMS_IN_AN_OPENED_ORDER",
            icon="error",
        )
        raise StopAction()

    order_interactor = OrderInteractor(pos_id=pos_id)
    if not is_order_table_service or not order_interactor.is_order_in_progress():
        _verify_pos_is_blocked(pos_id=pos_id)

    order_picture = order_interactor.get_order_picture(pos_id=pos_id)
    sale_type = order_picture.get("saleTypeDescr") if not sale_type and order_picture else sale_type
    if order_picture and order_picture.get("state") == "IN_PROGRESS":
        validate_can_change_order(
            pos_id=pos_id,
            order_picture=order_picture,
            line_to_replace=subst,
            context=context,
            line_number=line_number,
        )

    if is_a_product_with_measure_unit(product_code=part_code) and not isinstance(qty, float):
        if waiting_scale:
            return

        try:
            waiting_scale = True
            qty = _get_weight_qty(pos_id=pos_id)
        finally:
            waiting_scale = False

    sale_type = sale_type or "EAT_IN"

    return _do_complete_option(
        pos_id=pos_id,
        model=model,
        subst=subst,
        line_number=line_number,
        context=context,
        part_code=part_code,
        sale_type=sale_type,
        comment=comment,
        price_list=pricelist,
        service_period=service_period,
        qty=qty,
        seat=seat,
        *args
    )


def _do_complete_option(
    pos_id,            # type: str
    model,             # type: eTree.XML
    subst,             # type: str
    line_number,       # type: str
    context,           # type: str
    part_code,         # type: str
    sale_type,         # type: str
    comment,           # type: str
    price_list,        # type: str
    service_period,    # type: str
    qty,               # type: str
    seat,              # type: str
    *args
):
    # type: (...) -> None

    from posactions import (
        doClearOptionItem,
        doOption,
    )

    sale_xml = None
    pos_ot = get_posot(model=model)
    try:
        if subst:
            if qty != "0":
                pos_ot.blkopnotify = True

            doClearOptionItem(
                pos_id=pos_id,
                linenumber=line_number,
                item_id=subst,
                pos_ot=pos_ot,
            )
            pos_ot.blkopnotify = False

        if qty != "0":
            sale_xml = doOption(
                pos_id=pos_id,
                context=context,
                part_code=part_code,
                qty=qty,
                line_number=line_number,
                sale_type=sale_type,
                pricelist=price_list,
                comment=comment,
                pos_ot=pos_ot,
                seat=seat,
                service_period=service_period,
                *args
            )

        if sale_xml is None:
            return

        return sale_xml
    finally:
        pos_ot.blkopnotify = False


@block_action_if_pos_is_blocked
def _verify_pos_is_blocked(
    pos_id,    # type: str
):
    # type: (...) -> None

    return


def _get_weight_qty(
    pos_id,    # type: str
):
    # type: (...) -> str

    weight_qty = _get_scale_weight(pos_id=pos_id)
    if weight_qty is None:
        weight_qty = show_keyboard(
            posid=pos_id,
            message="$TYPE_WEIGHT_QTY",
            title="",
            mask="weight",
            defvalue="0.000",
            numpad=True,
        )
    if weight_qty in (None, "", "."):
        raise StopAction()

    if weight_qty == "0":
        raise StopAction()

    return str(float(weight_qty))


def _get_scale_weight(
    pos_id,    # type: str
):
    # type: (...) -> str

    scale_name = get_device_service(
        pos_id=pos_id,
        device_type="scale",
    )
    weight = _scale_wait_data(
        pos_id=pos_id,
        service_name=scale_name,
    )
    return weight


def _start_measuring_weight(
    service_name,    # type: str
):
    # type: (...) -> bool

    try:
        msg = send_message(
            dest_name=service_name,
            token=TK_DRV_IOCTL,
            format=FM_PARAM,
            data="",
        )
        if msg.token == TK_SYS_ACK:
            return True
        else:
            return False

    except (MBException,):
        sys_log_exception("Error communicating with the scale")
        return False


def _scale_wait_data(
    pos_id,          # type: str
    service_name,    # type: str
    timeout=2000,    # type: Optional[float]
):
    # type: (...) -> Optional[str]

    scale_event = pos_scale_events.create_scale_event(
        pos_id=pos_id,
        service_name=service_name,
    )

    return _get_scaled_data_weight(
        pos_id=pos_id,
        service_name=service_name,
        scale_event=scale_event,
        timeout=timeout,
    )


def _get_scaled_data_weight(
    pos_id,          # type: str
    service_name,    # type: str
    scale_event,     # type: ScaleEvent
    timeout,         # type: float
):
    # type: (...) -> Optional[str]

    from posactions import pos_scale_dialogs

    dialog_id = None
    weight_comparer = WeightComparer()
    try:
        while True:
            if not _start_measuring_weight(service_name=service_name):
                return None

            if dialog_id is None:
                dialog_id = show_messagebox(
                    posid=pos_id,
                    title="$WAIT_SCALE_WEIGHT",
                    buttons="$CANCEL",
                    asynch=True,
                )
                pos_scale_dialogs.append(str(dialog_id))
            elif str(dialog_id) not in pos_scale_dialogs:
                return ""

            scale_event.wait(float(timeout / 1000.0))
            if scale_event.weight is None:
                return None

            if scale_event.weight > 0 and weight_comparer.is_weight_equal_to_previous(weight=scale_event.weight):
                close_asynch_dialog(
                    posid=pos_id,
                    dlgid=dialog_id,
                )
                dialog_id = None

                selected_option = show_messagebox(
                    posid=pos_id,
                    message="$CONFIRM_SCALE_WEIGHT|{0}".format("%.3f" % scale_event.weight),
                    buttons="$OK|$GET_WEIGHT_AGAIN|$CANCEL",
                )

                if selected_option == 0:
                    return scale_event.weight
                elif selected_option == 2:
                    return ""
    finally:
        _update_scale_event(
            pos_id=pos_id,
            dialog_id=dialog_id,
            scale_event=scale_event,
        )


def _update_scale_event(
    pos_id,         # type: str
    dialog_id,      # type: str
    scale_event,    # type: ScaleEvent
):
    # type: (...) -> None

    if pos_id in pos_scale_events.get_wait_pos_id_event_dict():
        pos_scale_events.finish_event(scale_event=scale_event)

    if dialog_id is not None:
        close_asynch_dialog(
            posid=pos_id,
            dlgid=dialog_id,
        )
