package com.poiji.bind.mapping;

import com.poiji.bind.Unmarshaller;
import com.poiji.exception.PoijiException;
import com.poiji.option.PoijiOptions;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.filesystem.DocumentFactoryHelper;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.XMLHelper;
import org.apache.poi.xssf.eventusermodel.ReadOnlySharedStringsTable;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator;
import org.apache.poi.xssf.model.StylesTable;
import org.xml.sax.ContentHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;

/**
 * Created by hakan on 22/10/2017
 */
abstract class XSSFUnmarshaller implements Unmarshaller {

    protected final PoijiOptions options;

    XSSFUnmarshaller(PoijiOptions options) {
        this.options = options;
    }

    protected <T> void unmarshal0(Class<T> type, Consumer<? super T> consumer, OPCPackage open)
            throws ParserConfigurationException, IOException, SAXException, OpenXML4JException {

        ReadOnlySharedStringsTable readOnlySharedStringsTable = new ReadOnlySharedStringsTable(open);
        XSSFReader workbookReader = new XSSFReader(open);
        StylesTable styles = workbookReader.getStylesTable();

        PoijiNumberFormat poijiNumberFormat = options.getPoijiNumberFormat();
        if (poijiNumberFormat != null)
            poijiNumberFormat.overrideExcelNumberFormats(styles);

        XMLReader reader = XMLHelper.newXMLReader();
        InputSource is = new InputSource(workbookReader.getWorkbookData());

        reader.setContentHandler(new WorkBookContentHandler(options));
        reader.parse(is);

        WorkBookContentHandler wbch = (WorkBookContentHandler) reader.getContentHandler();
        List<WorkBookSheet> sheets = wbch.getSheets();
        SheetIterator iter = (SheetIterator) workbookReader.getSheetsData();
        int sheetCounter = 0;

        Optional<String> maybeSheetName = this.getSheetName(type, options);

        if (!maybeSheetName.isPresent()) {
            int requestedIndex = options.sheetIndex();
            int nonHiddenSheetIndex = 0;
            while (iter.hasNext()) {
                try (InputStream stream = iter.next()) {
                    WorkBookSheet wbs = sheets.get(sheetCounter);
                    if (wbs.getState().equals("visible")) {
                        if (nonHiddenSheetIndex == requestedIndex) {
                            processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer);
                            return;
                        }
                        nonHiddenSheetIndex++;
                    }
                }
                sheetCounter++;
            }
        } else {
            String sheetName = maybeSheetName.get();
            while (iter.hasNext()) {
                try (InputStream stream = iter.next()) {
                    WorkBookSheet wbs = sheets.get(sheetCounter);
                    if (wbs.getState().equals("visible")) {
                        if (iter.getSheetName().equalsIgnoreCase(sheetName)) {
                            processSheet(styles, reader, readOnlySharedStringsTable, type, stream, consumer);
                            return;
                        }
                    }
                }
                sheetCounter++;
            }
        }
    }

    private <T> void processSheet(StylesTable styles,
                                  XMLReader reader,
                                  ReadOnlySharedStringsTable readOnlySharedStringsTable,
                                  Class<T> type,
                                  InputStream sheetInputStream,
                                  Consumer<? super T> consumer) {

        DataFormatter formatter = new DataFormatter();
        InputSource sheetSource = new InputSource(sheetInputStream);
        try {
            PoijiHandler<T> poijiHandler = new PoijiHandler<>(type, options, consumer);
            ContentHandler contentHandler
                    = new XSSFSheetXMLPoijiHandler(styles,
                    null,
                    readOnlySharedStringsTable,
                    poijiHandler,
                    formatter,
                    false,
                    options);
            reader.setContentHandler(contentHandler);
            reader.parse(sheetSource);
        } catch (SAXException | IOException e) {
            IOUtils.closeQuietly(sheetInputStream);
            throw new PoijiException("Problem occurred while reading data", e);
        }
    }

    <T> void listOfEncryptedItems(Class<T> type, Consumer<? super T> consumer, POIFSFileSystem fs) throws IOException {
        InputStream stream = DocumentFactoryHelper.getDecryptedStream(fs, options.getPassword());

        try (OPCPackage open = OPCPackage.open(stream)) {
            unmarshal0(type, consumer, open);

        } catch (ParserConfigurationException | SAXException | IOException | OpenXML4JException e) {
            IOUtils.closeQuietly(fs);
            throw new PoijiException("Problem occurred while reading data", e);
        }
    }

    protected abstract <T> void returnFromExcelFile(Class<T> type, Consumer<? super T> consumer);

    protected abstract <T> void returnFromEncryptedFile(Class<T> type, Consumer<? super T> consumer);
}