import json

from order_api.dto.exception import (
    CannotSaveEmptyOrder,
    CannotSaveOrderWithNotFilledMandatoryOption,
)
from pos_api import PosService
from typing import (
    Dict,
    List,
    Optional,
    Union,
)
from xml.etree import cElementTree as eTree

from msgbus import FM_STRING
from messagebus import (
    ApplicationErrorMessage,
    DataType,
    DefaultToken,
    Message,
    MessageBus,
)
from .dto import (
    Table,
    TableMode,
    TableServiceDto,
    OrderLines,
)
from .dto.exception import (
    CurrentTabWithPartialTenders,
    InvalidTableStatus,
    InvalidTabMask,
    InvalidTabName,
    MissingAnyOfParameters,
    MissingNumSeats,
    OperatorNotLoggedIn,
    TabIsNotASplitSeat,
    TabNameAlreadyExists,
    TabNotFound,
    TabToJoinDoesNotExists,
    TabToJoinInInvalidStatus,
    TabToJoinIsAlreadyJoined,
    TabToJoinIsEmpty,
    TabToJoinIsTheSame,
    TabToJoinOpenedInAnotherPOS,
    TabToJoinWithPartialTenders,
    TableAlreadyOpened,
    TableIsNotInTabsMode,
    TableNotFound,
    TabNotInValidTabNames,
    TableNotOpened,
    TableServiceException,
    UnknownError,
)
from .dto.table_xml_parser import parse_table
from .error import CodedError
from .token import TK_CMP_MESSAGE


class TableService(object):

    def __init__(
        self,               # type: TableService
        message_bus,        # type: MessageBus
        pos_id,             # type: int
        pos_service=None,   # type: Optional[PosService]
    ):
        # type: (...) -> None

        self.message_bus = message_bus
        self.pos_id = pos_id
        self.table_mgr = "TABLEMGR"

        self.pos_service = pos_service
        if self.pos_service is None:
            self.pos_service = PosService(
                message_bus=message_bus,
                pos_id=self.pos_id,
            )

    def get_table_picture(
        self,       # type: TableService
        table_id,   # type: int
    ):
        # type: (...) -> eTree.Element

        params = (
            "TABLE_PICTURE",
            self.pos_id,
            table_id,
        )
        request_data = "\x00".join((str(p) for p in params))
        msg = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data=request_data.encode(),
                timeout=600000000,
            ),
        )
        if msg.token == DefaultToken.TK_SYS_ACK:
            return eTree.XML(msg.data)

        raise TableServiceException(description=msg.data.decode())

    def get_table(
        self,       # type: TableService
        table_id,   # type: int
    ):
        # type: (...) -> Table

        xml_table_picture = self.get_table_picture(table_id=table_id)
        table = parse_table(table_xml=xml_table_picture)
        return table

    def get_sectors(
        self,       # type: TableService
    ):
        # type: (...) -> List[str]

        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data=b"GET_SECTORS",
            )
        )

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise TableServiceException(description=reply.data.decode())

        return json.loads(reply.data.decode())

    def start_service(
        self,                       # type: TableService
        table_id,                   # type: int
        mode,                       # type: TableMode
        num_seats=None,             # type: Optional[int]
        custom_properties=None,     # type: Optional[Dict[str, str]]
    ):
        # type: (...) -> TableServiceDto

        request = {
            "posId": self.pos_id,
            "tableId": table_id,
            "mode": mode.value,
        }
        if num_seats is not None:
            request["numSeats"] = num_seats

        if custom_properties is not None:
            request["customProperties"] = custom_properties

        params = (
            "START_SERVICE",
            json.dumps(request),
        )
        request_data = "\0".join((str(p) for p in params))
        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data=request_data.encode(),
                timeout=600000000,
            ),
        )

        if reply.token == DefaultToken.TK_SYS_APPLICATION_ERROR:
            app_error = ApplicationErrorMessage.build_from_message(message=reply)
            ex_dict = json.loads(app_error.error_message)
            if ex_dict["ex"] == "TableNotFound":
                raise TableNotFound(table_id=ex_dict["tableId"])
            elif ex_dict["ex"] == "OperatorNotLoggedIn":
                raise OperatorNotLoggedIn(pos_id=ex_dict["posId"])
            elif ex_dict["ex"] == "MissingNumSeats":
                raise MissingNumSeats()
            elif ex_dict["ex"] == "TableAlreadyOpened":
                raise TableAlreadyOpened(table_id=ex_dict["tableId"])

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise UnknownError(
                token=reply.token,
                data=reply.data,
            )

        return self._build_table_service(reply)

    def start_tab(
        self,                       # type: TableService
        tab_name,                   # type: str
        parent_table_id=None,       # type: Optional[int]
        custom_properties=None,     # type: Optional[Dict[str, str]]
    ):
        request = {
            "posId": self.pos_id,
            "tabName": tab_name,
        }

        if parent_table_id is not None:
            request["parentTableId"] = parent_table_id

        if custom_properties is not None:
            request["customProperties"] = custom_properties

        params = (
            "START_TAB",
            json.dumps(request),
        )
        request_data = "\0".join((str(p) for p in params))
        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data=request_data.encode(),
                timeout=600000000,
            ),
        )

        if reply.token == DefaultToken.TK_SYS_APPLICATION_ERROR:
            app_error = ApplicationErrorMessage.build_from_message(message=reply)
            ex_dict = json.loads(app_error.error_message)
            if ex_dict["ex"] == "InvalidTabMask":
                raise InvalidTabMask(tab_name=ex_dict["tabName"])
            if ex_dict["ex"] == "TableNotFound":
                raise TableNotFound(table_id=ex_dict["tableId"])
            elif ex_dict["ex"] == "OperatorNotLoggedIn":
                raise OperatorNotLoggedIn(pos_id=ex_dict["posId"])
            elif ex_dict["ex"] == "MissingNumSeats":
                raise MissingNumSeats()
            elif ex_dict["ex"] == "TableAlreadyOpened":
                raise TableAlreadyOpened(table_id=ex_dict["tableId"])
            elif ex_dict["ex"] == "TabNameAlreadyExists":
                raise TabNameAlreadyExists(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "TableNotOpened":
                raise TableNotOpened(table_id=ex_dict["tableId"])
            elif ex_dict["ex"] == "TableIsNotInTabsMode":
                raise TableIsNotInTabsMode(table_id=ex_dict["tableId"])

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise UnknownError(
                token=reply.token,
                data=reply.data,
            )

        return self._build_table_service(reply)

    def get_service(
        self,           # type: TableService
        table_id,       # type: Union[int, str]
    ):
        # type: (...) -> TableServiceDto

        request = {
            "tableId": table_id,
        }

        params = (
            "TABLE_SERVICE_PICTURE",
            json.dumps(request),
        )
        request_data = "\0".join((str(p) for p in params))
        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data=request_data.encode(),
                timeout=600000000,
            ),
        )

        return self._build_table_service(reply)

    def get_service_tab(
        self,           # type: TableService
        tab_id=None,    # type: Optional[str]
        tab_name=None,  # type: Optional[str]
        pos_id=None,    # type: Optional[int]
    ):
        # type: (...) -> TableServiceDto

        request = {}

        if tab_id:
            request["tabId"] = tab_id
        if tab_name:
            request["tabName"] = tab_name
        if pos_id:
            request["posId"] = pos_id

        if not request:
            raise MissingAnyOfParameters(params=["tab_id", "tab_name", "pos_id"])

        params = ["TAB_SERVICE_PICTURE", json.dumps(request)]
        message = Message(
            token=TK_CMP_MESSAGE,
            data_type=DataType.param,
            data="\0".join(params).encode(),
            timeout=600000000,
        )

        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=message,
        )

        if reply.token == DefaultToken.TK_SYS_APPLICATION_ERROR:
            app_error = ApplicationErrorMessage.build_from_message(message=reply)
            ex_dict = json.loads(app_error.error_message)
            if ex_dict["ex"] == "InvalidTabMask":
                raise InvalidTabMask(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "TabNotInValidTabNames":
                raise TabNotInValidTabNames(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "InvalidTabName":
                raise InvalidTabName(tab_name=ex_dict["tabName"])
            if ex_dict["ex"] == "TabNotFound":
                raise TabNotFound(
                    tab_id=ex_dict["tabId"],
                    tab_name=ex_dict["tabName"],
                    pos_id=ex_dict["posId"],
                )

            raise UnknownError(
                token=reply.token,
                data=reply.data,
            )
        if reply.token == DefaultToken.TK_SYS_BAD_REQUEST:
            ret = json.loads(reply.data)
            raise CodedError(
                code=ret["errorCode"],
                message=ret["errorMessage"],

            )

        return self._build_table_service(reply=reply)

    def create_tab_from_split_service(
        self,               # type: TableService
        table_id,           # type: str
        table_name,         # type: str
        properties=None,    # type: Optional[Dict[str, str]]
    ):
        # type: (...) -> TableServiceDto

        if not table_id or not table_name:
            raise MissingAnyOfParameters(params=["table_id", "table_name"])

        if not table_id.startswith("TAB-"):
            raise TabNotFound(tab_id=table_id)

        request = {
            "posId": self.pos_id,
            "tableId": table_id,
            "tableName": table_name,
            "properties": properties,
        }
        params = ("CREATE_TAB_FROM_SPLIT_SERVICE", json.dumps(request))
        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data="\0".join((str(p) for p in params)).encode(),
                timeout=600000000,
            ),
        )

        if reply.token == DefaultToken.TK_SYS_APPLICATION_ERROR:
            app_error = ApplicationErrorMessage.build_from_message(message=reply)
            ex_dict = json.loads(app_error.error_message)
            if ex_dict["ex"] == "InvalidTabMask":
                raise InvalidTabMask(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "TabNotFound":
                raise TabNotFound(tab_id=ex_dict["tabId"])
            elif ex_dict["ex"] == "TabNameAlreadyExists":
                raise TabNameAlreadyExists(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "InvalidTabName":
                raise InvalidTabName(tab_name=ex_dict["tabName"])
            elif ex_dict["ex"] == "TableNotOpened":
                raise TableNotOpened(table_id=ex_dict["tableId"])
            elif ex_dict["ex"] == "TabIsNotASplitSeat":
                raise TabIsNotASplitSeat(tab_name=ex_dict["tabName"])

        if reply.token != DefaultToken.TK_SYS_ACK:
            raise UnknownError(
                token=reply.token,
                data=reply.data,
            )

        return self._build_table_service(reply=reply)

    def join_tabs(
        self,               # type: TableService
        current_tab_id,     # type: str
        tab_name_to_join,   # type: str
    ):
        # type: (...) -> TableServiceDto

        if not current_tab_id or not tab_name_to_join:
            raise MissingAnyOfParameters(params=["current_tab_id", "tab_name_to_join"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["JOIN_TABS", str(self.pos_id), user_id, current_tab_id, tab_name_to_join]
        message = Message(
            token=TK_CMP_MESSAGE,
            data_type=DataType.param,
            data="\0".join(params).encode(),
            timeout=600000000,
        )

        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=message,
        )

        if reply.token == DefaultToken.TK_SYS_ACK:
            return self._build_table_service(reply=reply)

        self._tabs_error(ex_message=reply.data.decode())

    def unlink_tabs(
        self,               # type: TableService
        current_tab_id,     # type: str
        tab_ids_to_unlink,  # type: List[str]
    ):
        # type: (...) -> TableServiceDto

        if not current_tab_id or not tab_ids_to_unlink:
            raise MissingAnyOfParameters(params=["current_tab_id", "tab_ids_to_unlink"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["UNLINK_TABS", str(self.pos_id), user_id, current_tab_id, ",".join(tab_ids_to_unlink)]
        reply = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data="\0".join(params).encode(),
                timeout=600000000,
            ),
        )

        if reply.token == DefaultToken.TK_SYS_ACK:
            return self._build_table_service(reply=reply)

        self._tabs_error(ex_message=reply.data.decode())

    def register_service_tender(
        self,                   # type: TableService
        table_id,               # type: str
        tender_id,              # type: int
        amount=None,            # type: Optional[float]
        tender_detail=None,     # type: Optional[Dict[str, Union[str, float, int]]]
    ):
        # type: (...) -> TableServiceDto

        if not table_id or tender_id is None:
            raise MissingAnyOfParameters(params=["table_id", "tender_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        amount = str(amount) if amount is not None else ""
        tender_id = str(tender_id)
        tender_details = json.dumps(tender_detail) if isinstance(tender_detail, dict) else ""
        params = ["REGISTER_SERVICE_TENDER", str(self.pos_id), user_id, table_id, tender_id, amount, tender_details]
        return self._process_old_call(params=params)

    def reopen_service(
        self,       # type: TableService
        table_id,   # type: str
    ):
        # type: (...) -> TableServiceDto

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["REOPEN_SERVICE", str(self.pos_id), user_id, table_id]
        return self._process_old_call(params=params)

    def recall_service(
        self,       # type: TableService
        table_id,   # type: str
    ):
        # type: (...) -> TableServiceDto

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["RECALL_SERVICE", str(self.pos_id), user_id, table_id]
        return self._process_old_call(params=params)

    def set_custom_property(
        self,               # type: TableService
        prop_key,           # type: str
        prop_value="",      # type: str
        table_id=None,      # type: Optional[str]
        service_id=None,    # type: Optional[int]
    ):
        # type: (...) -> None

        if not prop_key or (not table_id and not service_id):
            raise MissingAnyOfParameters(params=["prop_value", "table_id", "service_id"])

        params = ["SET_CUSTOM_PROPERTY", str(self.pos_id), prop_key, prop_value, table_id or "", service_id or ""]
        return self._process_old_call(params=params)

    def set_custom_properties(
        self,               # type: TableService
        properties=None,    # type: Union[str, Dict[str, Union[str, int, Dict, List]]]
        table_id=None,      # type: str
        service_id=None,    # type: str
    ):
        # type: (...) -> None

        if isinstance(properties, dict):
            properties = json.dumps(properties)

        params = ["SET_CUSTOM_PROPERTIES", str(self.pos_id), properties, table_id or "", service_id or ""]
        return self._process_old_call(params=params)

    def total_service(
        self,                   # type: TableService
        table_id,               # type: str
        tip_rate="",            # type: str
        tip_amount="",          # type: str
        sale_type="EAT_IN",     # type: str
    ):
        # type: (...) -> TableServiceDto

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["TOTAL_SERVICE", str(self.pos_id), user_id, table_id, tip_rate, tip_amount, sale_type]
        return self._process_old_call(params=params)

    def move_order_to_service(
        self,               # type: TableService
        order_id,           # type: int
        table_id=None,      # type: Optional[str]
        service_id=None,    # type: Optional[int]
    ):
        # type: (...) -> None

        if not order_id or (not table_id and not service_id):
            raise MissingAnyOfParameters(params=["order_id", "table_id", "service_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        service_id = str(service_id or "")
        params = ["MOVE_ORDER_TO_SERVICE", str(self.pos_id), user_id, table_id, str(order_id), service_id]
        self._process_old_call(params=params)

    def store_service(
        self,               # type: TableService
        table_id,           # type: str
        order_id=None,      # type: Optional[int]
    ):
        # type: (...) -> None

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["STORE_SERVICE", str(self.pos_id), user_id, table_id, str(order_id or "")]
        self._process_old_call(params=params)

    def close_service(
        self,       # type: TableService
        table_id,   # type: str
    ):
        # type: (...) -> TableServiceDto

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["CLOSE_SERVICE", str(self.pos_id), user_id, table_id]
        return self._process_old_call(params=params)

    def abandon_service(
        self,       # type: TableService
        table_id,   # type: str
    ):
        # type: (...) -> None

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        params = ["ABANDON_SERVICE", str(self.pos_id), user_id, table_id]
        return self._process_old_call(params=params)

    def move_tabs(
        self,              # type: TableService
        new_user_id,       # type: str
        from_tab_id,       # type: str
        dest_tab_id,       # type: str
        linked_tab,        # type: str
    ):
        # type: (...) -> Message

        params = [
            "MOVE_TABLE",
            str(self.pos_id),
            new_user_id,
            from_tab_id,
            dest_tab_id,
            "1",
            json.dumps([linked_tab]),
            "seats",
            "True",
            new_user_id,
            "None"
        ]

        msg = self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data="\0".join(params).encode(),
                timeout=600000000,
            ),
        )
        if msg.token == DefaultToken.TK_SYS_NAK:
            raise TableServiceException(
                errmsg=(msg.data if msg.format == FM_STRING and msg.data else "Error move tabs")
            )

        return msg

    def system_abandon_service(
        self,           # type: TableService
        table_id,       # type: str
    ):
        # type: (...) -> TableServiceDto

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = 0
        params = ["ABANDON_SERVICE", str(self.pos_id), str(user_id), table_id]
        return self._process_old_call(params=params)

    def create_service_order(
        self,
        table_id,                   # type: str
        custom_properties=None,     # type: Dict[str, str]
        sale_type="EAT_IN",         # type: str
    ):
        # type: (...) -> int

        if not table_id:
            raise MissingAnyOfParameters(params=["table_id"])

        user_id = str(self.pos_service.get_pos_model().logged_user.id)
        properties = json.dumps(custom_properties) if custom_properties else ""
        params = ["CREATE_ORDER", str(self.pos_id), user_id, table_id, "", properties, sale_type]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return int(eTree.XML(reply.data).get("orderId"))

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def change_number_of_seats(
        self,                   # type: TableService
        user_id,                # type: str
        seats,                  # type: str
        table_id,               # type: str
        service_id="",          # type: str
        custom_seats=None,      # type: Optional[List[str]]
    ):
        # type: (...) -> None

        params = [
            "CHANGE_NUMBER_OF_SEATS",
            str(self.pos_id),
            user_id,
            seats,
            table_id,
            service_id,
            ";".join(custom_seats) if custom_seats else "",
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return None

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def move_table(
        self,                           # type: TableService
        user_id,                        # type: str
        from_table_id,                  # type: str
        dest_table_id,                  # type: str
        number_of_seats="",             # type: str
        tables_to_set_available=None,   # type: Optional[List]
        mode="seats",                   # type: str
        ignore_totaled_orders=False,    # type: bool
        custom_seats=None,              # type: Optional[List[str]]
        current_user_id=None,           # type: Optional[Union[str, int]]
        authorizer=None,                # type: Optional[Union[str, int]]
    ):
        # type: (...) -> None

        params = [
            "MOVE_TABLE",
            str(self.pos_id),
            user_id,
            from_table_id,
            dest_table_id,
            number_of_seats,
            tables_to_set_available,
            mode,
            "true" if ignore_totaled_orders else "false",
            ";".join(custom_seats) if custom_seats else "",
            current_user_id,
            authorizer,
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def move_table_order(
        self,                       # type: TableService
        user_id,                    # type: str
        from_table_id,              # type: str
        dest_table_id,              # type: str
        order_id,                   # type: int
        number_of_seats="",         # type: Union[str, int]
        custom_seats=None,          # type: Optional[List[str]]
        current_user_id=None,       # type: Optional[Union[str, int]]
        authorizer=None,            # type: Optional[Union[str, int]]
    ):
        # type: (...) -> None

        params = [
            "MOVE_TABLE_ORDER",
            str(self.pos_id),
            user_id,
            from_table_id,
            dest_table_id,
            str(order_id),
            number_of_seats,
            ";".join(custom_seats) if custom_seats else "",
            current_user_id,
            authorizer
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return

        if "INVALID_TABLE_STATUS" in reply.data:
            raise InvalidTableStatus()

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def join_tables(
        self,                       # type: TableService
        user_id,                    # type: str
        from_table_id,              # type: str
        dest_table_id,              # type: str
        number_of_seats="",         # type: str
        custom_seats=None,          # type: Optional[List[str]]
        current_user_id=None,       # type: Optional[Union[str, int]]
        authorizer=None,            # type: Optional[Union[str, int]]
    ):
        # type: (...) -> None

        params = [
            "JOIN_TABLES",
            str(self.pos_id),
            user_id,
            from_table_id,
            dest_table_id,
            number_of_seats,
            ";".join(custom_seats) if custom_seats else "",
            current_user_id,
            authorizer,
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return

        if "INVALID_TABLE_STATUS" in reply.data:
            raise InvalidTableStatus()

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def change_seat_number(
        self,               # type: TableService
        table_id,           # type: str
        old_seat_number,    # type: str
        new_seat_number,    # type: str
    ):
        # type: (...) -> None

        params = [
            "CHANGE_SEAT_NUMBER",
            str(self.pos_id),
            table_id,
            old_seat_number,
            new_seat_number,
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def change_sale_lines_seats(
        self,           # type: TableService
        table_id,       # type: str
        to_seat,        # type: str
        orders_lines,   # type: List[OrderLines]
    ):
        # type: (...) -> str

        params = [
            "CHANGE_SALE_LINES_SEATS",
            str(self.pos_id),
            table_id,
            to_seat,
            str(orders_lines),
        ]

        reply = self._get_reply(params=params)
        if reply.token == DefaultToken.TK_SYS_ACK:
            return reply.data

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def _process_old_call(
        self,       # type: TableService
        params,     # type: List[str]
    ):
        # type: (...) -> Optional[TableServiceDto]

        reply = self._get_reply(params=params)

        if reply.token == DefaultToken.TK_SYS_ACK:
            return self._build_table_service(reply=reply) if reply.data else None

        if reply.data == CannotSaveEmptyOrder.get_error_code():
            raise CannotSaveEmptyOrder()

        if "|" in reply.data:
            splitted_data = reply.data.decode("utf-8").split("|")
            if splitted_data[0] == CannotSaveOrderWithNotFilledMandatoryOption.get_error_code():
                raise CannotSaveOrderWithNotFilledMandatoryOption(
                    part_code=int(splitted_data[1]),
                    part_name=splitted_data[2],
                )

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=reply.data,
        )

    def _get_reply(
        self,       # type: TableService
        params,     # type: List[str]
    ):
        # type: (...) -> Message

        return self.message_bus.send_message(
            component=self.table_mgr,
            message=Message(
                token=TK_CMP_MESSAGE,
                data_type=DataType.param,
                data="\0".join(params).encode(),
                timeout=600000000,
            ),
        )

    @staticmethod
    def _tabs_error(
        ex_message,     # type: str
    ):
        # type: (...) -> None

        ex_params = str(ex_message).split("|")

        if "$TAB_NOT_FOUND" == ex_params[0]:
            raise InvalidTabMask(tab_name=ex_params[1])

        if "$TAB_NOT_REGISTERED" == ex_params[0]:
            raise TabNotInValidTabNames(tab_name=ex_params[1])

        if "$SELECT_A_DIFFERENT_TAB" == ex_params[0]:
            raise TabToJoinIsTheSame()

        if "$TAB_TO_JOIN_DOES_NOT_EXISTS" == ex_params[0]:
            raise TabToJoinDoesNotExists(tab_name=ex_params[1])

        if "$TAB_TO_JOIN_IS_EMPTY" == ex_params[0]:
            raise TabToJoinIsEmpty(tab_name=ex_params[1])

        if "$TAB_TO_JOIN_IS_ALREADY_JOINED" == ex_params[0]:
            raise TabToJoinIsAlreadyJoined(tab_name=ex_params[1])

        if "$TAB_TO_JOIN_IS_OPENED_IN_ANOTHER_POS" == ex_params[0]:
            raise TabToJoinOpenedInAnotherPOS(tab_name=ex_params[1])

        if "$CURRENT_TAB_WITH_PARTIAL_TENDERS" == ex_params[0]:
            raise CurrentTabWithPartialTenders(table_id=ex_params[1])

        if "$TAB_TO_JOIN_WITH_PARTIAL_TENDERS" == ex_params[0]:
            raise TabToJoinWithPartialTenders(table_id=ex_params[1])

        if "$TAB_TO_JOIN_IN_INVALID_STATUS" == ex_params[0]:
            ex_params = ex_params[ex_params.index("$TAB_TO_JOIN_IN_INVALID_STATUS"):]
            raise TabToJoinInInvalidStatus(
                state=ex_params[2],
                tab_name=ex_params[1],
            )

        raise UnknownError(
            token=DefaultToken.TK_SYS_NAK,
            data=ex_message.encode(),
        )

    @staticmethod
    def _build_table_service(
        reply,      # type: Message
    ):
        # type: (...) -> TableServiceDto

        reply_xml = eTree.XML(reply.data)
        return TableServiceDto.from_xml(xml=reply_xml)
