package be.iminds.ilabt.jfed.experimenter_gui.tasks;

import be.iminds.ilabt.jfed.experimenter_gui.tutorials.Tutorial;
import be.iminds.ilabt.jfed.experimenter_gui.tutorials.TutorialViewsFactory;
import be.iminds.ilabt.jfed.experimenter_gui.tutorials.Tutorials;
import be.iminds.ilabt.jfed.experimenter_gui.util.FileUtils;
import be.iminds.ilabt.jfed.experimenter_gui.util.ProgressMonitorInputStream;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.DeserializationProblemHandler;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import javafx.concurrent.Task;
import javafx.stage.Window;
import org.controlsfx.dialog.ExceptionDialog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Collections;
import java.util.Objects;

/**
 * User: twalcari
 * Date: 3/3/2015
 * Time: 11:46
 */
public class OpenTutorialTask extends Task<Tutorials> {
    private static final Logger LOG = LoggerFactory.getLogger(OpenTutorialTask.class);
    private static final double READ_PROGRESS_PART = 0.25; //other part of progess is for parser
    private static final double PARSE_PROGRESS_PART = 1 - READ_PROGRESS_PART;

    private final Window parentWindow;

    private final InputStream inputStream;
    private final long fileLength;
    private final String name;

    private final TutorialViewsFactory tutorialViewsFactory;
    private final XMLInputFactory xmlInputFactory = XMLInputFactory.newInstance();


    OpenTutorialTask(Window parentWindow, File file, TutorialViewsFactory tutorialViewsFactory) throws FileNotFoundException {
        this.parentWindow = parentWindow;
        this.tutorialViewsFactory = tutorialViewsFactory;
        updateTitle("Loading tutorial '" + file.getName() + "'");

        this.inputStream = new FileInputStream(file);
        this.fileLength = file.length();
        this.name = file.getName().replace(FileUtils.EXPERIMENT_FILE_EXTENSION, "");
    }

    OpenTutorialTask(Window parentWindow, InputStream inputStream, long fileLength, String name, TutorialViewsFactory tutorialViewsFactory) {
        this.parentWindow = parentWindow;
        this.tutorialViewsFactory = tutorialViewsFactory;

        updateTitle("Loading request Rspec '" + name + "'");
        this.inputStream = inputStream;
        this.fileLength = fileLength;
        this.name = name;
    }

    OpenTutorialTask(Window parentWindow, String urlString, TutorialViewsFactory tutorialViewsFactory) throws IOException {
        this.parentWindow = parentWindow;
        this.tutorialViewsFactory = tutorialViewsFactory;


        URL url = new URL(urlString);

        //get name from url
        String name = urlString.substring(urlString.lastIndexOf('/') + 1);
        name = name.replace(FileUtils.EXPERIMENT_FILE_EXTENSION, "");

        this.name = name;
        updateTitle("Loading request Rspec '" + name + "'");

        //try to get file size
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        this.inputStream = url.openStream();
        this.fileLength = conn.getContentLengthLong();
    }

    @Override
    protected Tutorials call() throws IOException, TutorialParseException {
        ProgressMonitorInputStream monitoredInputStream =
                new ProgressMonitorInputStream(new BufferedInputStream(inputStream, 500_000)); //buffered input stream to allow reset
        monitoredInputStream.mark(500_000);

        monitoredInputStream.nreadProperty().addListener(observable -> updateProgress(monitoredInputStream.getNread(), monitoredInputStream.getSize()));

        ObjectMapper mapper = new XmlMapper();
        mapper.addHandler(new DeserializationProblemHandler() {
            @Override
            public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser jp, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException {
                LOG.warn("Encountered an unknown property {} while deserializing {} (position: {}, {})",
                        propertyName, beanOrClass.toString(), jp.getCurrentLocation().getLineNr(), jp.getCurrentLocation().getColumnNr());

                return true;
            }
        });

        String rootName = null;
        try {
            XMLStreamReader reader =
                    xmlInputFactory.createXMLStreamReader(monitoredInputStream);
            if (!reader.hasNext())
                throw new TutorialParseException("Given file seems to be invalid");
            reader.next();

            rootName = reader.getLocalName();

            reader.close();
        } catch (XMLStreamException ex) {
            throw new TutorialParseException("Exception while reading tutorial file: " + ex.getMessage(), ex);
        }
        monitoredInputStream.reset();

        if (Objects.equals("f4ftuts", rootName)) {
            return mapper.readValue(monitoredInputStream, Tutorials.class);
        } else if (Objects.equals("f4ftut", rootName)) {
            Tutorials result = new Tutorials();
            final Tutorial tutorial = mapper.readValue(monitoredInputStream, Tutorial.class);
            result.setTutorials(Collections.singletonList(tutorial));

            return result;
        } else {
            throw new TutorialParseException("Unexpected type of root element: " + rootName);
        }


    }

    @Override
    protected void succeeded() {
        tutorialViewsFactory.createTutorialsView(getValue()).showDialog();
    }

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    @Override
    protected void failed() {
        if (getException() instanceof JsonParseException) {
            JsonParseException ex = (JsonParseException) getException();
            ExceptionDialog dialog = new ExceptionDialog(ex);
            dialog.initOwner(parentWindow);
            dialog.setContentText("The specified source does not contain a valid tutorial file.\n\n" +
                    "Error on line " + ex.getLocation().getLineNr()
                    + ", column " + ex.getLocation().getColumnNr()
                    + ": " + ex.getMessage());
            dialog.setHeaderText("Opening Tutorial failed");
            dialog.setTitle("Opening Tutorial failed");
            dialog.showAndWait();
        } else {
            ExceptionDialog dialog = new ExceptionDialog(getException());
            dialog.initOwner(parentWindow);
            dialog.setContentText("Could not open the tutorial from the given source: " + getException().getMessage());
            dialog.setHeaderText("Error while opening Tutorial");
            dialog.setTitle("Error while opening Tutorial");
            dialog.showAndWait();
        }
    }

    public String getName() {
        return name;
    }

    public static final class TutorialParseException extends Exception {
        public TutorialParseException() {
        }

        public TutorialParseException(String message) {
            super(message);
        }

        public TutorialParseException(String message, Throwable cause) {
            super(message, cause);
        }

        public TutorialParseException(Throwable cause) {
            super(cause);
        }

        public TutorialParseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
}
