import cfgtools
import os
from copy import copy

import sysactions

from typing import (
    Dict,
    List,
    Optional,
)

from pos_model import (
    SaleItem,
    SimplifiedSaleItem,
)
from pos_util import simplify_sale_items

from basereport import TableReport, ReportColumnDefinition, AlignTypes
from report import Part
from report.command import (
    RepeatCommand,
    AlignCommand,
    FontCommand,
    CutPaperCommand
)
from utils import format_line

from ._ReportBuilder import ReportBuilder
from ..dto import TableReportBodyDto, TableReportOrderDto, TableReportSaleLineDto


class TableReportBody(ReportBuilder):

    def __init__(
        self,
        table_report_body_dto,      # type: TableReportBodyDto
        print_by,                   # type: str
        hide_unpriced_items,        # type: bool
        pre_account_with_discount,  # type: bool
        pos_id,                     # type: str
        filtered_seat="0"           # type: Optional[str]
    ):
        # type: (...) -> None

        self.table_report_body_dto = table_report_body_dto
        self.print_by = print_by
        self.hide_unpriced_items = hide_unpriced_items
        self.pre_account_with_discount = pre_account_with_discount
        self.pos_id = pos_id
        self.filtered_seat = filtered_seat
        self.cut_paper_by_seats = self._get_cut_paper_by_seats_configuration()

    def generate(self):
        # type: () -> List[TableReport]

        lines = []
        table_report_order_dto = self.table_report_body_dto.orders
        table_seats = self.table_report_body_dto.service_seats

        self._configure_font(lines)
        self._generate_items_description(lines)
        self._repeat_of_equals_symbol_line(lines)
        self._generate_order(
            lines=lines,
            table_report_order_dto=table_report_order_dto,
            tip_amount=self.table_report_body_dto.tip,
            detailed_discounts=self.table_report_body_dto.detailed_discounts,
            detailed_payments=self.table_report_body_dto.detailed_payments,
            detailed_tip_excluded=self.table_report_body_dto.detailed_tip_excluded,
            sub_total_amount=self.table_report_body_dto.sub_total_amount,
            total_payment=self.table_report_body_dto.promotional_tenders,
            seats=table_seats,
            discounts_groups=self.table_report_body_dto.discount_groups,
        )

        if self.filtered_seat == "0":
            self._repeat_of_equals_symbol_line(lines)
            self._generate_sub_total_summary(
                lines=lines,
                sub_total_amount=self.table_report_body_dto.sub_total_amount,
                tip_amount=self.table_report_body_dto.tip,
                discounts_amount=self.table_report_body_dto.discount_amount,
                promotional_tenders=self.table_report_body_dto.promotional_tenders,
                discount_groups=self.table_report_body_dto.discount_groups,
            )
            self._generate_total_summary(
                lines,
                self.table_report_body_dto.total_amount
            )
            self._repeat_of_equals_symbol_line(lines)
            if self.table_report_body_dto.service_tenders:
                self._generate_partial_payments_summary(
                    lines=lines,
                    service_tenders=self.table_report_body_dto.service_tenders,
                )
                self._generate_remaining_amount(
                    lines=lines,
                    total_amount=self.table_report_body_dto.total_amount,
                    service_tenders=self.table_report_body_dto.service_tenders,
                )
                self._repeat_of_equals_symbol_line(lines=lines)

            self._generate_total_division_sugestion(
                lines,
                self.table_report_body_dto.total_amount,
                self.table_report_body_dto.service_seats
            )
            self._generate_customer_doc_field(lines)
            self._repeat_of_under_symbol_line(lines)
        elif self.table_report_body_dto.service_tenders:
            self._repeat_of_equals_symbol_line(lines=lines)
            self._generate_warning_partial_payments(lines=lines)
            self._repeat_of_equals_symbol_line(lines=lines)
        self._a_new_empty_line(lines)
        self._generate_footer(lines)

        return lines

    def _generate_order(
        self,
        lines,                      # type: List
        table_report_order_dto,     # type: List[TableReportOrderDto]
        tip_amount,                 # type: float
        detailed_discounts,         # type: Dict
        detailed_payments,          # type: Dict
        detailed_tip_excluded,      # type: Dict
        sub_total_amount,           # type: float
        total_payment,              # type: float
        seats,                      # type: int
        discounts_groups,           # type: Dict
    ):
        # type: (...) -> TableReportBody

        grouped_sale_lines = self._get_grouped_sale_lines(table_report_order_dto, seats)
        fraction_line_numbers = []
        for _, sale_lines in grouped_sale_lines.items():
            fraction_line_numbers.extend(self._get_fraction_sale_line_identifiers(sales_line_list=sale_lines))

        if self.print_by in ["SEATS", "TAB"] and len(grouped_sale_lines) > 1:
            simplified_sale_lines = {}
            for seat, sale_lines in grouped_sale_lines.items():
                simplified_sale_lines[seat] = _simplify_table_report_sale_lines(
                    sale_lines,
                    hide_unpriced_items=self.hide_unpriced_items,
                    fraction_line_numbers=fraction_line_numbers,
                )
            lines.extend(
                self._generate_orders_split_by_seats_from_sale_lines_with_tab_on_modifiers(
                    grouped_sale_lines=simplified_sale_lines,
                    tip_amount=tip_amount,
                    detailed_discounts=detailed_discounts,
                    detailed_payments=detailed_payments,
                    detailed_tip_excluded=detailed_tip_excluded,
                    sub_total_amount=sub_total_amount,
                    total_payment=total_payment,
                    printed_by=self.print_by,
                    filtered_seat=self.filtered_seat,
                    discounts_groups=discounts_groups,
                )
            )
        else:
            all_orders_sale_lines = []
            for order in table_report_order_dto:
                all_orders_sale_lines.extend(order.sales_line_list)
            simplified_sale_lines = _simplify_table_report_sale_lines(
                all_orders_sale_lines,
                hide_unpriced_items=self.hide_unpriced_items,
                fraction_line_numbers=fraction_line_numbers,
            )
            lines.extend(self._generate_order_without_split_seats(sale_lines=simplified_sale_lines))
            lines.append(a_new_line())

        return self

    @staticmethod
    def _get_parent_table_seat(sales_line_list, line_number):
        seat = [x.seat for x in sales_line_list if x.level == 0 and x.line_number == line_number]
        if len(seat) > 0:
            return seat[0]
        return 0

    def _get_table_seat(self, sales_line_list, sale_line):
        if sale_line.level == 0:
            seat = sale_line.seat
        else:
            seat = self._get_parent_table_seat(sales_line_list, sale_line.line_number)

        if seat == 0:
            seat = "$TABLE"

        return seat

    def _get_grouped_sale_lines(self, table_report_order_dto, seats):
        grouped_sale_lines = {}
        for i in range(seats):
            grouped_sale_lines[i+1] = []

        for order in table_report_order_dto:
            for sale_line in order.sales_line_list:
                if sale_line.item_type == "COUPON":
                    continue

                if sale_line.item_type == "COMBO" and sale_line.qty == 0:
                    continue

                seat = self._get_table_seat(order.sales_line_list, sale_line)

                if seat not in grouped_sale_lines:
                    grouped_sale_lines[seat] = [sale_line]
                else:
                    grouped_sale_lines[seat].append(sale_line)
        return grouped_sale_lines

    @staticmethod
    def _get_cut_paper_by_seats_configuration():
        config = cfgtools.read(os.environ["LOADERCFG"])
        cut_paper_config = (config.find_value("TableReport.CutPaperBySeat") or "false").lower() == "true"

        return cut_paper_config

    def _generate_order_without_split_seats(
        self,
        sale_lines,     # type: List[TableReportSaleLineDto]
    ):
        # type: (...) -> List[TableReport]

        order_items_lines = self._build_product_lines(sales_line_list=sale_lines)
        order_lines = TableReport(
            lines=order_items_lines,
            column_definitions=a_sub_total_summary_column_definition(),
        )
        return [order_lines]

    def _generate_orders_split_by_seats_from_sale_lines_with_tab_on_modifiers(
        self,
        grouped_sale_lines,     # type: Dict
        tip_amount,             # type: float
        detailed_discounts,     # type: Dict
        detailed_payments,      # type: Dict
        detailed_tip_excluded,  # type: Dict
        sub_total_amount,       # type: float
        total_payment,          # type: float
        printed_by,             # type: str
        filtered_seat='0',      # type: Optional[str]
        discounts_groups=None,  # type: Optional[Dict]
    ):
        # type: (...) -> List

        if printed_by == "TAB":
            grouped_sale_lines = {k: v for k, v in grouped_sale_lines.items() if v}

        tip_excluded_amount = sum(detailed_tip_excluded.values())
        tip_percentage = self._get_percentage(tip_amount, sub_total_amount - tip_excluded_amount)
        seat_quantities = len(grouped_sale_lines.keys()) - (1 if "$TABLE" in grouped_sale_lines else 0)

        not_mapped_payment = total_payment - sum(detailed_payments.values())

        table_tip_excluded = detailed_tip_excluded[0] if 0 in detailed_tip_excluded.keys() else 0.0
        table_discount = detailed_discounts[0] if 0 in detailed_discounts.keys() else 0.0
        table_payment = detailed_payments[0] if 0 in detailed_payments.keys() else 0.0
        table_sale_line = grouped_sale_lines["$TABLE"] if "$TABLE" in grouped_sale_lines else {}
        table_sub_total = 0.0 + sum([float(x.price) if x.price and x.qty != 0 else 0.0 for x in table_sale_line])
        table_tip = ((tip_percentage * (table_sub_total - table_tip_excluded)) / 100)
        table_per_seat = round(table_sub_total / seat_quantities, 2)
        table_discount_per_seat = table_discount / seat_quantities
        table_payment_per_seat = table_payment / seat_quantities
        table_tip_per_seat = table_tip / seat_quantities

        sub_total_seats_amount = sub_total_amount - table_sub_total or 1

        lines = []
        sorted_grouped_sale_lines = sorted(grouped_sale_lines, key=lambda g: (1, g) if isinstance(g, int) else (0, g))
        for index, seat in enumerate(sorted_grouped_sale_lines):
            if str(seat) != filtered_seat and filtered_seat != '0':
                continue
            seat_lines = []
            add_to_report = True
            if filtered_seat == '0':
                if self.cut_paper_by_seats:
                    seat_lines.append(Part(commands=[CutPaperCommand()]))
                elif index != 0:
                    seat_lines.append(Part(commands=[RepeatCommand(38, "-")]))
                    seat_lines.append(a_new_line())

            is_table = seat == "$TABLE"
            name_by = "$SEAT_NUMBER" if printed_by == "SEATS" else "$TAB_NUMBER"
            seat_descr = seat if is_table else "{} {}".format(name_by, seat)
            seat_sub_total = sum([float(x.price) if x.price and x.qty != 0 else 0.0 for x in grouped_sale_lines[seat]])
            discounts_groups_seat = copy(discounts_groups)
            seat_tip = 0
            seat_total = 0
            seat_payment = 0
            seat_discount = 0
            if not is_table:
                for key in discounts_groups_seat.keys():
                    value = discounts_groups_seat[key]
                    seat_discount = value * (seat_sub_total / sub_total_seats_amount)
                    seat_discount += detailed_discounts[seat] if seat in detailed_discounts.keys() else 0.0
                    seat_discount = round(seat_discount + table_discount_per_seat, 2)
                    discounts_groups_seat[key] = seat_discount

                seat_tip_excluded = detailed_tip_excluded[seat] if seat in detailed_tip_excluded.keys() else 0.0
                seat_payment = not_mapped_payment * (seat_sub_total / sub_total_seats_amount)
                seat_payment += detailed_payments[seat] if seat in detailed_payments.keys() else 0.0
                seat_payment = round(seat_payment + table_payment_per_seat, 2)
                seat_tip = (tip_percentage * (seat_sub_total - seat_tip_excluded)) / 100
                seat_tip = round(seat_tip + table_tip_per_seat, 2)
                seat_total = seat_sub_total + table_per_seat - seat_discount + seat_tip - seat_payment

            seat_title = [
                [seat_descr, ""]
            ]
            seat_lines.append(TableReport(lines=seat_title, column_definitions=a_total_summary_column_definitions()))
            seat_lines.append(a_new_line())

            order_items = self._build_product_lines(sales_line_list=grouped_sale_lines[seat])
            order_lines = TableReport(
                lines=order_items,
                column_definitions=a_sub_total_summary_column_definition(),
            )
            seat_lines.append(order_lines)
            seat_lines.append(a_new_line())

            subtotal = [["$SUBTOTAL", "#NUMBER({})".format(seat_sub_total)]]
            seat_lines.append(
                TableReport(
                    lines=subtotal,
                    column_definitions=a_total_detail_summary_column_definition(),
                )
            )

            seat_details = []
            has_details = False
            if table_per_seat > 0 and is_table:
                seat_details.append(["$DIVISION_FOR_SEATS", "#NUMBER({})".format(table_per_seat)])
            if table_per_seat > 0 and not is_table:
                seat_details.append(["$DIVISION_FROM_TABLE", "#NUMBER({})".format(table_per_seat)])
            seat_lines.append(
                TableReport(
                    lines=seat_details,
                    column_definitions=a_total_detail_summary_column_definition(),
                )
            )

            seat_details = []
            if seat_discount > 0:
                self._generate_discount_field(
                    report_lines=seat_lines,
                    discount_list=seat_details,
                    discount_amount=seat_discount,
                    discount_groups=discounts_groups_seat,
                )

            if seat_payment > 0 and not is_table:
                seat_details.append(["$PROMOTIONAL_TENDERS", "#NUMBER({})".format(-seat_payment)])
            if seat_tip > 0:
                seat_details.append(["$TIP", "#NUMBER({})".format(seat_tip)])

            if len(seat_details) > 0:
                has_details = True

            if not is_table:
                seat_details.append(["$TOTAL", "#NUMBER({})".format(seat_total)])
            if not has_details and len(order_lines.lines) == 0:
                add_to_report = False
            seat_lines.append(
                TableReport(
                    lines=seat_details,
                    column_definitions=a_total_detail_summary_column_definition(),
                )
            )

            if filtered_seat == '0' and self.cut_paper_by_seats and index == (len(grouped_sale_lines) - 1):
                seat_lines.append(Part(commands=[CutPaperCommand()]))

            if add_to_report:
                lines.extend(seat_lines)

        return lines

    @staticmethod
    def _get_percentage(amount, sub_total_amount):
        if amount > 0:
            percentage = round((amount * 100) / sub_total_amount, 2)
        else:
            percentage = 0
        return percentage

    @staticmethod
    def _build_product_lines(
        sales_line_list,    # type: List[TableReportSaleLineDto]
    ):
        # type: (...) -> List

        order_items_lines = []
        for item in sales_line_list:
            item_service_period = "[{}]".format(item.service_period) if item.service_period else " "

            item_price = item.price or 0
            item_price = "#NUMBER({})".format(item_price) if item_price else ""

            comment_description = ""
            if item.comment and item.comment.startswith("["):
                comment_description = "$" + item.comment.replace("[", "").replace("]", "").upper() + " "

            product_description = comment_description + item.product_name
            fraction = item.fraction_parent
            if fraction:
                numerator = int(round(item.qty * fraction))
                qty_description = " {}/{}".format(numerator, fraction) if numerator != fraction else "1.00"
            else:
                qty_description = "{:.2f}".format(item.qty)
            qty_description = qty_description.rjust(5)
            indentation = " " * (item.level + 1)
            description = u"{}{}".format(indentation, product_description).ljust(30)[:30]
            item_price = item_price.rjust(38 - len(description) + len(qty_description))

            order_items_lines.append([qty_description, description, item_service_period, item_price])

        return order_items_lines

    @staticmethod
    def _get_fraction_sale_line_identifiers(
        sales_line_list,    # type: List[TableReportSaleLineDto]
    ):
        # type: (...) -> List

        fraction_sale_lines_list = []
        for sale_line in sales_line_list:
            if sale_line.fraction_parent:
                line_number_identifier = "{}-{}".format(sale_line.order_id, sale_line.line_number)
                fraction_sale_lines_list.append(line_number_identifier)

        return fraction_sale_lines_list

    def _generate_sub_total_summary(
        self,
        lines,                  # type: List
        sub_total_amount,       # type: float
        tip_amount,             # type: float
        discounts_amount,       # type: float
        promotional_tenders,    # type: float
        discount_groups,        # type: dict
    ):
        # type: (...) -> TableReportBody

        total_summary_column_definitions = a_total_summary_column_definitions()

        sub_total_lines = [["$SUBTOTAL", "#NUMBER({})".format(sub_total_amount)]]

        lines.extend([
            TableReport(
                lines=sub_total_lines,
                column_definitions=total_summary_column_definitions,
            )
        ])

        if discounts_amount > 0:
            self._generate_discount_field(
                report_lines=lines,
                discount_list=sub_total_lines,
                discount_amount=discounts_amount,
                discount_groups=discount_groups,
            )

        sub_total_lines = []
        if promotional_tenders > 0:
            sub_total_lines.append(["$PROMOTIONAL_TENDERS", "#NUMBER({})".format(-promotional_tenders)])
        if tip_amount > 0:
            sub_total_lines.append(["$SUGGESTED_TIP", "#NUMBER({})".format(tip_amount)])

        lines.extend([
            TableReport(
                lines=sub_total_lines,
                column_definitions=total_summary_column_definitions,
            ),
            a_new_line(),
        ])
        return self

    def _generate_discount_field(
        self,
        report_lines,       # type: List
        discount_list,      # type: List
        discount_amount,    # type: float
        discount_groups,    # type: Dict
    ):
        # type: (...) -> None

        total_summary_column_definitions = a_total_summary_column_definitions()
        discount_lines = []
        has_groups = len(discount_groups) > 1
        if has_groups:
            report_lines.extend([a_new_line()])
            report_lines.extend([
                Part(
                    text="$DISCOUNTS",
                    commands=[AlignCommand(AlignCommand.Alignment.left)],
                ),
                a_new_line(),
            ])
            self._parse_discounts_group(
                discounts_list=discount_lines,
                discount_groups=discount_groups,
            )
            report_lines.extend([
                TableReport(
                    lines=discount_lines,
                    column_definitions=total_summary_column_definitions,
                ),
                a_new_line(),
            ])
        else:
            discount_list.append(["$DISCOUNTS", "#NUMBER({})".format(-discount_amount)])

    @staticmethod
    def _parse_discounts_group(
        discounts_list,         # type: List
        discount_groups=None,   # type: Dict
    ):
        # type: (...) -> None

        for discount in discount_groups:
            value = float(discount_groups.get(discount))
            discounts_list.append(["{}".format(discount), "#NUMBER({})".format(-value)])

    def _generate_total_summary(self, lines, total_amount):
        # type: (List, float) -> TableReportBody

        total_summary_column_definitions = a_total_summary_column_definitions()

        sub_total_lines = [
            ["$TOTAL", "#NUMBER({})".format(total_amount)]
        ]
        lines.extend([TableReport(lines=sub_total_lines, column_definitions=total_summary_column_definitions)])
        return self

    def _generate_partial_payments_summary(
        self,
        lines,              # type: List
        service_tenders,    # type: List[dict]
    ):
        # type: (...) -> TableReportBody

        total_summary_column_definitions = a_total_summary_column_definitions()
        payments = []
        for tender in service_tenders:
            tender_amount = tender.get('tender_amount')
            payments.append([tender.get('tender_descr'), "#NUMBER({})".format(tender_amount)])

        total_paid = sum([float(tender.get('tender_amount')) for tender in service_tenders])
        payments.append(["$PAID", "#NUMBER({})".format(total_paid)])

        lines.extend(
            [
                TableReport(
                    lines=payments,
                    column_definitions=total_summary_column_definitions,
                ),
                a_new_line()
            ]
        )

        return self

    def _generate_remaining_amount(
        self,
        lines,              # type: List
        total_amount,       # type: float
        service_tenders,    # type: List[dict]
    ):
        # type: (...) -> TableReportBody

        total_summary_column_definitions = a_total_summary_column_definitions()

        total_paid = sum([float(service_tender.get('tender_amount')) for service_tender in service_tenders])
        to_pay = [["$TO_PAY", "#NUMBER({})".format(total_amount - total_paid)]]
        lines.extend([
            TableReport(
                lines=to_pay,
                column_definitions=total_summary_column_definitions,
            )
        ])

        return self

    def _generate_warning_partial_payments(
        self,
        lines,              # type: List
        columns_width=38,   # type: int
    ):
        # type: (...) -> TableReportBody

        model = sysactions.get_model(self.pos_id)
        message = sysactions.translate_message(model, "PAYMENTS_NOT_DISTRIBUTED")

        message_size = len(message)
        begin = 0
        while begin < message_size:
            lines.extend([
                Part(
                    text=message[begin:begin + columns_width],
                    commands=[AlignCommand(AlignCommand.Alignment.left)],
                ),
                a_new_line(),
            ])
            begin += columns_width

        return self

    def _generate_total_division_sugestion(self, lines, total_amount, service_seats):
        # type: (List, float, int) -> TableReportBody

        if service_seats > 1:
            division_amount = total_amount / service_seats

            lines.extend([
                Part("$EQUAL_DIVISION_FOR_ALL_SEATS", [AlignCommand(AlignCommand.Alignment.left)]), a_new_line()
            ])

            sub_total_lines = [
                ["$DIVISION_SUGESTION_FOR {}".format(service_seats), "#NUMBER({})".format(division_amount)]]

            lines.extend(
                [TableReport(lines=sub_total_lines, column_definitions=a_total_division_sugestion_definitions()),
                 a_new_line()
                 ])
        return self

    def _generate_customer_doc_field(self, lines):
        # type: (List) -> TableReportBody

        total_division_sugestion_definitions = a_customer_doc_field()

        sub_total_lines = [["$TYPE_YOUR_DOC_NUM"]]
        lines.extend(
            [TableReport(lines=sub_total_lines, column_definitions=total_division_sugestion_definitions),
             a_new_line()
             ])
        return self

    def _generate_footer(self, lines):
        # type: (List) -> TableReportBody
        lines.extend([
            Part("$THANK_YOU_FOR_YOUR_PREFERENCE", [AlignCommand(AlignCommand.Alignment.center)]), a_new_line(),
            Part("$SERVICE_TAX_IS_NOT_MANDATORY", [AlignCommand(AlignCommand.Alignment.center)]), a_new_line()
        ])

        if not self.pre_account_with_discount:
            model = sysactions.get_model(self.pos_id)
            message = sysactions.translate_message(model, "WARNING_REPORT_ITEMS_CONFERENCE")
            self._a_new_empty_line(lines)
            lines.extend(format_line('', message, columns=38, definition=a_information_footer()))

        self._a_new_empty_line(lines)
        return self

    def _generate_items_description(self, lines):
        # type: (List[TableReport]) -> TableReportBody

        items_description = TableReport(
            lines=[["$QTY", "$ITEM", "#CURRENCY_SYMBOL()"]],
            column_definitions=a_item_column_definition())

        lines.append(items_description)
        return self

    def _configure_font(self, lines):
        # type: (List[TableReport]) -> TableReportBody
        lines.append(Part(commands=[FontCommand(FontCommand.Font.A)]))

        return self

    def _repeat_of_equals_symbol_line(self, lines):
        # type: (List) -> TableReportBody
        repeat_symbol = [Part(commands=[RepeatCommand(38, "=")]), a_new_line()]
        lines.extend(repeat_symbol)

        return self

    def _repeat_of_under_symbol_line(self, lines):
        # type: (List) -> TableReportBody
        repeat_symbol = [Part(commands=[RepeatCommand(38, "_")]), a_new_line()]
        lines.extend(repeat_symbol)

        return self

    def _a_new_empty_line(self, lines):
        # type: (List) -> TableReportBody
        lines.extend([a_new_line()])

        return self


def _simplify_table_report_sale_lines(
    sale_lines,                     # type: List[TableReportSaleLineDto]
    hide_unpriced_items=False,      # type: bool
    fraction_line_numbers=None,     # type: Optional[List]
):
    # type: (...) -> List[TableReportSaleLineDto]

    items = [
        _create_simplified_sale_item(
            sale_line=sale_line,
            fraction_line_numbers=fraction_line_numbers,
        ) for sale_line in sale_lines
    ]
    simplified_sale_items = simplify_sale_items(
        items,
        hide_unpriced_items=hide_unpriced_items,
    )
    result_sale_lines = []

    for item in simplified_sale_items:
        sale_line = item.original_reference  # type: TableReportSaleLineDto
        sale_line.level = item.sale_item.level
        sale_line.price = item.sale_item.item_price
        sale_line.qty = item.sale_item.quantity
        if item.sale_item.default_quantity is not None:
            sale_line.default_qty = item.sale_item.default_quantity
        result_sale_lines.append(sale_line)

    return result_sale_lines


def _create_simplified_sale_item(
    sale_line,                      # type: TableReportSaleLineDto
    fraction_line_numbers=None,     # type: Optional[Dict]
):
    # type: (...) -> SimplifiedSaleItem

    sale_item = SaleItem(
        line_number=sale_line.line_number,
        level=sale_line.level,
        part_code=sale_line.part_code,
        product_name=sale_line.product_name,
        item_id=sale_line.item_id,
        item_price=sale_line.price,
        quantity=sale_line.qty,
    )
    sale_item.overwritten_unit_price = sale_line.overwritten_unit_price
    sale_item.seat = sale_line.seat
    sale_item.comment = sale_line.comment
    sale_item.service_period = sale_line.service_period
    sale_item.default_quantity = sale_line.default_qty
    sale_item.type = sale_line.item_type
    sale_line_identifier = "{}-{}".format(sale_line.order_id, sale_line.line_number)

    item = SimplifiedSaleItem(
        sale_item,
        order_id=sale_line.order_id,
        original_reference=sale_line,
        has_fraction=sale_line_identifier in fraction_line_numbers
    )
    return item


def a_new_line():
    return Part("\n")


def a_item_column_definition():
    return [
        ReportColumnDefinition(width=5,
                               before_text="",
                               after_text="",
                               after_fill_text="",
                               align=AlignTypes.LEFT),
        ReportColumnDefinition(width=25,
                               fill_with_char=" ",
                               before_text="  ",
                               align=AlignTypes.LEFT),
        ReportColumnDefinition(width=8,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.RIGHT)

    ]


def a_sub_total_summary_column_definition():
    return [
        ReportColumnDefinition(width=5,
                               before_text="",
                               after_text="",
                               after_fill_text="",
                               align=AlignTypes.CENTER),
        ReportColumnDefinition(width=22,
                               fill_with_char=" ",
                               before_text=" ",
                               align=AlignTypes.LEFT),
        ReportColumnDefinition(width=4,
                               fill_with_char=" ",
                               align=AlignTypes.RIGHT),
        ReportColumnDefinition(width=7,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.RIGHT)

    ]


def a_total_detail_summary_column_definition():
    return [
        ReportColumnDefinition(width=25,
                               fill_with_char=" ",
                               before_text=" ",
                               align=AlignTypes.RIGHT),
        ReportColumnDefinition(width=13,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.RIGHT)

    ]


def a_total_summary_column_definitions():
    return [
        ReportColumnDefinition(width=25,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.LEFT),
        ReportColumnDefinition(width=13,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.RIGHT)

    ]


def a_total_division_sugestion_definitions():
    return [
        ReportColumnDefinition(width=25,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.LEFT),
        ReportColumnDefinition(width=13,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.RIGHT)

    ]


def a_customer_doc_field():
    return [
        ReportColumnDefinition(width=25,
                               fill_with_char=" ",
                               before_text=" ",
                               align=AlignTypes.CENTER),
    ]


def a_information_footer():
    return [
        ReportColumnDefinition(width=38,
                               fill_with_char="",
                               before_text="",
                               align=AlignTypes.CENTER),
    ]
