package net.ranides.assira.xls;

import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;

import net.ranides.assira.collection.iterators.IterableUtils;
import net.ranides.assira.collection.iterators.IteratorUtils;
import net.ranides.assira.collection.query.CQuery;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class XLSWriter implements AutoCloseable {

    private final Path target;
    private final SXSSFWorkbook wb;
    private final Sheet sh;

    private List<String> header;
    private boolean opened;

    public XLSWriter(String filename) {
        this(Paths.get(filename));
    }

    public XLSWriter(Path filename) {
        this.target = filename;
        this.wb = new SXSSFWorkbook();
        this.sh = wb.createSheet();
        this.opened = true;
    }

    @Override
    public void close() throws IOException {
        if(!opened) {
            return;
        }
        try (OutputStream ostream = new FileOutputStream(target.toFile())) {
            wb.write(ostream);
        } finally {
            wb.dispose();
            opened = false;
        }
    }

    public boolean isOpened() {
        return opened;
    }

    public boolean hasHeader() {
        return header != null;
    }

    public XLSWriter header(Row cells) {
        return header(CQuery.from().iterable(cells).map(XLSUtils::text));
    }

    public XLSWriter header(Iterable<String> cells) {
        if(hasHeader()) {
            throw new IllegalStateException("Header is already defined");
        }
        this.header = IteratorUtils.collect(cells.iterator(), new ArrayList<>());
        return appendRowAt(0, cells);
    }


    public XLSWriter appendRowAt(int rowIndex, Row cells) {
        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
    }

    public XLSWriter appendRowAt(int rowIndex, Iterable<String> cells) {
        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
    }

    public XLSWriter appendRowAt(int rowIndex, Map<String, String> cells) {
        if(!hasHeader()) {
            throw new IllegalStateException("Define header first");
        }
        return appendCells(sh.createRow(rowIndex).getRowNum(), cells);
    }

    public XLSWriter appendRow(Row cells) {
        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
    }

    public XLSWriter appendRow(String[] cells) {
        return appendRow(Arrays.asList(cells));
    }

    public XLSWriter appendRow(Iterable<String> cells) {
        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
    }

    public XLSWriter appendRow(Map<String, String> cells) {
        return appendRowAt(sh.getPhysicalNumberOfRows(), cells);
    }

    public XLSWriter appendCells(int rowIndex, Map<String, String> cells) {
        if(!hasHeader()) {
            throw new IllegalStateException("Define header first");
        }
        Row row = sh.getRow(rowIndex);
        for(int index = 0, n = header.size(); index < n; index++){
            row.createCell(index).setCellValue(cells.get(header.get(index)));
        }
        return this;
    }

    public XLSWriter appendCells(Row cells) {
        return appendCells(sh.getLastRowNum(), cells);
    }


    public XLSWriter appendCells(int rowIndex, Row cells) {
        return appendCells(rowIndex, IterableUtils.map(cells, cell -> XLSUtils.text(cell)));
    }

    public XLSWriter appendCells(Iterable<String> cells) {
        return appendCells(sh.getLastRowNum(), cells);
    }


    public XLSWriter appendCells(int rowIndex, Iterable<String> cells) {
        Row row = sh.getRow(rowIndex);
        // please note, that getLastCellNum is 1-based, can't return 0
        int last = Math.max(0, row.getLastCellNum());

        for(String text : cells) {
            row.createCell(last++).setCellValue(text);
        }
        return this;
    }

    public XLSWriter changeCell(int rowIndex, int cellIndex, String value) {
        Row row = sh.getRow(rowIndex);
        row.createCell(cellIndex).setCellValue(value);
        return this;
    }

    public XLSWriter changeCell(int cellIndex, String value) {
        Row row = sh.getRow(sh.getLastRowNum());
        row.createCell(cellIndex).setCellValue(value);
        return this;
    }

}
