package com.flowable.local.work.model;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.stereotype.Component;

import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedHashMap;


@Component
public class FlowableExcelParser {

    // excelPath defines the location of the Excel file
    // Return all cells in all sheets with the following restrictions:
    // - Max column count per sheet is defined in the first row
    // - Max column count starts with the 1st cell and ends with the 1st empty cell
    // - Parsing the sheet stops after the 1st empty row
    // All sheets are added to an ArrayList
    // All rows are added to an ArrayList in the sheets ArrayList
    // All cell values are added to an ArrayList in the rows ArrayList
    // Formulas in cells are evaluated
    // All values are returned as String
    public ArrayList<ArrayList<ArrayList<String>>> parseExcelBookFromResource(String excelResourcePath) {
        try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(excelResourcePath))) {
            Workbook workBook = new XSSFWorkbook(fileInputStream);
            return parseBook(workBook);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String getResourcePath(String resourcePath) {
        ClassLoader classLoader = FlowableExcelParser.class.getClassLoader();
        URL resourceUrl = classLoader.getResource(resourcePath);
        if  (resourceUrl == null) {
            throw new RuntimeException("Resource not found: " + resourcePath);
        }
        return resourceUrl.getPath();
    }

    private ArrayList<ArrayList<ArrayList<String>>> parseBook(Workbook workBook ) {
        ArrayList<ArrayList<ArrayList<String>>> book = new ArrayList<>();
        FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
        for (Sheet workSheet : workBook) {
            book.add(parseSheet(workSheet, formulaEvaluator));
        }
        return book;
    }

    private ArrayList<ArrayList<String>> parseSheet(Sheet workSheet, FormulaEvaluator formulaEvaluator) {
        ArrayList<ArrayList<String>> sheet = new ArrayList<>();
        // Count columns of first row
        int maxCols = getMaxCols(workSheet.getRow(0));
        DataFormatter dataFormatter = new DataFormatter();
        int firstRow = workSheet.getFirstRowNum();
        int lastRow = workSheet.getLastRowNum();
        // For (Row workRow : workSheet) sometimes ignores empty rows, then the first empty row as an exit criterion wouldn't work
        for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
            ArrayList<String> row = parseRow(workSheet.getRow(rowIndex), maxCols, formulaEvaluator, dataFormatter);
            if (row == null) {
                // Take all rows until the first empty row
                break;
            } else {
                sheet.add(row);
            }
        }
        return sheet;
    }

    private ArrayList<String> parseRow(Row workRow, int maxCols, FormulaEvaluator formulaEvaluator, DataFormatter dataFormatter) {
        if (workRow == null) {
            // Row is empty
            return null;
        }
        ArrayList<String> row = new ArrayList<>();
        boolean allCellsNull = true;
        for (int colIndex = 0; colIndex < maxCols; colIndex++) {
            Cell workCell = workRow.getCell(colIndex);
            formulaEvaluator.evaluate(workCell);
            String content = dataFormatter.formatCellValue(workCell, formulaEvaluator);
            if (content != null && !content.isEmpty()) {
                allCellsNull = false;
            }
            row.add(content);
        }
        if (allCellsNull) {
            // Row is empty
            return null;
        } else {
            return row;
        }
    }

    private int getMaxCols(Row workRow) {
        int maxCol = workRow.getLastCellNum();
        for (int colIndex = 0; colIndex < maxCol; colIndex++) {
            Cell workCell = workRow.getCell(colIndex);
            if (workCell == null || workCell.getCellType() == CellType.BLANK || workCell.getCellType() == CellType._NONE) {
                // Count cells until the first empty cell
                return colIndex;
            }
        }
        return maxCol;
    }

    @Deprecated
    public ArrayList<LinkedHashMap<String, Object>> parseExcelFromResource(String resourcePath) {
        try (FileInputStream fileInputStream = new FileInputStream(getResourcePath(resourcePath))) {
            Workbook workBook = new XSSFWorkbook(fileInputStream);
            FormulaEvaluator formulaEvaluator = workBook.getCreationHelper().createFormulaEvaluator();
            ArrayList<ArrayList<String>> sheet = parseSheet(workBook.getSheetAt(0), formulaEvaluator);
            ArrayList<String> colHeaders = new ArrayList<>();
            ArrayList<LinkedHashMap<String, Object>> rowList = new ArrayList<>();
            for (int rowIndex = 0; rowIndex < sheet.size(); rowIndex++) {
                ArrayList<String> row = sheet.get(rowIndex);
                if (rowIndex == 0) {
                    // Header row
                    for (Object cell : row) {
                        colHeaders.add(String.valueOf(cell));
                    }
                    continue;
                }
                LinkedHashMap<String, Object> rowMap = new LinkedHashMap<>();
                for (int colIndex = 0; colIndex < row.size(); colIndex++) {
                    rowMap.put(colHeaders.get(colIndex), row.get(colIndex));
                }
                rowList.add(rowMap);
            }
            return rowList;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}
