/*
 * MIT License
 *
 * Copyright (c) 2018-2019 Daniel Cortes
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package danielcortes.xyz.informes;

import danielcortes.xyz.models.caja.SQLiteCajaDAO;
import danielcortes.xyz.models.informes.egresos.InformeEgresosContent;
import danielcortes.xyz.models.informes.egresos.SQLiteInformeEgresosContentDAO;
import danielcortes.xyz.utils.Pair;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.PropertyTemplate;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

public class InformeEgresos {
    private final String[] titles = {
            "FECHA",
            "N°",
            "DESCRIPCION",
            "VALOR"
    };

    private List<InformeEgresosContent> informe;
    private Path saveFile;

    //Filas donde se almacenaran los totales
    private ArrayList<Row> totalRows;
    //Filas donde se almacenaran los egresos.
    private ArrayList<Row> dataRows;
    //Fila que contiene el total final;
    private Row totalFinal;


    //Rango que corresponde a un dia de egresos;
    private ArrayList<Pair<Integer, Integer>> totalRange;
    private Workbook wb;
    private Sheet sheet;
    private CreationHelper createHelper;
    private HashMap<String, CellStyle> styles;

    public InformeEgresos(int tipoEgresoId, LocalDate date, Path saveFile) {
        new SQLiteCajaDAO().createCajasForMonth(date);
        this.informe = new SQLiteInformeEgresosContentDAO().getInformeEgresosFactuasMateriaPrima(date, tipoEgresoId);
        this.saveFile = saveFile;

        this.wb = new HSSFWorkbook();
        this.sheet = wb.createSheet();
        this.createHelper = wb.getCreationHelper();

        this.totalRows = new ArrayList<>();
        this.totalRange = new ArrayList<>();
        this.dataRows = new ArrayList<>();

        this.styles = this.generateStyles();
    }

    private void fillHeaders() {
        Row titles = sheet.createRow(0);

        for (int x = 0; x < this.titles.length; x++) {
            titles.createCell(x).setCellValue(this.titles[x]);
        }
    }

    private void fillData() {
        int rowCounter = 1;
        int dayStart = rowCounter;
        int dayEnd;

        for (int informeID = 0; informeID < informe.size(); informeID++) {
            InformeEgresosContent data = informe.get(informeID);
            int cellCounter = 0;
            Row dataRow = sheet.createRow(rowCounter);
            dataRows.add(dataRow);
            Date fecha = Date.from(data.getFecha().atStartOfDay(ZoneId.systemDefault()).toInstant());

            dataRow.createCell(cellCounter++).setCellValue(fecha);
            dataRow.createCell(cellCounter++).setCellValue(data.getNro());
            dataRow.createCell(cellCounter++).setCellValue(data.getDescripcion());
            dataRow.createCell(cellCounter).setCellValue(data.getValor());

            // Comprueba si es que el siguiente elemento en la lista de informe es del siguiente dia, si es asi, agrega una linea con los totales diarios.
            if (informeID + 1 >= informe.size() || !data.getFecha().equals(informe.get(informeID + 1).getFecha())) {
                totalRows.add(sheet.createRow(rowCounter + 1));

                dayEnd = rowCounter;
                totalRange.add(new Pair<>(dayStart, dayEnd));
                dayStart = rowCounter + 2;
                rowCounter++;
            }

            rowCounter++;
        }
    }

    private void fillTotales() {
        StringBuilder sumTotalFinal = new StringBuilder();

        //Se aprovechara la iteracion para agregar los totales individuales y para construir la suma del total final
        for (int x = 0; x < totalRows.size(); x++) {
            Row row = totalRows.get(x);
            Pair<Integer, Integer> range = totalRange.get(x);

            row.createCell(2).setCellValue("TOTAL DIARIO");
            row.createCell(3).setCellFormula("SUM(D" + (range.getLeft() + 1) + ":D" + (range.getRight() + 1) + ")");

            sumTotalFinal.append("D").append(row.getRowNum() + 1);
            if (x + 1 != totalRows.size()) {
                sumTotalFinal.append("+");
            } else {
                this.totalFinal = sheet.createRow(row.getRowNum() + 1);
                this.totalFinal.createCell(2).setCellValue("TOTAL MENSUAL");
                this.totalFinal.createCell(3).setCellFormula(sumTotalFinal.toString());
            }
        }


    }

    private void freezeCells() {
        this.sheet.createFreezePane(0, 1);
    }

    private void addBorders() {
        int rows = this.totalRows.size() + this.dataRows.size() + 1;
        PropertyTemplate pt = new PropertyTemplate();

        //Bordes Internos
        pt.drawBorders(new CellRangeAddress(0, rows, 0, 3), BorderStyle.THIN, BorderExtent.ALL);

        //Bordes de los Headers
        pt.drawBorders(new CellRangeAddress(0, 0, 0, 3), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

        //Bordes de los Totales
        for (Row row : this.totalRows) {
            int index = row.getRowNum();
            pt.drawBorders(new CellRangeAddress(index, index, 0, 3), BorderStyle.NONE, BorderExtent.ALL);
            pt.drawBorders(new CellRangeAddress(index, index, 0, 3), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
        }

        //Borde del Total Final
        pt.drawBorders(new CellRangeAddress(this.totalFinal.getRowNum(), this.totalFinal.getRowNum(), 0, 3), BorderStyle.NONE, BorderExtent.ALL);
        pt.drawBorders(new CellRangeAddress(this.totalFinal.getRowNum(), this.totalFinal.getRowNum(), 0, 3), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

        //Borde externo
        pt.drawBorders(new CellRangeAddress(0, rows, 0, 3), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

        pt.applyBorders(this.sheet);
    }

    private void setStyles() {
        //Estilo para el header
        this.sheet.getRow(0).getCell(0).setCellStyle(this.styles.get("header"));
        this.sheet.getRow(0).getCell(1).setCellStyle(this.styles.get("header"));
        this.sheet.getRow(0).getCell(2).setCellStyle(this.styles.get("header"));
        this.sheet.getRow(0).getCell(3).setCellStyle(this.styles.get("header"));

        //Estilo para el Total Final
        this.totalFinal.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("gray"));
        this.totalFinal.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("gray"));
        this.totalFinal.getCell(2, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("gray"));
        this.totalFinal.getCell(3).setCellStyle(this.styles.get("total_final"));

        //Estilo para las filas de datos
        for (Row row : this.dataRows) {
            row.getCell(0).setCellStyle(this.styles.get("date"));
            row.getCell(1).setCellStyle(this.styles.get("regular"));
            row.getCell(2).setCellStyle(this.styles.get("regular"));
            row.getCell(3).setCellStyle(this.styles.get("money"));
        }

        //Estilo para las filas de totales
        for (Row row : this.totalRows) {
            row.getCell(0, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("not_so_gray"));
            row.getCell(1, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("not_so_gray"));
            row.getCell(2, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("not_so_gray"));
            row.getCell(3, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK).setCellStyle(this.styles.get("total"));
        }


        //Setea el alto de las filas
        this.sheet.getRow(0).setHeightInPoints(30);
        this.totalFinal.setHeightInPoints(20);
        for (Row row : this.dataRows) {
            row.setHeightInPoints(15);
        }
        for (Row row : this.totalRows) {
            row.setHeightInPoints(18);
        }


        //Setea el ancho de las columnas
        sheet.autoSizeColumn(0);
        sheet.autoSizeColumn(1);
        sheet.autoSizeColumn(2);
        sheet.autoSizeColumn(3);
    }

    private HashMap<String, CellStyle> generateStyles() {
        Font font = this.wb.createFont();
        font.setBold(true);
        font.setColor(IndexedColors.WHITE.getIndex());

        CellStyle regularStyle = this.wb.createCellStyle();

        CellStyle grayStyle = this.wb.createCellStyle();
        grayStyle.setFont(font);
        grayStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        grayStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.getIndex());
        grayStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        CellStyle notSoGrayStyle = this.wb.createCellStyle();
        notSoGrayStyle.setFont(font);
        notSoGrayStyle.setVerticalAlignment(VerticalAlignment.CENTER);
        notSoGrayStyle.setFillForegroundColor(IndexedColors.GREY_40_PERCENT.getIndex());
        notSoGrayStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        CellStyle dateStyle = this.wb.createCellStyle();
        dateStyle.setDataFormat(this.createHelper.createDataFormat().getFormat("dd/mm/yyyy"));

        CellStyle moneyStyle = this.wb.createCellStyle();
        moneyStyle.setDataFormat(this.createHelper.createDataFormat().getFormat("\"$\"#,##0_);(\"$\"#,##0)"));

        CellStyle headerStyle = this.wb.createCellStyle();
        headerStyle.cloneStyleFrom(grayStyle);
        headerStyle.setAlignment(HorizontalAlignment.CENTER);

        CellStyle totalStyle = this.wb.createCellStyle();
        totalStyle.cloneStyleFrom(notSoGrayStyle);
        totalStyle.setDataFormat(this.createHelper.createDataFormat().getFormat("\"$\"#,##0_);(\"$\"#,##0)"));

        CellStyle totalFinalStyle = this.wb.createCellStyle();
        totalFinalStyle.cloneStyleFrom(grayStyle);
        totalFinalStyle.setDataFormat(this.createHelper.createDataFormat().getFormat("\"$\"#,##0_);(\"$\"#,##0)"));

        HashMap<String, CellStyle> styles = new HashMap<>();
        styles.put("regular", regularStyle);
        styles.put("gray", grayStyle);
        styles.put("not_so_gray", notSoGrayStyle);
        styles.put("date", dateStyle);
        styles.put("money", moneyStyle);
        styles.put("header", headerStyle);
        styles.put("total", totalStyle);
        styles.put("total_final", totalFinalStyle);

        return styles;
    }

    public Path generarInforme() {
        fillHeaders();
        fillData();
        fillTotales();
        freezeCells();
        setStyles();
        addBorders();

        try (OutputStream fileOut = Files.newOutputStream(this.saveFile)) {
            wb.write(fileOut);
            return this.saveFile;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
