/*
 * 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.egreso.Egreso;
import danielcortes.xyz.models.tipo_egreso.TipoEgreso;
import danielcortes.xyz.utils.Pair;
import java.time.LocalDate;
import java.time.YearMonth;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderExtent;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.VerticalAlignment;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.PropertyTemplate;

public class InformeEgresosToExcel {

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

  private InformeEgresos informe;
  private YearMonth mes;

  //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 InformeEgresosToExcel(TipoEgreso tipoEgreso, YearMonth mes) {
    this.informe = InformeEgresos.generate(mes, tipoEgreso);
    this.mes = mes;

    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 currentRow = 1;

    LocalDate currentDay = mes.atDay(1);
    LocalDate endDayPlusOne = mes.atEndOfMonth().plusDays(1);

    while (currentDay.isBefore(endDayPlusOne)) {
      int firstRow = currentRow;

      Date fecha = Date.from(currentDay.atStartOfDay(ZoneId.systemDefault()).toInstant());
      List<Egreso> egresos = informe.get(currentDay);

      for (Egreso egreso : egresos) {
        Row dataRow = sheet.createRow(currentRow);
        this.dataRows.add(dataRow);

        dataRow.createCell(0).setCellValue(fecha);
        dataRow.createCell(1).setCellValue(egreso.getNro());
        dataRow.createCell(2).setCellValue(egreso.getDescripcion());
        dataRow.createCell(3).setCellValue(egreso.getValor());

        currentRow++;
      }

      int lastRow = currentRow - 1;

      if (egresos.size() > 0) {
        Row totalRow = sheet.createRow(currentRow++);
        totalRows.add(totalRow);
        totalRow.createCell(2).setCellValue("TOTAL DIARIO");
        totalRow.createCell(3)
            .setCellFormula("SUM(D" + (firstRow + 1) + ":D" + (lastRow + 1) + ")");
      }

      currentDay = currentDay.plusDays(1);
    }
  }

  private void fillTotales() {
    String operation = totalRows.stream().map(row -> "D" + (row.getRowNum() + 1))
        .reduce((a, b) -> String.join("+", a, b)).get();
    this.totalFinal = sheet.createRow(this.dataRows.get(this.dataRows.size()-1).getRowNum() + 2);
    this.totalFinal.createCell(2).setCellValue("TOTAL MENSUAL");
    this.totalFinal.createCell(3).setCellFormula(operation);

  }

  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 Workbook generarInforme() {
    fillHeaders();
    fillData();
    fillTotales();
    freezeCells();
    setStyles();
    addBorders();

    return this.wb;
  }
}
