import com.gridnine.xtrip.common.l10n.model.LocaleHelper
import com.gridnine.xtrip.common.model.EntityContainer
import com.gridnine.xtrip.common.model.EntityReference
import com.gridnine.xtrip.common.model.booking.OperationBatch
import com.gridnine.xtrip.common.model.booking.agencymemo.AgencyMemoProductType
import com.gridnine.xtrip.common.model.dict.AddressType
import com.gridnine.xtrip.common.model.dict.ContractType
import com.gridnine.xtrip.common.model.dict.CurrencyInfoReference
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.entity.EntityStorageHelper
import com.gridnine.xtrip.common.model.finance.*
import com.gridnine.xtrip.common.model.helpers.DictHelper
import com.gridnine.xtrip.common.model.helpers.FinanceHelper
import com.gridnine.xtrip.common.model.helpers.ProfileHelper
import com.gridnine.xtrip.common.model.profile.Address
import com.gridnine.xtrip.common.model.profile.ContractIndex
import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicket
import com.gridnine.xtrip.common.reports.model.AirTicketsTemplateReportTicketType
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.search.SortOrder
import com.gridnine.xtrip.common.util.MiscUtil
import com.gridnine.xtrip.common.util.TextUtil
import com.gridnine.xtrip.common.util.ValueHolder

import java.text.SimpleDateFormat

createStyle(name: "left", h_alignment: "LEFT", fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "left-bold", h_alignment: "LEFT", fontBold: true, fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center", h_alignment: "CENTER", fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center-bold-bottom-border", h_alignment: "CENTER", fontBold: true, bottomBorder: 'THIN', fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center-grey", h_alignment: "CENTER", fontColor: 'GREY_25_PERCENT', fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center-grey-border", h_alignment: "CENTER", v_alignment: "CENTER", foreground: 'GREY_25_PERCENT', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center-bold-grey-border", h_alignment: "CENTER", v_alignment: "CENTER", fontBold: true, foreground: 'GREY_25_PERCENT', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10)
createStyle(name: "center-bold-blue-border", h_alignment: "CENTER", fontBold: true, foreground: 'PALE_BLUE', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "center-bold-border-thick", h_alignment: "CENTER", fontBold: true, topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "center-border-thin", h_alignment: "CENTER", topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "center-border-thin-top", h_alignment: "CENTER", rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "center-border-thin-bottom", h_alignment: "CENTER", rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-bold-border-thick", h_alignment: "LEFT", fontBold: true, topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-border-thin", h_alignment: "LEFT", topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-border-thin-top", h_alignment: "LEFT", rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-border-thin-bottom", h_alignment: "LEFT", topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-bold-blue-border", h_alignment: "LEFT", fontBold: true, foreground: 'PALE_BLUE', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-border-thin", h_alignment: "RIGHT", format: '#,##0.00', topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-border-thin-top", h_alignment: "RIGHT", format: '#,##0.00', topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-border-thin-bottom", h_alignment: "RIGHT", format: '#,##0.00', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THIN', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-bold-border-thick", h_alignment: "RIGHT", format: '#,##0.00', fontBold: true, topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-bold-blue-border-thick", h_alignment: "RIGHT", format: '#,##0.00', fontBold: true, foreground: 'PALE_BLUE', topBorder: 'THICK', rightBorder: 'THICK', leftBorder: 'THICK', bottomBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "right-numeric-yellow-red-border-thin", h_alignment: "RIGHT", format: '#,##0.00', fontColor: 'RED', foreground: 'YELLOW', topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")
createStyle(name: "left-yellow-border-thin", h_alignment: "left", foreground: 'YELLOW', topBorder: 'THIN', rightBorder: 'THICK', leftBorder: 'THICK', wrapText: true, fontFamily: "TimesNewRoman", fontHeight: 10, v_alignment: "CENTER")

def fillSheet() {
    ValueHolder<String> currencyHolder = new ValueHolder<>("")
    fillHeader(currencyHolder)
    fillTable(currencyHolder)
    fillSignature()
}

def fillHeader(ValueHolder<String> currencyHolder) {
    rowHeight(14)
    nextColumn()
    EntityReference<Organization> agency = parameters['key-report-params']?.agency
    EntityReference<Organization> supplier = parameters['key-report-params']?.supplier
    String top = "PAYMENT SUMMARY № "
    String name = "Name of Agent: "
    String currency = "Currency: "
    if (agency && supplier) {
        SearchQuery query = new SearchQuery()
        query.getCriteria().getCriterions().add(
                SearchCriterion.eq(ContractIndex.Property.supplier.name(), supplier))
        query.getCriteria().getCriterions().add(
                SearchCriterion.eq(ContractIndex.Property.customer.name(), agency))
        query.getCriteria().getCriterions().add(
                SearchCriterion.eq(ContractIndex.Property.disabled.name(), false))
        query.getCriteria().getCriterions()
                .add(SearchCriterion.or(
                        SearchCriterion.eq(ContractIndex.Property.endDate.name(), null),
                        SearchCriterion.ge(ContractIndex.Property.endDate.name(), new Date())))
        query.getCriteria().getOrders().put(
                ContractIndex.Property.defaultContract.name(), SortOrder.DESC)
        List<ContractIndex> contractIndices =
                EntityStorage.get().search(ContractIndex.class, query).getData()
        if (!contractIndices.isEmpty()) {
            if (TextUtil.nonBlank(contractIndices.get(0).getNumber())) {
                top = top.concat(contractIndices.get(0).getNumber())
            }
            String curr
            if (TextUtil.nonBlank(contractIndices.get(0).getPaymentCurrency())) {
                curr = contractIndices.get(0).getPaymentCurrency()
            } else {
                curr = DictHelper.getLocalCurrencyCode()
            }
            currency = currency.concat(curr)
            currencyHolder.setValue(curr)
        }
    }
    text(top, "left-bold", 1, 1)
    3.times {
        nextRow()
    }
    nextColumn()
    String iata = "IATA Code: "
    String location = ""
    if (agency) {
        Organization organization = EntityStorage.get().resolve(agency)?.getEntity()
        if (organization) {
            name = name.concat(ProfileHelper.getFullName(
                    organization, LocaleHelper.EN_LOCALE, false))
            //TODO iata after IBECORP-6061
            Address address = organization.getAddresses().find { a ->
                a.getAddressType() == AddressType.LEGAL
            }
            if (address) {
                String city = address.getCity() ? DictHelper.getTranslation(address.getCity(), LocaleHelper.EN_LOCALE, false) : ""
                String street = address.getHouse() ?
                        address.getAddress() ?
                                address.getAddress().toString(LocaleHelper.EN_LOCALE).concat(" ")
                                        .concat(address.getHouse().toString(LocaleHelper.EN_LOCALE)) :
                                "" :
                        address.getAddress() ?
                                address.getAddress().toString(LocaleHelper.EN_LOCALE) :
                                address.getHouse().toString(LocaleHelper.EN_LOCALE)
                location = city.concat(", ").concat(street)
            }
        }
    }
    text(name, "left-bold", 1, 1)
    nextColumn()
    text("Period:", "left", 1, 1)
    nextColumn()
    SimpleDateFormat sdfPeriod = new SimpleDateFormat("ddMMMyy", LocaleHelper.EN_LOCALE)
    Date fromDate = parameters['key-report-params']?.periodBegin
    String from = fromDate ? sdfPeriod.format(fromDate).toUpperCase() : ""
    Date toDate = parameters['key-report-params']?.periodEnd
    String to = toDate ? sdfPeriod.format(toDate).toUpperCase() : ""
    text(String.format("%s-%s", from, to), "center-bold-bottom-border", 1, 1)
    nextRow()
    nextColumn()
    text(iata, "left-bold", 1, 1)
    nextColumn()
    text("Date:", "left", 1, 1)
    nextColumn()
    SimpleDateFormat sdfToday = new SimpleDateFormat("M/d/yyyy")
    text(sdfToday.format(new Date()), "center", 1, 1)
    nextRow()
    nextColumn()
    text(currency, "left", 1, 1)
    nextColumn()
    text("Location:", "left", 1, 1)
    nextColumn()
    text(location, "left", 1, 1)
    2.times {
        nextRow()
    }
}

def fillTable(ValueHolder<String> currencyHolder) {
    ValueHolder<BigDecimal> balanceHolder = new ValueHolder<>(BigDecimal.ZERO)
    fillTableHeader()
    rowHeight(14)
    fillIncomingBalance(currencyHolder, balanceHolder)
    fillFirstBlock()
    fillSecondBlock()
    fillThirdBlock()
    nextRow()
    fillFourthBlock()
    fillFifthBlock()
    fillSixthBlock()
    nextRow()
    fillSeventhBlock()
    nextRow()
    fillEighthBlock()
    nextRow()
    fillNinthBlock()
    nextRow()
    fillTenthBlock()
    nextRow()
    fillEleventhBlock()
    fillOutgoingBalance(balanceHolder)
    nextRow()
}

def fillTableHeader() {
    2.times {
        nextColumn()
    }
    text("A (column)", "center-grey", 1, 1)
    nextColumn()
    text("B (column)", "center-grey", 1, 1)
    nextRow()
    rowHeight(24)
    Organization supplier = (Organization) EntityStorage.get().resolve(parameters['key-report-params']?.supplier)?.getEntity()
    fillRow("№", "", "Amount\ndue to ".concat(supplier?.getFullName()?.toString(LocaleHelper.EN_LOCALE)),
            "Amount\ndue to Agent", "center-grey-border",
            "center-grey-border", "center-bold-grey-border",
            "center-bold-grey-border")
}

def fillIncomingBalance(ValueHolder<String> currencyHolder, ValueHolder<BigDecimal> balanceHolder) {
    BigDecimal balance = BigDecimal.ZERO
    EntityReference<Organization> agency = parameters['key-report-params']?.agency
    EntityReference<Organization> supplier = parameters['key-report-params']?.supplier
    Date fromDate = parameters['key-report-params']?.periodBegin
    Date toDate = parameters['key-report-params']?.periodEnd
    EntityReference<ChartOfAccountsSettings> chartOfAccountsSettings =
            FinanceHelper.getChartOfAccountsSettings(agency)

    if (chartOfAccountsSettings != null) {
        balance = calculateBalance(chartOfAccountsSettings, agency, supplier, balanceHolder, ChartOfAccountsElementType.SUPPLIER, currencyHolder, fromDate, toDate)
        balanceHolder.setValue(balanceHolder.getValue() ? balanceHolder.getValue().negate() : BigDecimal.ZERO)
        balance = balance ? MiscUtil.sum(balance.negate(), calculateBalance(chartOfAccountsSettings, agency, supplier, balanceHolder, ChartOfAccountsElementType.SUPPLIER_COMMISSION, currencyHolder, fromDate, toDate)) :
                calculateBalance(chartOfAccountsSettings, agency, supplier, balanceHolder, ChartOfAccountsElementType.SUPPLIER_COMMISSION, currencyHolder, fromDate, toDate)
    }
    fillRow("", "Incoming Balance", balance, "",
            "center-bold-border-thick", "left-bold-border-thick",
            "right-numeric-bold-border-thick", "right-numeric-bold-border-thick")
}

def fillFirstBlock() {
    Map<DataType, BigDecimal> dataSell = getSalesOrRefundsData(OperationBatch.SELL)
    fillBlueTwoFormulaeRow("1.", "ET Sales Total:",
            "${cellIndex(1, 2)}+${cellIndex(3, 2)}+${cellIndex(4, 2)}+${cellIndex(5, 2)}",
            "${cellIndex(6, 3)}+${cellIndex(7, 3)}")
    fillTopRow("1.1", "Fare (Query Report)",
            dataSell.get(DataType.FARE), "")
    fillMiddleRow("1.2", "Total Tax",
            MiscUtil.sum(dataSell.get(DataType.YQ_TAX),
                    dataSell.get(DataType.OTHER_TAX)), "")
    fillMiddleRow("1.3", "YQ Tax",
            dataSell.get(DataType.YQ_TAX), "")
    fillMiddleRow("1.4", "Other Tax",
            dataSell.get(DataType.OTHER_TAX), "")
    fillMiddleRow("1.5", "Penalty (Query Report)",
            dataSell.get(DataType.PENALTY_FEE), "")
    fillMiddleRow("1.6", "Agent Commission (Query Report)",
            "", dataSell.get(DataType.COMMISSION))
    fillRow("", "Incentive", "", getIncentive(),
            "left-yellow-border-thin", "left-yellow-border-thin",
            "right-numeric-yellow-red-border-thin",
            "right-numeric-yellow-red-border-thin")
}

def fillSecondBlock() {
    Map<DataType, BigDecimal> dataRefund = getSalesOrRefundsData(OperationBatch.REFUND)
    fillBlueTwoFormulaeRow("2.", "ET Refunds Total:",
            "${cellIndex(1, 2)}+${cellIndex(2, 2)}+${cellIndex(3, 2)}-${cellIndex(4, 2)}",
            "${cellIndex(5, 3)}")
    fillTopRow("2.1", "Fare (Query Report)",
            dataRefund.get(DataType.FARE), "")
    fillMiddleRow("2.2", "Other Tax",
            dataRefund.get(DataType.OTHER_TAX), "")
    fillMiddleRow("2.3", "YQ Tax",
            dataRefund.get(DataType.YQ_TAX), "")
    fillMiddleRow("2.4", "Refund Fee",
            dataRefund.get(DataType.PENALTY_FEE), "")
    fillBottomRow("2.5", "Agent Commission (Query Report)",
            "", dataRefund.get(DataType.COMMISSION))
}

def fillThirdBlock() {
    fillBlueTwoFormulaeRow("3", "ET Sales Gross",
            "${cellIndex(-14, 2)}-${cellIndex(-6, 2)}",
            "${cellIndex(-14, 3)}-${cellIndex(-6, 3)}")
}

def fillFourthBlock() {
    fillBlueRow("4", "Paper Ticket Sales Total:", "", "")
    fillTopRow("4.1", "Fare (TSR, MCO, EXB sales report)", "", "")
    fillMiddleRow("4.2", "Passenger Tax (TSR, MCO, Sales Report)", "", "")
    fillMiddleRow("4.3", "GB Tax (TSR, MCO Sales Report)", "", "")
    fillMiddleRow("4.4", "YQ Tax (TSR, MCO Sales Report)", "", "")
    fillMiddleRow("4.5", "Penalty (Agent Ticket MCO Sales Report, refund fee)", "", "")
    fillBottomRow("4.6", "Agent commission", "", "")
}

def fillFifthBlock() {
    fillBlueRow("5", "Paper Ticket Refund Total:", "", "")
    fillTopRow("5.1", "Fare (Ticket Refund Report)", "", "")
    fillMiddleRow("5.2", "Passenger Tax (Ticket Refund Report)", "", "")
    fillMiddleRow("5.3", "GB Tax (Ticket Refund Report)", "", "")
    fillMiddleRow("5.4", "YQ Tax (Ticket Refund Report)", "", "")
    fillBottomRow("5.5", "Agent commission", "", "")
}

def fillSixthBlock() {
    fillBlueRow("6", "Paper Ticket Sales Gross:", "", "")
}

def fillSeventhBlock() {
    Organization supplier = (Organization) EntityStorage.get().resolve(parameters['key-report-params']?.supplier)?.getEntity()
    fillBlueOneFormulaRow("7", "Net Amount Due to ".concat(supplier?.getFullName()?.toString(LocaleHelper.EN_LOCALE)),
            "${cellIndex(-17, 2)}-${cellIndex(-17, 3)}", "")
}

def fillEighthBlock() {
    List<MiscUtil.Pair<String, BigDecimal>> claims = getClaims()
    if (claims.isEmpty()) {
        fillBlueRow("8", "Claims", "", "")
    } else {
        fillBlueOneFormulaRow("8", "Claims",
                "SUM(${cellIndex(1, 2)}:${cellIndex(claims.size(), 2)})", "")
    }
    int row = 1
    claims.forEach { MiscUtil.Pair<String, BigDecimal> claim ->
        if (row == 1) {
            fillTopRow("8.".concat(String.valueOf(row++)), claim.getFirst(), claim.getSecond(), "")
            first = false
        } else if (row != claims.size()) {
            fillMiddleRow("8.".concat(String.valueOf(row++)), claim.getFirst(), claim.getSecond(), "")
        } else {
            fillBottomRow("8.".concat(String.valueOf(row++)), claim.getFirst(), claim.getSecond(), "")
        }
    }
}

def fillNinthBlock() {
    fillBlueOneFormulaRow("9", "Settlements:",
            "SUM(${cellIndex(1, 2)}:${cellIndex(3, 2)})", "")
    fillTopRow("9.1", "", "", "")
    fillMiddleRow("9.2", "", "", "")
    fillBottomRow("9.3", "", "", "")
}

def fillTenthBlock() {
    List<BasicPaymentDocumentIndex> paymentOrders = getPaymentOrders()
    if (paymentOrders.isEmpty()) {
        fillBlueRow("10", "Type of Payment", "", "")
    } else {
        fillBlueOneFormulaRow("10", "Type of Payment:",
                "SUM(${cellIndex(1, 2)}:${cellIndex(paymentOrders.size(), 2)})", "")
    }
    int row = 1
    paymentOrders.forEach { BasicPaymentDocumentIndex order ->
        if (row == 1) {
            fillTopRow("10.".concat(String.valueOf(row++)), buildTypeOfPaymentName(order),
                    order.getTotalAmount(), "")
            first = false
        } else if (row != paymentOrders.size()) {
            fillMiddleRow("10.".concat(String.valueOf(row++)), buildTypeOfPaymentName(order),
                    order.getTotalAmount(), "")
        } else {
            fillBottomRow("10.".concat(String.valueOf(row++)), buildTypeOfPaymentName(order),
                    order.getTotalAmount(), "")
        }
    }
}

def fillEleventhBlock() {
    fillBlueOneFormulaRow("11", "Payment Details:",
            "SUM(${cellIndex(1, 2)}:${cellIndex(3, 2)})", "")
    fillTopRow("11.1", "", "", "")
    fillMiddleRow("11.2", "", "", "")
    fillBottomRow("11.3", "", "", "")
}

def fillOutgoingBalance(ValueHolder<BigDecimal> balanceHolder) {
    fillRow("", "Outgoing Balance",
            balanceHolder.getValue(), "",
            "center-bold-border-thick", "left-bold-border-thick",
            "right-numeric-bold-border-thick",
            "right-numeric-bold-border-thick")
}

def fillSignature() {
    nextColumn()
    text("Signature of Agent    ______________________________", "left", 1, 1)
    2.times {
        nextRow()
    }
    nextColumn()
    text("Validation Stamp", "left", 1, 1)
}

def fillMiddleRow(String first, String second, BigDecimal third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin", "left-border-thin",
            "right-numeric-border-thin",
            "right-numeric-border-thin")
}

def fillMiddleRow(String first, String second, String third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin", "left-border-thin",
            "right-numeric-border-thin",
            "right-numeric-border-thin")
}

def fillMiddleRow(String first, String second, String third, BigDecimal fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin", "left-border-thin",
            "right-numeric-border-thin",
            "right-numeric-border-thin")
}

def fillTopRow(String first, String second, BigDecimal third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin", "left-border-thin",
            "right-numeric-border-thin-bottom",
            "right-numeric-border-thin-bottom")
}

def fillTopRow(String first, String second, String third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin", "left-border-thin",
            "right-numeric-border-thin-bottom",
            "right-numeric-border-thin-bottom")
}

def fillBottomRow(String first, String second, BigDecimal third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin-bottom", "left-border-thin-bottom",
            "right-numeric-border-thin-top",
            "right-numeric-border-thin-top")
}

def fillBottomRow(String first, String second, String third, BigDecimal fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin-bottom", "left-border-thin-bottom",
            "right-numeric-border-thin-top",
            "right-numeric-border-thin-top")
}

def fillBottomRow(String first, String second, String third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-border-thin-bottom", "left-border-thin-bottom",
            "right-numeric-border-thin-top",
            "right-numeric-border-thin-top")
}

def fillBlueRow(String first, String second, String third, String fourth) {
    fillRow(first, second, third, fourth,
            "center-bold-blue-border", "left-bold-blue-border",
            "right-numeric-bold-blue-border-thick",
            "right-numeric-bold-blue-border-thick")
}

def fillBlueOneFormulaRow(String first, String second, String third, String fourth) {
    fillOneFormulaRow(first, second, third, fourth,
            "center-bold-blue-border", "left-bold-blue-border",
            "right-numeric-bold-blue-border-thick",
            "right-numeric-bold-blue-border-thick")
}

def fillBlueTwoFormulaeRow(String first, String second, String third, String fourth) {
    fillTwoFormulaeRow(first, second, third, fourth,
            "center-bold-blue-border", "left-bold-blue-border",
            "right-numeric-bold-blue-border-thick",
            "right-numeric-bold-blue-border-thick")
}

def fillRow(String first, String second, BigDecimal third,
            String fourth, String firstStyle, String secondStyle,
            String thirdStyle, String fourthStyle) {
    fillFirstSecond(first, second, firstStyle, secondStyle)
    number(third, thirdStyle)
    nextColumn()
    text(fourth, fourthStyle, 1, 1)
    nextRow()
}

def fillRow(String first, String second, String third,
            BigDecimal fourth, String firstStyle, String secondStyle,
            String thirdStyle, String fourthStyle) {
    fillFirstSecond(first, second, firstStyle, secondStyle)
    text(third, thirdStyle, 1, 1)
    nextColumn()
    number(fourth, fourthStyle)
    nextRow()
}

def fillRow(String first, String second, String third,
            String fourth, String firstStyle, String secondStyle,
            String thirdStyle, String fourthStyle) {
    fillFirstSecond(first, second, firstStyle, secondStyle)
    text(third, thirdStyle, 1, 1)
    nextColumn()
    text(fourth, fourthStyle, 1, 1)
    nextRow()
}

def fillOneFormulaRow(String first, String second, String third,
                      String fourth, String firstStyle, String secondStyle,
                      String thirdStyle, String fourthStyle) {
    fillFirstSecond(first, second, firstStyle, secondStyle)
    formula(third, thirdStyle, 1, 1)
    nextColumn()
    text(fourth, fourthStyle)
    nextRow()
}

def fillTwoFormulaeRow(String first, String second, String third,
                       String fourth, String firstStyle, String secondStyle,
                       String thirdStyle, String fourthStyle) {
    fillFirstSecond(first, second, firstStyle, secondStyle)
    formula(third, thirdStyle)
    nextColumn()
    formula(fourth, fourthStyle)
    nextRow()
}

def fillFirstSecond(String first, String second, String firstStyle,
                    String secondStyle) {
    text(first, firstStyle, 1, 1)
    nextColumn()
    text(second, secondStyle, 1, 1)
    nextColumn()
}

BigDecimal calculateBalance(EntityReference<ChartOfAccountsSettings> chartOfAccountsSettings,
                            EntityReference<Organization> agency, EntityReference<Organization> supplier,
                            ValueHolder<BigDecimal> balanceHolder, ChartOfAccountsElementType type,
                            ValueHolder<String> currencyHolder, Date fromDate, Date toDate) {
    EntityReference<ChartOfAccountsElement> supplierAccountsEl =
            FinanceHelper.getChartOfAccountsElement(agency,
                    chartOfAccountsSettings, type,
                    new CurrencyInfoReference(currencyHolder.getValue()))
    BigDecimal balance = BigDecimal.ZERO
    EntityContainer<ChartOfAccountsElement> accountsElementContainer =
            EntityStorage.get().resolve(supplierAccountsEl)
    if (!EntityStorageHelper.isEmpty(accountsElementContainer)) {
        if (accountsElementContainer.getEntity().getSettings() != null) {
            EntityContainer<ChartOfAccountsSettings> settingsContainer =
                    EntityStorage.get().resolve(accountsElementContainer.getEntity().getSettings())
            if (!EntityStorageHelper.isEmpty(settingsContainer)) {
                BasicChartOfAccountsElementSettings settings =
                        FinanceHelper.getChartOfAccountsElementSettings(settingsContainer.getEntity(),
                                accountsElementContainer.getEntity().getType())
                TransactionSplitIndex.Property indexProperty = null
                if ((settings.getDimension1() != null)
                        && (settings.getDimension1().getType() != null)) {
                    if (settings.getDimension1().getType()
                            .equals(DimensionType.ORGANIZATION)) {
                        indexProperty = TransactionSplitIndex.Property.dimension1;
                    }
                }
                if ((settings.getDimension2() != null)
                        && (settings.getDimension2().getType() != null)) {
                    if (settings.getDimension2().getType()
                            .equals(DimensionType.ORGANIZATION)) {
                        indexProperty = TransactionSplitIndex.Property.dimension2;
                    }
                }
                if ((settings.getDimension3() != null)
                        && (settings.getDimension3().getType() != null)) {
                    if (settings.getDimension3().getType()
                            .equals(DimensionType.ORGANIZATION)) {
                        indexProperty = TransactionSplitIndex.Property.dimension3;
                    }
                }
                Map<FinanceHelper.Dimension, String> dimensions = new HashMap<>()
                if ((indexProperty != null) && (supplier != null)) {
                    String value = FinanceHelper.getDimensionValue(
                            DimensionType.ORGANIZATION, supplier)
                    switch (indexProperty) {
                        case TransactionSplitIndex.Property.dimension1:
                            dimensions.put(FinanceHelper.Dimension.DIMENSION_1, value)
                            break
                        case TransactionSplitIndex.Property.dimension2:
                            dimensions.put(FinanceHelper.Dimension.DIMENSION_2, value)
                            break
                        case TransactionSplitIndex.Property.dimension3:
                            dimensions.put(FinanceHelper.Dimension.DIMENSION_3, value)
                            break
                    }
                }
                if (fromDate != null) {
                    balance = FinanceHelper.calculateBalance(supplierAccountsEl, null,
                            MiscUtil.getBeforeTime(fromDate), null, true,
                            dimensions, null)
                }
                if (toDate != null) {
                    balanceHolder.setValue(balanceHolder.getValue() ? MiscUtil.sum(balanceHolder.getValue(),
                            FinanceHelper.calculateBalance(supplierAccountsEl, null,
                                    toDate, null, true, dimensions, null)) :
                            FinanceHelper.calculateBalance(supplierAccountsEl, null,
                                    toDate, null, true, dimensions, null))
                }
            }
        }
    }
    return balance
}

def buildTypeOfPaymentName(BasicPaymentDocumentIndex index) {
    return String.format("Payment Order No %d dated %s", index.getNumber(),
            new SimpleDateFormat("dd MMM yyyy",
                    LocaleHelper.EN_LOCALE).format(index.getDate()).toUpperCase())
}

def getSalesOrRefundsData(OperationBatch... batches) {
    Map<DataType, BigDecimal> result = new HashMap<>()
    BigDecimal fare = BigDecimal.ZERO
    BigDecimal yq = BigDecimal.ZERO
    BigDecimal other = BigDecimal.ZERO
    BigDecimal penaltyOrFee = BigDecimal.ZERO
    BigDecimal commission = BigDecimal.ZERO
    tickets { AirTicketsTemplateReportTicket ticket ->
        if (batches.contains(ticket.getOperationBatch())) {
            fare = MiscUtil.sum(fare, ticket.getEquivalentFare())
            yq = MiscUtil.sum(yq,
                    (BigDecimal) ticket.getTaxes().findAll { t ->
                        t.getCode() == "YQ" && t.getEquivalentAmount() != null
                    }.collect { t ->
                        t.getEquivalentAmount()
                    }.sum(BigDecimal.ZERO))
            other = MiscUtil.sum(other,
                    (BigDecimal) ticket.getTaxes().findAll { t ->
                        t.getCode() != "YQ" && t.getEquivalentAmount() != null
                    }.collect { t ->
                        t.getEquivalentAmount()
                    }.sum(BigDecimal.ZERO))
            if (ticket.getOperationBatch() == OperationBatch.REFUND) {
                penaltyOrFee = MiscUtil.sum(penaltyOrFee, ticket.getClientFeeValue())
            } else {
                penaltyOrFee = MiscUtil.sum(
                        penaltyOrFee, ticket.getPenalty(), ticket.getPenaltyVPD())
            }
            commission = MiscUtil.sum(commission,
                    (BigDecimal) ticket.getCommissions().findAll { c ->
                        c.getContractType() == ContractType.VENDOR && c.getEquivalentAmount() != null
                    }.collect { c ->
                        c.getEquivalentAmount()
                    }.sum(BigDecimal.ZERO))
        }
    }
    result.put(DataType.FARE, fare)
    result.put(DataType.YQ_TAX, yq)
    result.put(DataType.OTHER_TAX, other)
    result.put(DataType.PENALTY_FEE, penaltyOrFee)
    result.put(DataType.COMMISSION, commission)
    return result
}

def getIncentive() {
    BigDecimal incentive = BigDecimal.ZERO
    tickets { AirTicketsTemplateReportTicket ticket ->
        if (ticket.getType() == AirTicketsTemplateReportTicketType.MEMO && ticket.getAgencyMemoProductType() == AgencyMemoProductType.ACM) {
            incentive = MiscUtil.sum(incentive, ticket.getAgencyMemoProductBonusEquivalentAmount())
        }
    }
    return incentive
}

def getClaims() {
    List<MiscUtil.Pair<String, BigDecimal>> claims = new ArrayList<>()
    tickets { AirTicketsTemplateReportTicket ticket ->
        if (ticket.getType() == AirTicketsTemplateReportTicketType.MEMO && ticket.getAgencyMemoProductType() == AgencyMemoProductType.ADM) {
            ticket.getAgencyMemoProductItemGroup().forEach { g ->
                claims.add(new MiscUtil.Pair<String, BigDecimal>(
                        g.getType().toString(LocaleHelper.EN_LOCALE), g.getEquivalentAmount()))
            }
        }
    }
    return claims
}

def getPaymentOrders() {
    EntityReference<Organization> agency = parameters['key-report-params']?.agency
    EntityReference<Organization> supplier = parameters['key-report-params']?.supplier
    Date fromDate = parameters['key-report-params']?.periodBegin
    Date toDate = parameters['key-report-params']?.periodEnd
    if (agency && supplier) {
        SearchQuery query = new SearchQuery()
        query.getCriteria().getCriterions().add(SearchCriterion.eq(
                BasicPaymentDocumentIndex.Property.organization.name(), supplier))
        query.getCriteria().getCriterions().add(SearchCriterion.eq(
                BasicPaymentDocumentIndex.Property.payer.name(), agency))
        query.getCriteria().getCriterions().add(SearchCriterion
                .ne(BasicPaymentDocumentIndex.Property.date.name(), null))
        query.getCriteria().getCriterions().add(SearchCriterion
                .ge(BasicPaymentDocumentIndex.Property.date.name(), fromDate))
        query.getCriteria().getCriterions().add(SearchCriterion
                .le(BasicPaymentDocumentIndex.Property.date.name(), toDate))
        return EntityStorage.get().search(BasicPaymentDocumentIndex.class, query).getData()
    }
    return new ArrayList<BasicPaymentDocumentIndex>()
}

def setWidth() {
    nextRow()
    columnWidth(9)
    nextColumn()
    columnWidth(43)
    nextColumn()
    columnWidth(20)
    nextColumn()
    columnWidth(25)
}

page { "PAYMENT SUMMARY" } {
    // Set fix width for amount of sheets
    fitWidth(1)

    // Set fix height for amount of sheets
    fitHeight(1)

    // Set portrait mode
    landscape(false)

    // Set narrow margins
    margin(0.25, 0.25, 0.25, 0.25)

    // Set scale
    scale(70)

    // Set preserve mode
    preserve(true)

    fillSheet()
    setWidth()
}

enum DataType {
    FARE,
    YQ_TAX,
    OTHER_TAX,
    PENALTY_FEE,
    COMMISSION,
}