/**
 * Copyright (c) 2016 by Brainwy Software Ltda. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.debug.pyunit;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.python.pydev.core.log.Log;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.callbacks.CallbackWithListeners;
import org.python.pydev.shared_core.io.FileUtils;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.string.StringUtils;
import org.python.pydev.shared_core.structure.Tuple;

// Class which is friends with PyUnitView
public class PyUnitViewTestsHolder {

    public static int MAX_RUNS_TO_KEEP = 20;

    private static PyUnitTestRun lastPinned;
    private static PyUnitTestRun currentPinned;

    public static PyUnitTestRun getLastPinned() {
        return lastPinned;
    }

    public static PyUnitTestRun getCurrentPinned() {
        return currentPinned;
    }

    public static void setCurrentPinned(PyUnitTestRun pin) {
        if (pin != null) {
            PyUnitViewTestsHolder.lastPinned = pin;
        }
        PyUnitViewTestsHolder.currentPinned = pin;
        onPinSelected.call(pin);
    }

    public static final CallbackWithListeners<PyUnitTestRun> onPinSelected = new CallbackWithListeners<>();

    /*default*/ static final Object lockServerListeners = new Object();
    /*default*/ static final LinkedList<PyUnitViewServerListener> serverListeners = new LinkedList<PyUnitViewServerListener>();

    /*default*/ static class DummyPyUnitServer implements IPyUnitServer {

        private IPyUnitLaunch launch;

        public DummyPyUnitServer(IPyUnitLaunch launch) {
            this.launch = launch;
        }

        @Override
        public void registerOnNotifyTest(IPyUnitServerListener pyUnitViewServerListener) {

        }

        @Override
        public IPyUnitLaunch getPyUnitLaunch() {
            return this.launch;
        }
    };

    /*default*/ static Job saveDiskIndexJob = new Job("Save PyUnit test runs") {

        {
            setPriority(Job.BUILD);
            setSystem(true);
        }

        @Override
        protected IStatus run(IProgressMonitor monitor) {
            saveTestsRunState(false);
            return Status.OK_STATUS;
        }
    };

    public static void restoreTestsRunState() {
        try {
            File workspaceMetadataFile = getPyUnitTestsDir();
            SortedMap<Integer, File> files = new TreeMap<>();

            // Load existing data on test runs.
            final int i0 = "test_run_".length();
            for (File f : workspaceMetadataFile.listFiles()) {
                String name = f.getName();

                if (name.endsWith(".xml") && name.startsWith("test_run_")) {
                    String val0 = name.substring(i0, name.length() - 4);
                    try {
                        int i = Integer.parseInt(val0);
                        files.put(i, f);
                    } catch (NumberFormatException e) {
                        //ignore
                    }
                }
            }

            // Get the content on the launches pinned.
            String pinInfoContents;
            try {
                pinInfoContents = FileUtils
                        .getFileContents(new File(workspaceMetadataFile, "test_run_pin_info.txt"));
            } catch (Exception e1) {
                pinInfoContents = null;
            }
            if (pinInfoContents == null) {
                pinInfoContents = "";
            }
            Tuple<String, String> split = StringUtils.splitOnFirst(pinInfoContents, '|');
            int currentPin = -1;
            int lastPin = -1;
            if (split.o1.length() > 0) {
                try {
                    currentPin = Integer.parseInt(split.o1);
                } catch (NumberFormatException e) {
                    //ignore
                }
            }
            if (split.o2.length() > 0) {
                try {
                    lastPin = Integer.parseInt(split.o2);
                } catch (NumberFormatException e) {
                    //ignore
                }
            }

            // Erase the files that we don't want to keep (but take care for keeping the pinned ones).
            int i = 0;
            while (files.size() > MAX_RUNS_TO_KEEP) {
                // Erase the files for the runs that we don't want to keep (and restore the remaining ones).
                Iterator<Entry<Integer, File>> it = files.entrySet().iterator();
                Entry<Integer, File> entry = it.next();
                // We need to prevent from deleting the pinned contents.
                if (entry.getKey() == currentPin) {
                    try {
                        currentPinned = PyUnitTestRun.fromXML(FileUtils.getFileContents(entry.getValue()));
                        setSavedDiskIndex(currentPinned, workspaceMetadataFile, i, entry);
                        i += 1;
                        if (currentPin == lastPin) {
                            lastPinned = currentPinned;
                        }
                    } catch (Exception e) {
                        Log.log(e);
                    }
                } else if (entry.getKey() == lastPin) {
                    try {
                        lastPinned = PyUnitTestRun.fromXML(FileUtils.getFileContents(entry.getValue()));
                        setSavedDiskIndex(lastPinned, workspaceMetadataFile, i, entry);
                        i += 1;
                    } catch (Exception e) {
                        Log.log(e);
                    }
                } else {
                    entry.getValue().delete();
                }
                it.remove();
            }

            Set<Entry<Integer, File>> entrySet = files.entrySet();
            for (Entry<Integer, File> entry : entrySet) {
                File file = entry.getValue();
                try {
                    PyUnitTestRun testRunRestored = PyUnitTestRun.fromXML(FileUtils.getFileContents(file));
                    setSavedDiskIndex(testRunRestored, workspaceMetadataFile, i, entry);
                    i += 1;

                    // If the pinned files are current files, we have to restore them too.
                    if (entry.getKey() == currentPin) {
                        currentPinned = testRunRestored;
                    }
                    if (entry.getKey() == lastPin) {
                        lastPinned = testRunRestored;
                    }
                    DummyPyUnitServer pyUnitServer = new DummyPyUnitServer(testRunRestored.getPyUnitLaunch());
                    final PyUnitViewServerListener serverListener = new PyUnitViewServerListener(pyUnitServer,
                            testRunRestored);
                    addServerListener(serverListener);
                } catch (Exception e) {
                    Log.log(e);
                }
            }
        } catch (Exception e) {
            Log.log(e);
        }
    }

    private static void setSavedDiskIndex(PyUnitTestRun testRun, File workspaceMetadataFile, int i,
            Entry<Integer, File> entry) {
        if (i != entry.getKey()) {
            // Restart the numbering on the disk (otherwise that number would grow forever as we
            // always start numbering from the last maximum number + 1).
            entry.getValue().renameTo(new File(workspaceMetadataFile, "test_run_" + i + ".xml"));
        }
        testRun.savedDiskIndex = i;
    }

    /**
     * Adds a server listener to the static list of available server listeners. This is needed so that we start
     * to listen to it when the view is restored later on (if it's still not visible).
     */
    protected static void addServerListener(PyUnitViewServerListener serverListener) {
        synchronized (lockServerListeners) {

            if (serverListeners.size() + 1 > MAX_RUNS_TO_KEEP) {
                serverListeners.removeFirst();
            }
            serverListeners.add(serverListener);
        }
    }

    public final static Object saveTestsRunStateLock = new Object();

    /**
     * @param forceWriteUnfinished if true, we'll write even the unfinished test runs.
     */
    public static void saveTestsRunState(boolean forceWriteUnfinished) {
        try {
            List<PyUnitTestRun> lst;
            synchronized (lockServerListeners) {
                lst = new ArrayList<>(serverListeners.size() + 2);
                for (PyUnitViewServerListener pyUnitViewServerListener : serverListeners) {
                    lst.add(pyUnitViewServerListener.getTestRun());
                }

            }
            synchronized (saveTestsRunStateLock) {
                File workspaceMetadataFile = getPyUnitTestsDir();
                int i = 0;

                PyUnitTestRun currPin = currentPinned;
                PyUnitTestRun lastPin = lastPinned;
                boolean foundCurr = false;
                boolean foundLast = false;

                for (PyUnitTestRun testRun : lst) {
                    if (testRun == currPin) {
                        foundCurr = true;
                    }
                    if (testRun == lastPin) {
                        foundLast = true;
                    }
                    if (testRun.savedDiskIndex != null && testRun.savedDiskIndex >= i) {
                        i = testRun.savedDiskIndex + 1;
                    }
                }

                if (!foundCurr && currPin != null) {
                    lst.add(currPin);
                    if (currPin.savedDiskIndex != null && currPin.savedDiskIndex >= i) {
                        i = currPin.savedDiskIndex + 1;
                    }
                }
                if (!foundLast && lastPin != null) {
                    lst.add(lastPin);
                    if (lastPin.savedDiskIndex != null && lastPin.savedDiskIndex >= i) {
                        i = lastPin.savedDiskIndex + 1;
                    }
                }

                for (PyUnitTestRun testRun : lst) {
                    // We want to write only the deltas, so, skip it if it's already saved
                    if (testRun.savedDiskIndex != null) {
                        continue;
                    }
                    if (!forceWriteUnfinished) {
                        if (!testRun.getFinished()) {
                            continue;
                        }
                    }
                    String xml = testRun.toXML();
                    File f = new File(workspaceMetadataFile,
                            "test_run_" + i + ".xml");
                    FileUtils.writeBytesToFile(xml.getBytes(StandardCharsets.UTF_8), f);
                    testRun.savedDiskIndex = i;
                    i += 1;
                }

                FastStringBuffer buf = new FastStringBuffer();
                if (currPin != null) {
                    buf.append(currPin.savedDiskIndex);
                }
                buf.append('|');
                if (lastPin != null) {
                    buf.append(lastPin.savedDiskIndex);
                }
                FileUtils.writeBytesToFile(buf.getBytes(), new File(workspaceMetadataFile, "test_run_pin_info.txt"));
            }

        } catch (Exception e) {
            Log.log(e);
        }
    }

    /**
     * @return the directory to save the files 
     */
    private static File getPyUnitTestsDir() {
        File workspaceMetadataFile = PydevPlugin.getWorkspaceMetadataFile("pyunit_tests");
        if (!workspaceMetadataFile.exists()) {
            workspaceMetadataFile.mkdirs();
        }
        return workspaceMetadataFile;
    }

}
