package ru.nbch.credit_tracker.service.xml.stax;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Iterator;
import java.util.function.Consumer;
import java.util.function.Function;

public abstract class BaseStaxReader<T> implements StaxReader<T> {
    private final Deque<String> stack = new ArrayDeque<>();
    protected final StringBuilder buffer = new StringBuilder();

    protected abstract T createObject();

    @Override
    public T read(XMLStreamReader reader) throws XMLStreamException {
        return read(reader, null, null, null, null);
    }

    public T read(XMLStreamReader reader, Consumer<T> objProcessor, Function<T, Boolean> emptyBatchCheckFunction, Integer batchSize, String endBatchElementPath) throws XMLStreamException {
        T obj = createObject();
        int objCount = 0;
        try {
            while (reader.hasNext()) {
                boolean breakExecution = false;
                int ev = reader.next();
                switch (ev) {
                    case XMLStreamConstants.START_ELEMENT -> {
                        buffer.setLength(0);
                        startElement(reader.getLocalName());
                        innerProcessStartElement(reader, obj);
                    }
                    case XMLStreamConstants.CHARACTERS -> {
                        String text = reader.getText().trim();
                        if (!text.isEmpty()) buffer.append(text);
                    }
                    case XMLStreamConstants.ATTRIBUTE -> {
                        breakExecution = innerProcess(reader, obj);
                    }
                    case XMLStreamConstants.END_ELEMENT -> {
                        breakExecution = innerProcess(reader, obj);
                        String lastElementPath = getPath();
                        endElement();
                        if (objProcessor != null) {
                            if (endBatchElementPath.equals(lastElementPath)) {
                                objCount++;
                                if (objCount == batchSize) {
                                    objProcessor.accept(obj); // обрабатываем пачку элементов
                                    objCount = 0;
                                }
                            }
                        }
                    }
                    default -> {
                    }
                }
                if (breakExecution) break;
            }
            if (objProcessor != null) {
                if (emptyBatchCheckFunction == null) {
                    objProcessor.accept(obj); // Дополнительная обработка пачки если: batchSize меньше количества элементов в xml или дообрабатываем "хвост" меньший чем batchSize
                } else if (!emptyBatchCheckFunction.apply(obj)) { // Проверка на пустоту пачки, чтобы не обрабатывать пустой список
                    objProcessor.accept(obj);
                }
            }
            return obj;
        } catch (Exception e) {
            stack.clear();
            throw e;
        }
    }

    protected abstract boolean innerProcess(XMLStreamReader reader, T obj) throws XMLStreamException;

    protected abstract void innerProcessStartElement(XMLStreamReader reader, T obj) throws XMLStreamException;

    public void startElement(String localName) {
        localName = substringAfterColon(localName);
        stack.push(localName);
    }

    public void endElement() {
        if (!stack.isEmpty()) {
            stack.pop();
        }
    }

    public String getPath() {
        StringBuilder sb = new StringBuilder();
        for (Iterator<String> it = stack.descendingIterator(); it.hasNext(); ) {
            String name = it.next();
            sb.append('/').append(name);
        }
        return sb.toString();
    }

    public static String substringAfterColon(String input) {
        if (input == null) {
            return null;
        }
        int idx = input.indexOf(':');
        if (idx == -1) {
            return input;
        }
        if (idx == input.length() - 1) {
            return "";
        }
        return input.substring(idx + 1);
    }
}
