package com.gridnine.xtrip.server.reports.templates

import java.util.Collection
import java.util.LinkedHashMap

import com.gridnine.xtrip.common.model.booking.archive.ArchivedProductIndex
import com.gridnine.xtrip.common.model.booking.archive.ArchivedBookingHelper
import com.gridnine.xtrip.common.model.booking.vip.VipClientSitePriceStructure
import com.gridnine.xtrip.common.model.booking.BaseProduct
import com.gridnine.xtrip.common.model.booking.Traveller;
import com.gridnine.xtrip.common.model.entity.EntityStorage
import com.gridnine.xtrip.common.model.booking.BookingFile
import com.gridnine.xtrip.common.Environment
import com.gridnine.xtrip.common.model.handlers.HandlersRegistry
import com.gridnine.xtrip.common.model.handlers.ProductHandler
import com.gridnine.xtrip.common.vip.CommonVIPHelper
import com.gridnine.xtrip.common.model.helpers.BookingHelper
import com.gridnine.xtrip.common.model.system.Document
import com.gridnine.xtrip.common.model.system.BasicDocumentIndex
import com.gridnine.xtrip.common.model.system.BaseFileDocument
import com.gridnine.xtrip.common.model.booking.railway.RailwayProduct
import com.gridnine.xtrip.common.model.Xeption
import com.gridnine.xtrip.common.search.SearchCriteria
import com.gridnine.xtrip.common.search.SearchCriterion
import com.gridnine.xtrip.common.search.SearchQuery
import com.gridnine.xtrip.common.model.system.DocumentType
import com.gridnine.xtrip.common.model.system.ContentType

import java.util.Date
import java.util.List
import java.text.SimpleDateFormat
import java.io.ByteArrayOutputStream;
import java.math.BigDecimal

import com.gridnine.xtrip.common.model.profile.Organization
import com.gridnine.xtrip.common.l10n.model.L10nString
import com.gridnine.xtrip.common.l10n.model.L10nStringHelper
import com.gridnine.xtrip.common.util.LocaleUtil
import com.gridnine.xtrip.common.model.EntityContainer

import java.awt.image.BufferedImage

import javax.imageio.ImageIO

import org.apache.poi.ss.usermodel.Workbook
import org.jpedal.PdfDecoderServer

import groovy.transform.Field

import com.google.common.base.Joiner
import com.gridnine.xtrip.server.reports.prepare.template.common.renderer.excel.Excel2007Builder

@Field int TITLE_ROW_INDEX = 0, TITLE_COLUMN_INDEX = 0
@Field int CLIENT_NAME_ROW_INDEX = 1, CLIENT_NAME_COLUMN_INDEX = 0
@Field int FIRST_PRODUCT_ROW_INDEX = 3

@Field float PDF_SCALE_FACTOR = 96F/72F * 0.95
@Field int LEFT_BORDER = 26
@Field int RIGHT_BORDER = 33
@Field int TOP_BORDER = 13
@Field int BOTTOM_BORDER = 85


createStyle(name: 'title', h_alignment: 'CENTER', v_alignment: 'CENTER', fontBold : true, fontHeight : 12)
createStyle(name: 'client', h_alignment: 'CENTER', v_alignment: 'CENTER', fontBold : false, fontHeight : 12)
createStyle(name: 'textData', h_alignment: 'CENTER', v_alignment: 'CENTER', leftBorder:'THIN', rightBorder:'THIN', topBorder:'THIN', bottomBorder:'THIN', 'wrapText':true)
createStyle(name: 'numberData', parent: 'textData', h_alignment: 'RIGHT')

class ProductStruct {
    ArchivedProductIndex productIndex
    BaseProduct product
    //VipClientSitePriceStructure priceStructure
    ProductHandler productHandler
    EntityContainer<BaseFileDocument> travelDocumentCnt
    
    BaseFileDocument getTravelDocument() {
        return travelDocumentCnt?.entity
    }
}

EntityContainer<BaseFileDocument> getRailwayTravelDocument(RailwayProduct product, EntityContainer<BookingFile> bookingCnt) {
    SearchQuery query = new SearchQuery()
    query.criteria.criterions.add(SearchCriterion.eq(BasicDocumentIndex.Property.owner.name(), bookingCnt.toReference()))
    query.criteria.criterions.add(SearchCriterion.eq(BasicDocumentIndex.Property.type.name(), DocumentType.TRAVELDOCUMENT))
    query.criteria.criterions.add(SearchCriterion.eq(BasicDocumentIndex.Property.contentType.name(), ContentType.PDF))
    for (BasicDocumentIndex documentIndex : EntityStorage.get().search(BasicDocumentIndex.class, query).data) {
        EntityContainer<BaseFileDocument> documentCnt = EntityStorage.get().resolve(documentIndex.source)
        BaseFileDocument document = documentCnt.entity
        if (document.navigationKey == product.reservation.uid) {
            return documentCnt
        }
    }
    return null
}

ProductStruct getProductStruct(ArchivedProductIndex productIndex) {
    def archivedCnt = EntityStorage.get().resolve(productIndex.source)
    if (archivedCnt == null) {
        return null
    }
    EntityContainer<BookingFile> bookingCnt
    if (archivedCnt.entity instanceof BookingFile) {
        bookingCnt = archivedCnt
    } else {
        bookingCnt = ArchivedBookingHelper.getBookingContainer(archivedCnt.entity)
    }
    if (bookingCnt == null) {
        return null
    }
    BookingFile booking = bookingCnt.entity
    BaseProduct product = BookingHelper.findProductByUid(productIndex.getNavigationKey(), booking)
    ProductStruct result = new ProductStruct()
    result.product = product
    result.productIndex = productIndex
    result.productHandler = Environment.getPublished(HandlersRegistry.class).findProductHandler(product.getClass())
    if (product instanceof RailwayProduct) {
        result.travelDocumentCnt = getRailwayTravelDocument(product, bookingCnt)
    } else {
        throw Xeption.forDeveloper("invalid product: ${product}")
    }
    return result
}

@Field Joiner commaJoiner = Joiner.on(', ');
String getProductNumber(ProductStruct productStruct) {
    List<String> productNumbers = productStruct.productHandler.getProductNumbers(productStruct.product)
    if (productNumbers == null || productNumbers.empty) {
        return null
    }
    return commaJoiner.join(productNumbers)
}

String getTraveller(ProductStruct productStruct) {
    Collection<Traveller> travellers = productStruct.productHandler.getTravellers(productStruct.product)
    if (travellers == null || travellers.empty) {
        return null
    }
    List<String> travellerNames = []
    for (Traveller traveller : travellers) {
        travellerNames.add(traveller.name)
    }
    return commaJoiner.join(travellerNames)
}

@Field SimpleDateFormat dateFormat = new SimpleDateFormat('dd.MM.yyyy')
String getIssueDate(ProductStruct productStruct) {
    Date issueDate = productStruct.productHandler.findIssueDate(productStruct.product)
    if (issueDate == null) {
        return null
    }
    return dateFormat.format(issueDate)
}

BigDecimal getPrice(ProductStruct productStruct) {
    BaseProduct product = productStruct.product
    if (product instanceof RailwayProduct) {
        RailwayProduct railwayProduct = product
        return railwayProduct.equivalentFare
    } else {
        throw Xeption.forDeveloper("invalid product: ${product}")
    }
}

BigDecimal getVat(ProductStruct productStruct) {
    BaseProduct product = productStruct.product
    if (product instanceof RailwayProduct) {
        RailwayProduct railwayProduct = product
        return railwayProduct.equivalentVAT
    } else {
        throw Xeption.forDeveloper("invalid product: ${product}")
    }
}

@Field int productCounter = 0
def printProductInfo(ProductStruct productStruct) {
    nextRow()
    rowAutoHeight()
    
    number(++productCounter, 'numberData')
    
    nextColumn()
    text(null, 'textData')
    
    nextColumn()
    text(getProductNumber(productStruct), 'textData')
    
    nextColumn()
    text(getTraveller(productStruct), 'textData')
    
    nextColumn()
    text(getIssueDate(productStruct), 'textData')
    
    nextColumn()
    number(getPrice(productStruct), 'numberData')
    
    nextColumn()
    number(getVat(productStruct), 'numberData')
}

String getTitle() {
    return "Реест билетов с ${dateFormat.format(parameters.params.periodBegin)} по ${dateFormat.format(parameters.params.periodEnd)}"
}

String getRuValue(L10nString str) {
    return L10nStringHelper.getValue(str, LocaleUtil.LOCALE_RU, false)
}

String getClient() {
    Organization client = EntityStorage.get().resolve(parameters['CLIENT'])?.entity
    if (client == null) {
        warn ('client not found')
        return null
    }
    String name
    if (client.fullName != null) {
        name = getRuValue(client.fullName)
    } else if (client.shortName != null) {
        name = getRuValue(client.shortName)
    } else {
        name = client.code
    }
    String form = client.legalForm?.toString(LocaleUtil.LOCALE_RU)
    if (form == null) {
        return name
    } else {
        return "${form} \"${name}\""
    }
}

@Field PdfDecoderServer pdfDecoder = new PdfDecoderServer(false);
pdfDecoder.setPageParameters(PDF_SCALE_FACTOR, 1)

List<byte[]> getTravelDocumentPng(ProductStruct productStruct) {
    if (productStruct.travelDocumentCnt == null) {
        return null
    }
    byte[] pdfContent = productStruct.travelDocument.content
    pdfDecoder.openPdfArray(pdfContent)
    int pageCount = pdfDecoder.getPageCount()
    List<byte[]> result = new ArrayList<byte[]>(pageCount)
    for (int i = 1; i <= pageCount; i++) {
        pdfDecoder.decodePage(i);
        BufferedImage bufferedImage = pdfDecoder.getPageAsTransparentImage(i)
        bufferedImage = bufferedImage.getSubimage(LEFT_BORDER, TOP_BORDER,
            bufferedImage.width - RIGHT_BORDER - LEFT_BORDER,
            bufferedImage.height - BOTTOM_BORDER - TOP_BORDER);
        ByteArrayOutputStream output = new ByteArrayOutputStream()
        ImageIO.write(bufferedImage, "png", output)
        result.add(output.toByteArray())
    }
    return result
}

///////////////////////////////////////////////////

switchPage('реестр билетов')

moveTo(TITLE_ROW_INDEX, TITLE_COLUMN_INDEX)
text(getTitle(), 'title')

moveTo(CLIENT_NAME_ROW_INDEX, CLIENT_NAME_COLUMN_INDEX)
text(getClient(), 'client')

moveTo(FIRST_PRODUCT_ROW_INDEX - 1, 0)

LinkedHashMap<String, List<byte[]>> travelDocumentPngs = new LinkedHashMap()

allTickets.each { ArchivedProductIndex productIndex ->
    ProductStruct productStruct = getProductStruct(productIndex)
    printProductInfo(productStruct)
    if (productStruct.travelDocumentCnt != null && !travelDocumentPngs.containsKey(productStruct.travelDocumentCnt.uid)) {
        travelDocumentPngs.put(productStruct.travelDocumentCnt.uid, getTravelDocumentPng(productStruct))
    }
}

nextRow()
rowBreak()
for (List<byte[]> travelDocumentPngList : travelDocumentPngs.values()) {
    for (byte[] travelDocumentPng : travelDocumentPngList) {
        nextRow()
        int pictureId = addPicture(travelDocumentPng, Excel2007Builder.PictureFormatType.PNG)
        moveTo(getPictureRightBottom(pictureId))
        rowBreak()
    }
}