/*
 * 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 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.Iterator;
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.Cell;
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 InformeLibroDeVentasToExcel {

  private final String[] titles = {
      "", "",
      "BOLETA MANUAL", "", "",
      "BOLETA FISCAL", "", "", "", "",
      "BOLETAS EXENTAS", "", "",
      "SUB",
      "FACTURA", "", "",
      "GUIAS", "", "",
      "ESTADISTICAS"
  };

  private final String[] subtitles = {
      "DIA", "FECHA",
      "INICIAL", "FINAL", "VALOR",
      "Z DEL", "Z AL", "INCIAL", "FINAL", "VALOR",
      "INICIAL", "FINAL", "VALOR",
      "TOTAL",
      "INICIAL", "FINAL", "VALOR",
      "INICIAL", "FINAL", "VALOR",
      "TOTAL", "ACUMULADO", ""
  };

  private final String[] dias = {"Lunes", "Martes", "Miercoles", "Jueves", "Viernes",
      "Sabado", "Domingo"};

  private YearMonth mes;

  private InformeLibroDeVentas informe;

  private ArrayList<Row> dataRows;
  private Row footerRow;
  private ArrayList<Row> headerRows;

  private Workbook wb;
  private Sheet sheet;
  private CreationHelper createHelper;
  private HashMap<String, CellStyle> styles;

  public InformeLibroDeVentasToExcel(YearMonth mes) {
    this.mes = mes;
    this.informe = InformeLibroDeVentas.generate(mes);

    this.dataRows = new ArrayList<>();
    this.headerRows = new ArrayList<>();

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

    this.styles = this.generateStyles();
  }

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

    headerRows.add(titles);
    headerRows.add(subtitles);

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

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

  private void fillData() {
    int rowCounter = 2;

    LocalDate currentDate = this.mes.atDay(1);
    LocalDate endDatePlusOne = this.mes.atEndOfMonth().plusDays(1);

    while (currentDate.isBefore(endDatePlusOne)) {
      LibroDeVentas data = this.informe.get(currentDate);
      Row dataRow = sheet.createRow(rowCounter);
      dataRows.add(dataRow);
      Date fecha = Date.from(data.getFecha().atStartOfDay(ZoneId.systemDefault()).toInstant());

      int cellCounter = 0;
      dataRow.createCell(cellCounter++).setCellValue(this.dias[data.getDia() - 1]);
      dataRow.createCell(cellCounter++).setCellValue(fecha);

      dataRow.createCell(cellCounter++)
          .setCellValue(data.getManualesInicial() == null ? "0" : data.getManualesInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getManualesFinal() == null ? "0" : data.getManualesFinal());
      dataRow.createCell(cellCounter++).setCellValue(data.getManuales());

      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFiscalesZInicial() == null ? "0" : data.getFiscalesZInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFiscalesZFinal() == null ? "0" : data.getFiscalesZFinal());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFiscalesInicial() == null ? "0" : data.getFiscalesInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFiscalesFinal() == null ? "0" : data.getFiscalesFinal());
      dataRow.createCell(cellCounter++).setCellValue(data.getFiscales());

      dataRow.createCell(cellCounter++)
          .setCellValue(data.getExentasInicial() == null ? "0" : data.getExentasInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getExentasFinal() == null ? "0" : data.getExentasFinal());
      dataRow.createCell(cellCounter++).setCellValue(data.getExentas());

      dataRow.createCell(cellCounter++).setCellFormula(
          "E" + (rowCounter + 1) + "+J" + (rowCounter + 1) + "+M" + (rowCounter + 1)
      );

      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFacturasInicial() == null ? "0" : data.getFacturasInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getFacturasFinal() == null ? "0" : data.getFacturasFinal());
      dataRow.createCell(cellCounter++).setCellValue(data.getFacturas());

      dataRow.createCell(cellCounter++)
          .setCellValue(data.getGuiasInicial() == null ? "0" : data.getGuiasInicial());
      dataRow.createCell(cellCounter++)
          .setCellValue(data.getGuiasFinal() == null ? "0" : data.getGuiasFinal());
      dataRow.createCell(cellCounter++).setCellValue(data.getGuias());

      dataRow.createCell(cellCounter++).setCellFormula(
          "E" + (rowCounter + 1) + "+J" + (rowCounter + 1) + "+M" + (rowCounter + 1) + "+Q" +
              (rowCounter + 1) + "+T" + (rowCounter + 1)
      );

      if (rowCounter == 2) {
        dataRow.createCell(cellCounter).setCellFormula(("U" + (rowCounter + 1)));
      } else {
        dataRow.createCell(cellCounter)
            .setCellFormula(("U" + (rowCounter + 1)) + ("+") + ("V" + (rowCounter)));
      }
      rowCounter++;
      currentDate = currentDate.plusDays(1);
    }
  }

  private void fillTotales() {
    int row = 2 + this.informe.size();
    this.footerRow = sheet.createRow(row);

    this.footerRow.createCell(0).setCellValue("TOTALES");
    this.footerRow.createCell(4).setCellFormula("SUM(E" + 3 + ":E" + row + ")");
    this.footerRow.createCell(9).setCellFormula("SUM(J" + 3 + ":J" + row + ")");
    this.footerRow.createCell(12).setCellFormula("SUM(M" + 3 + ":M" + row + ")");
    this.footerRow.createCell(16).setCellFormula("SUM(Q" + 3 + ":Q" + row + ")");
    this.footerRow.createCell(19).setCellFormula("SUM(T" + 3 + ":T" + row + ")");
  }

  private void joinCells() {
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 2, 4));
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 5, 9));
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 10, 12));
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 14, 16));
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 17, 19));
    this.sheet.addMergedRegion(new CellRangeAddress(0, 0, 20, 22));

  }

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

  private void addBorders() {
    int row = 2 + informe.size();
    PropertyTemplate pt = new PropertyTemplate();

    //Bordes internos
    pt.drawBorders(new CellRangeAddress(2, row - 1, 0, 22), BorderStyle.THIN, BorderExtent.ALL);

    //Bordes de los headers
    pt.drawBorders(new CellRangeAddress(0, 1, 0, 1), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 2, 4), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 5, 9), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 10, 12), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 13, 13), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 14, 16), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 17, 19), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(0, 1, 20, 22), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

    //Bordes que agrupan
    pt.drawBorders(new CellRangeAddress(2, row, 0, 1), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 2, 4), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 5, 9), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 10, 12), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 13, 13), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 14, 16), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 17, 19), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);
    pt.drawBorders(new CellRangeAddress(2, row, 20, 22), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

    //Bordes del total
    pt.drawBorders(new CellRangeAddress(row, row, 0, 22), BorderStyle.MEDIUM, BorderExtent.OUTSIDE);

    pt.applyBorders(this.sheet);
  }

  private void setStyles() {
    //Estilos para los 2 filas de titulos
    for (Row header : headerRows) {
      Iterator<Cell> cellIterator = header.cellIterator();
      while (cellIterator.hasNext()) {
        Cell cell = cellIterator.next();
        cell.setCellStyle(this.styles.get("header"));
      }
    }

    //Estilos para las celdas de los datos
    for (Row row : dataRows) {
      int y = 0;

      //Primeras Celdas
      row.getCell(y++).setCellStyle(this.styles.get("dia"));
      row.getCell(y++).setCellStyle(this.styles.get("date"));

      //Boletas Manuales
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Boletas Fiscales
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Boletas Exentas
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Sub Total
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Facturas
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Guias
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("regular"));
      row.getCell(y++).setCellStyle(this.styles.get("money"));

      //Estadisticas
      row.getCell(y++).setCellStyle(this.styles.get("money"));
      row.getCell(y).setCellStyle(this.styles.get("money"));
    }

    //Estilos para los totales del footer
    for (int x = 0; x < 23; x++) {
      this.footerRow.getCell(x, Row.MissingCellPolicy.CREATE_NULL_AS_BLANK)
          .setCellStyle(this.styles.get("footer"));
    }
    this.footerRow.getCell(4).setCellStyle(this.styles.get("footer_money"));
    this.footerRow.getCell(9).setCellStyle(this.styles.get("footer_money"));
    this.footerRow.getCell(12).setCellStyle(this.styles.get("footer_money"));
    this.footerRow.getCell(16).setCellStyle(this.styles.get("footer_money"));
    this.footerRow.getCell(19).setCellStyle(this.styles.get("footer_money"));

    //Setea la altura para todas las filas
    this.headerRows.get(0).setHeightInPoints(20);
    this.headerRows.get(1).setHeightInPoints(30);
    this.footerRow.setHeightInPoints(20);

    for (Row row : dataRows) {
      row.setHeightInPoints(15);
    }

    //Coloca el ancho como automatico en todas las columnas
    for (int x = 0; x <= 23; x++) {
      sheet.autoSizeColumn(x);
    }
  }

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

    return wb;
  }


  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.setFillForegroundColor(IndexedColors.GREY_50_PERCENT.getIndex());
    grayStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

    CellStyle diaStyle = this.wb.createCellStyle();
    diaStyle.cloneStyleFrom(grayStyle);

    CellStyle dateStyle = this.wb.createCellStyle();
    dateStyle.cloneStyleFrom(grayStyle);
    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.setVerticalAlignment(VerticalAlignment.CENTER);
    headerStyle.setAlignment(HorizontalAlignment.CENTER);

    CellStyle footerStyle = this.wb.createCellStyle();
    footerStyle.cloneStyleFrom(grayStyle);
    footerStyle.setVerticalAlignment(VerticalAlignment.CENTER);

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

    HashMap<String, CellStyle> styles = new HashMap<>();
    styles.put("regular", regularStyle);
    styles.put("date", dateStyle);
    styles.put("dia", diaStyle);
    styles.put("money", moneyStyle);
    styles.put("header", headerStyle);
    styles.put("footer", footerStyle);
    styles.put("footer_money", footerMoneyStyle);

    return styles;
  }
}
