package ru.ulstu.core.service;

import org.apache.poi.POIXMLDocument;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import ru.ulstu.core.error.XlsParseException;

import java.io.*;

public class XlsDocumentBuilder {
    private static final int DEFAULT_SHEET_NUM = 0;
    private File documentFile;
    private Workbook workBook;
    private Sheet currentSheet;

    /**
     * Constructor for reading and writing data from/to *.[xls|xlsx] document
     *
     * @param file contains existing document for reading or new document to save
     */
    public XlsDocumentBuilder(File file) throws IOException, XlsParseException {
        this.documentFile = file;
        if (file.exists()) {
            workBook = getWorkBook(file);
            currentSheet = workBook.getSheetAt(DEFAULT_SHEET_NUM);
        } else {
            workBook = new XSSFWorkbook();
            currentSheet = workBook.createSheet();
        }
    }

    private Workbook getWorkBook(File file) throws XlsParseException, IOException {
        InputStream inputStream = new PushbackInputStream(new FileInputStream(file), 4096);
        if (isXlsx(inputStream)) {
            return new XSSFWorkbook(inputStream);
        } else if (isExcel(inputStream)) {
            return new HSSFWorkbook(inputStream);
        }
        throw new XlsParseException("Wrong document format");
    }

    /**
     * Change active sheet to write or read data
     *
     * @param index index of sheet to activate
     */
    public XlsDocumentBuilder setActiveSheet(int index) {
        workBook.setActiveSheet(index);
        currentSheet = workBook.getSheetAt(index);
        return this;
    }

    /**
     * Create new sheet in document and set it active
     *
     * @param sheetName
     */
    public XlsDocumentBuilder insertNewSheet(String sheetName) {
        currentSheet = workBook.createSheet(sheetName);
        workBook.setActiveSheet(getSheetCount() - 1);
        return this;
    }

    public XlsDocumentBuilder insertNewSheet(String sheetName, int order) {
        insertNewSheet(sheetName);
        workBook.setSheetOrder(sheetName, order);
        return this;
    }

    /**
     * Returns number of sheet in document
     *
     * @return sheets count
     */
    public int getSheetCount() {
        return workBook.getNumberOfSheets();
    }

    /**
     * Returns number of rows in sheet
     *
     * @return rows count
     */
    public int getRowCount() {
        return currentSheet.getLastRowNum();
    }

    /**
     * Returns number of columns in sheet
     *
     * @return columns count
     */
    public int getColumnCount() {
        Row row = currentSheet.getRow(getRowCount());
        if (row == null) {
            return 0;
        }
        return row.getLastCellNum() - 1;
    }

    /**
     * Returns converted to string representation of cell value
     *
     * @param rowIndex row index of current sheet
     * @param colIndex column index of current sheet
     * @return string value of cell
     */
    public String getCellAsString(int rowIndex, int colIndex) {
        Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
        cell.setCellType(Cell.CELL_TYPE_STRING);
        return cell.getStringCellValue();
    }

    /**
     * Sets string cell value
     *
     * @param rowIndex row index of current sheet
     * @param colIndex column index of current sheet
     */
    public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, String value) {
        if (currentSheet.getRow(rowIndex) == null) {
            currentSheet.createRow(rowIndex);
        }
        if (currentSheet.getRow(rowIndex).getCell(colIndex) == null) {
            currentSheet.getRow(rowIndex).createCell(colIndex);
        }
        Cell cell = currentSheet.getRow(rowIndex).getCell(colIndex);
        cell.setCellValue(value);
        setColumnAutosize(colIndex, colIndex);
        return this;
    }

    public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, int value) {
        return setCellValue(rowIndex, colIndex, String.valueOf(value));
    }

    public XlsDocumentBuilder setCellValue(int rowIndex, int colIndex, long value) {
        return setCellValue(rowIndex, colIndex, String.valueOf(value));
    }

    /**
     * Save current file
     */
    public XlsDocumentBuilder save() throws IOException {
        OutputStream out = new FileOutputStream(documentFile);
        workBook.write(out);
        return this;
    }

    private boolean isExcel(InputStream i) throws IOException {
        return (POIFSFileSystem.hasPOIFSHeader(i) || POIXMLDocument.hasOOXMLHeader(i));
    }

    private boolean isXlsx(InputStream i) throws IOException {
        return POIXMLDocument.hasOOXMLHeader(i);
    }

    public int getActiveSheetIndex() {
        return workBook.getActiveSheetIndex();
    }

    public XlsDocumentBuilder mergeCells(int rowFrom, int rowTo, int colFrom, int colTo) {
        currentSheet.addMergedRegion(new CellRangeAddress(rowFrom, rowTo, colFrom, colTo));
        return this;
    }

    public void setRegionBorderWithMedium(int rowFrom, int rowTo, int colFrom, int colTo) {
        for (int row = rowFrom; row < rowTo; row++) {
            for (int col = colFrom; col <= colTo; col++) {
                CellRangeAddress cellRangeAddress = new CellRangeAddress(row, row, col, col);
                RegionUtil.setBorderBottom(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
                RegionUtil.setBorderLeft(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
                RegionUtil.setBorderRight(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
                RegionUtil.setBorderTop(CellStyle.BORDER_THIN, cellRangeAddress, currentSheet, workBook);
            }
        }
    }

    public XlsDocumentBuilder setColumnAutosize(int from, int to) {
        for (int col = from; col <= to; col++) {
            currentSheet.autoSizeColumn(col, true);
        }
        return this;
    }

    public XlsDocumentBuilder setRowAutosize(int from, int to) {
        CellStyle style = workBook.createCellStyle();
        style.setWrapText(true);
        for (int row = from; row <= to; row++) {
            for (int col = 0; col <= currentSheet.getRow(row).getLastCellNum(); col++) {
                if (currentSheet.getRow(row).getCell(col) != null) {
                    currentSheet.getRow(row).getCell(col).setCellStyle(style);
                }
            }
        }
        return this;
    }

    public XlsDocumentBuilder deleteSheet(int index) {
        workBook.removeSheetAt(index);
        return this;
    }
}
