package be.iminds.ilabt.jfed.call_log_output;

import be.iminds.ilabt.jfed.log.cache.ApiCallDetailsCache;
import be.iminds.ilabt.jfed.log.cache.ApiCallDetailsRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.inject.Singleton;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventFactory;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.StringWriter;

import static be.iminds.ilabt.jfed.call_log_output.CallReport.State;
import static be.iminds.ilabt.jfed.call_log_output.CallReportConstants.*;
import static be.iminds.ilabt.jfed.call_log_output.SerializableApiCallDetailsWriter.writeApiCallDetails;
import static java.time.ZoneId.systemDefault;
import static java.time.ZonedDateTime.ofInstant;
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;

/**
 * User: twalcari
 * Date: 3/24/2015
 * Time: 21:16
 */
@Singleton
public class CallReportWriter {
    private static final Logger LOG = LoggerFactory.getLogger(CallReportWriter.class);

    private final ApiCallDetailsCache cache;

    @Inject
    public CallReportWriter(ApiCallDetailsCache cache) {
        this.cache = cache;
    }


    public synchronized void writeCallReport(CallReport callReport, XMLEventWriter xtw,
                                             XMLEventFactory eventFactory, boolean useCallRefs) throws XMLStreamException {
        QName topLevelElement =  Q_CALL_REPORT;


        xtw.add(eventFactory.createStartElement(topLevelElement, null, null));
        xtw.add(eventFactory.createAttribute(Q_TITLE, callReport.getReportTitle()));
        xtw.add(eventFactory.createAttribute(Q_REPORTER, callReport.getReportObject()));


        xtw.add(eventFactory.createStartElement(Q_STATES, null, null));
        for (State state : callReport.getStates()) {
            writeState(state, xtw, eventFactory, useCallRefs);
        }
        xtw.add(eventFactory.createEndElement(Q_STATES, null));

        xtw.add(eventFactory.createEndElement(topLevelElement, null)); //StitchingReport
        xtw.flush();
    }

    private void writeState(CallReport.State state, XMLEventWriter xtw,
                            XMLEventFactory eventFactory, boolean useCallRefs) throws XMLStreamException {
        xtw.add(eventFactory.createStartElement(Q_STATE, null, null));

        xtw.add(eventFactory.createStartElement(Q_START, null, null));
        xtw.add(eventFactory.createCharacters(ISO_OFFSET_DATE_TIME.format(
                ofInstant(state.getStateStart().toInstant(), systemDefault()))));
        xtw.add(eventFactory.createEndElement(Q_START, null));

        if (state.getEnd() != null) {
            xtw.add(eventFactory.createStartElement(Q_END, null, null));
            xtw.add(eventFactory.createCharacters(ISO_OFFSET_DATE_TIME.format(
                    ofInstant(state.getEnd().toInstant(), systemDefault()))));
            xtw.add(eventFactory.createEndElement(Q_END, null));
        }

        xtw.add(eventFactory.createStartElement(Q_NAME, null, null));
        xtw.add(eventFactory.createCharacters(state.getName()));
        xtw.add(eventFactory.createEndElement(Q_NAME, null));

        xtw.add(eventFactory.createStartElement(Q_LOGENTRIES, null, null));
        for (LogOutput.LogEntry logLine : state.getLogLines()) {
            writeLogLine(logLine, xtw, eventFactory);
        }
        xtw.add(eventFactory.createEndElement(Q_LOGENTRIES, null)); //logentries


        //write all the calls to the stream
        xtw.add(eventFactory.createStartElement(Q_CALLS, null, null));

        for (ApiCallDetailsRef apiCallDetailsRef : state.getCalls()) {
            if (useCallRefs) {
                writeApiCallDetailsRef(apiCallDetailsRef, xtw, eventFactory);
            } else {
                SerializableApiCallDetails apiCallDetails = cache.getApiCallDetails(apiCallDetailsRef);
                writeApiCallDetails(apiCallDetails, xtw, eventFactory);

            }
            xtw.flush();
        }

        xtw.add(eventFactory.createEndElement(Q_CALLS, null)); //calls

        xtw.add(eventFactory.createStartElement(Q_STATESTATE, null, null));
        xtw.add(eventFactory.createCharacters(state.getState().name()));
        xtw.add(eventFactory.createEndElement(Q_STATESTATE, null));

        xtw.add(eventFactory.createEndElement(Q_STATE, null));   //state
        xtw.flush();
    }

    private void writeLogLine(LogOutput.LogEntry logLine, XMLEventWriter xtw,
                              XMLEventFactory eventFactory) throws XMLStreamException {
        xtw.add(eventFactory.createStartElement(Q_LOGENTRY, null, null));

        xtw.add(eventFactory.createStartElement(Q_TYPE, null, null));
        xtw.add(eventFactory.createCharacters(logLine.getType().name()));
        xtw.add(eventFactory.createEndElement(Q_TYPE, null));

        xtw.add(eventFactory.createStartElement(Q_TEXT, null, null));
        xtw.add(eventFactory.createCharacters(logLine.getText()));
        xtw.add(eventFactory.createEndElement(Q_TEXT, null));

        xtw.add(eventFactory.createStartElement(Q_TIME_IN_MS, null, null));
        xtw.add(eventFactory.createCharacters(Long.toString(logLine.getTimeMs())));
        xtw.add(eventFactory.createEndElement(Q_TIME_IN_MS, null));

        if (logLine.getExceptionStackTrace() != null) {
            xtw.add(eventFactory.createStartElement(Q_EXCEPTION, null, null));
            xtw.add(eventFactory.createCharacters(logLine.getExceptionStackTrace()));
            xtw.add(eventFactory.createEndElement(Q_EXCEPTION, null));
        }

        xtw.add(eventFactory.createEndElement(Q_LOGENTRY, null)); //logentry
    }

    public void writeApiCallDetailsRef(ApiCallDetailsRef apiCallDetailsRef, XMLEventWriter xtw,
                                       XMLEventFactory eventFactory) throws XMLStreamException {
        xtw.add(eventFactory.createStartElement(Q_CALL_REF, null, null));
        xtw.add(eventFactory.createAttribute(Q_CALL_REF_ID, Integer.toString(apiCallDetailsRef.getId())));
        xtw.add(eventFactory.createEndElement(Q_CALL_REF, null));
    }

    public synchronized void writeCallReport(CallReport callReport, File file, boolean useCallRefs)
            throws FileNotFoundException, XMLStreamException {

        FileOutputStream fos = new FileOutputStream(file);
        XMLOutputFactory xof = XMLOutputFactory.newInstance();
        writeCallReport(callReport, xof.createXMLEventWriter(fos), XMLEventFactory.newInstance(), useCallRefs);
    }

    public synchronized String writeCallReportToString(CallReport callReport, boolean useCallRefs)
            throws XMLStreamException {

        StringWriter sw = new StringWriter();
        XMLOutputFactory xof = XMLOutputFactory.newInstance();
        writeCallReport(callReport, xof.createXMLEventWriter(sw), XMLEventFactory.newInstance(), useCallRefs);

        return sw.toString();
    }
}
