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

import be.iminds.ilabt.jfed.experimenter_gui.editor.EditableRspec;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorTab;
import be.iminds.ilabt.jfed.experimenter_gui.editor.ExperimentEditorTabFactory;
import be.iminds.ilabt.jfed.experimenter_gui.editor.rspec_validation.RspecCheckReason;
import be.iminds.ilabt.jfed.experimenter_gui.editor.rspec_validation.RspecValidationEngine;
import be.iminds.ilabt.jfed.experimenter_gui.editor.rspec_validation.RspecValidationResult;
import be.iminds.ilabt.jfed.experimenter_gui.ui.ExperimentEditorTabHost;
import be.iminds.ilabt.jfed.experimenter_gui.util.FileUtils;
import be.iminds.ilabt.jfed.rspec.model.ModelRspecType;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.parser.RspecParseException;
import be.iminds.ilabt.jfed.rspec.rspec_source.RequestRspecSource;
import be.iminds.ilabt.jfed.ui.javafx.dialogs.JFDialogs;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.control.ProgressIndicator;
import javafx.stage.Window;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;

/**
 * User: twalcari
 * Date: 3/3/2015
 * Time: 11:46
 */
public class OpenRequestRspecTask extends Task<EditableRspec> {
    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 Source source;

    private final ExperimentEditorTabFactory experimentEditorTabFactory;
    private final ExperimentEditorTabHost experimentEditorTabHost;


    private final RspecValidationEngine validationEngine = new RspecValidationEngine();


    OpenRequestRspecTask(Window parentWindow, File file, ExperimentEditorTabFactory experimentEditorTabFactory, ExperimentEditorTabHost experimentEditorTabHost) {
        this.parentWindow = parentWindow;
        this.experimentEditorTabFactory = experimentEditorTabFactory;
        this.experimentEditorTabHost = experimentEditorTabHost;
        updateTitle("Loading request Rspec file '" + file.getName() + "'");

        this.source = new Source() {
            private FileInputStream fis = null;

            @Override
            public void open() throws IOException {
                fis = new FileInputStream(file);

            }

            @Override
            public void close() throws IOException {
                if(fis != null)
                    fis.close();
            }

            @Override
            public InputStream getInputStream() {
                return fis;
            }

            @Override
            public Long getLength() {
                return file.length();
            }

            @Override
            public String getName() {
                return file.getName().replace(FileUtils.EXPERIMENT_FILE_EXTENSION, "");
            }

            @Override
            public File getDefaultSaveFile() {
                return file;
            }
        };
    }

    OpenRequestRspecTask(Window parentWindow, InputStream inputStream, long fileLength, String name, File defaultSaveFile,
                         ExperimentEditorTabFactory experimentEditorTabFactory, ExperimentEditorTabHost experimentEditorTabHost) {
        this.parentWindow = parentWindow;
        this.experimentEditorTabFactory = experimentEditorTabFactory;
        this.experimentEditorTabHost = experimentEditorTabHost;

        updateTitle("Loading request Rspec '" + name + "'");

        this.source = new Source() {
            @Override
            public void open() throws IOException {
                //nothing to do..
            }

            @Override
            public void close() throws IOException {
                //nothing to do..
            }

            @Override
            public InputStream getInputStream() {
                return inputStream;
            }

            @Override
            public Long getLength() {
                return fileLength;
            }

            @Override
            public String getName() {
                return name;
            }

            @Override
            public File getDefaultSaveFile() {
                return defaultSaveFile;
            }
        };
    }

    OpenRequestRspecTask(Window parentWindow, String urlString, ExperimentEditorTabFactory experimentEditorTabFactory, ExperimentEditorTabHost experimentEditorTabHost) {
        this.parentWindow = parentWindow;
        this.experimentEditorTabFactory = experimentEditorTabFactory;
        this.experimentEditorTabHost = experimentEditorTabHost;

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


        this.source = new Source() {
            private URL url;

            //try to get file size
            private HttpURLConnection conn;

            @Override
            public void open() throws IOException {
                url = new URL(urlString);
                conn = (HttpURLConnection) url.openConnection();
            }

            @Override
            public void close() throws IOException {
                if(conn != null)
                    conn.disconnect();
            }

            @Override
            public InputStream getInputStream() throws IOException {
                return conn.getInputStream();
            }

            @Override
            public Long getLength() {
                return conn.getContentLengthLong();
            }

            @Override
            public String getName() {
                return name;
            }

            @Override
            public File getDefaultSaveFile() {
                return null;
            }
        };

        updateTitle("Loading request Rspec '" + name + "'");
    }


    private interface Source {

        void open() throws IOException;
        void close() throws IOException;

        InputStream getInputStream() throws IOException;

        Long getLength();

        String getName();

        File getDefaultSaveFile();
    }

    @Override
    protected EditableRspec call() throws Exception {
        source.open();

        StringBuilder fileContents = new StringBuilder();
        try (InputStreamReader reader = new InputStreamReader(source.getInputStream())) {
            int readingProgress = 0;
            char[] cbuf = new char[1024];

            int count;
            while ((count = reader.read(cbuf)) != -1) {
                fileContents.append(cbuf, 0, count);
                readingProgress += count;    //update progress

                if (source.getLength() != null)
                    updateProgress(READ_PROGRESS_PART * readingProgress / source.getLength(), 1);
                else
                    updateProgress(ProgressIndicator.INDETERMINATE_PROGRESS,0);
            }
        }

        source.close();

        final RequestRspecSource requestRspecSource =
                new RequestRspecSource(fileContents.toString(), ModelRspecType.FX);

        RunnableFuture<RspecValidationResult> verifyCall =
                new FutureTask<>(() -> validationEngine.validateRSpec(RspecCheckReason.OPENED_FILE, requestRspecSource, parentWindow));
        Platform.runLater(verifyCall);

        RequestRspecSource resultRspecSource;

        RspecValidationResult verifyRspecResult = verifyCall.get();
        if (verifyRspecResult != null) {
            //users fixed something automatically, so we replace the source
            resultRspecSource = verifyRspecResult.getReplacementRequestRspecSource();
        } else {
            //if no replacement is done, there are no errors, or the user has been informed of them
            //either way, we put the file content in the editor, however wrong it may be
            resultRspecSource = requestRspecSource;
        }

        /*
        if the ModelRspec can be made without losing data, we modify our ResultRspecSource so that this ModelRspec
        is the primary source. This will make sure that the users sees the loaded rspec first in the topology editor.
         */
        if (!resultRspecSource.isLosingData(ModelRspecType.FX)) {
            resultRspecSource =
                    new RequestRspecSource((FXModelRspec) resultRspecSource.getModelRspec(ModelRspecType.FX));
        }

        return new EditableRspec(resultRspecSource, source.getName(), source.getDefaultSaveFile());
    }

    @Override
    protected void succeeded() {
        ExperimentEditorTab newTab =
                experimentEditorTabFactory.createExperimentEditorTab(getValue());
        experimentEditorTabHost.addExperimentEditorTab(newTab);
        experimentEditorTabHost.showExperimentEditorTab(newTab);
    }

    @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
    @Override
    protected void failed() {
        if (getException() instanceof RspecParseException) {
            RspecParseException ex = (RspecParseException) getException();
            JFDialogs.create()
                    .owner(parentWindow)
                    .message("The specified source does not contain a valid Rspec.\n\n" +
                            "Error on line " + ex.getStaxLocation().getLineNumber()
                            + ", column " + ex.getStaxLocation().getColumnNumber()
                            + ": " + ex.getMessage())
                    .masthead("Opening Rspec failed")
                    .title("Opening Rspec failed")
                    .showException(ex);
        } else {
            JFDialogs.create()
                    .owner(parentWindow)
                    .message("Could not open the RSpec from the given source: " + getException().getMessage())
                    .masthead("Error while opening Experiment Definition")
                    .title("Error while opening Experiment Definition")
                    .showException(getException());
        }
    }

    public String getName() {
        return source.getName();
    }
}
