/*******************************************************************************
 * Copyright (c) 2012 Andrey.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * Contributor:  Andrey Loskutov - initial API and implementation
 *******************************************************************************/
package de.loskutov.anyedit.ui.wizards;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;

import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IElementFactory;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.IWorkingSetManager;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.XMLMemento;
import org.eclipse.ui.internal.IWorkbenchConstants;

import de.loskutov.anyedit.AnyEditToolsPlugin;
import de.loskutov.anyedit.IAnyEditConstants;
import de.loskutov.anyedit.util.EclipseUtils;

/**
 * @author Andrey
 */
public class ImportPage extends WSPage {

    private static final String TITLE = "Import working sets from the local file system";
    private static final String DESCRIPTION = "Select the file path to import working " +
            "sets from and working sets to import";

    protected boolean isMerge;

    protected ImportPage(String pageName) {
        super(pageName, TITLE, DESCRIPTION, "icons/import_wiz.gif");
        isMerge = true;
    }

    @Override
    public void createControl(Composite parent) {
        super.createControl(parent);
        final Button chooserBtn = new Button(comp, SWT.CHECK);
        chooserBtn.setSelection(isMerge);
        chooserBtn.setText("Merge with existing working sets");
        chooserBtn.addSelectionListener(new SelectionListener() {
            public void widgetDefaultSelected(SelectionEvent e) {
                // ignored
            }

            public void widgetSelected(SelectionEvent e) {
                isMerge = chooserBtn.getSelection();
            }
        });
    }

    private String readSets() {
        String pathname = getFileString();
        if (pathname == null) {
            return "Please select file";
        }
        File file = new File(pathname);
        FileInputStream input = null;
        BufferedReader reader;
        IMemento memento;
        try {
            input = new FileInputStream(file);
            reader = new BufferedReader(new InputStreamReader(input, "utf-8"));
            memento = XMLMemento.createReadRoot(reader);
        } catch (FileNotFoundException e) {
            return "Couldn't read working set file: " + file + ": " + e.getMessage();
        } catch (IOException e) {
            return "Couldn't read working set file: " + file + ": " + e.getMessage();
        } catch (CoreException e) {
            return "Couldn't read working set file: " + file + ": " + e.getMessage();
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    AnyEditToolsPlugin.logError(null, e);
                }
            }
        }

        IMemento[] mementos = memento.getChildren("workingSet");
        List<IWorkingSet> sets = new ArrayList<IWorkingSet>();
        for (int i = 0; i < mementos.length; i++) {
            IWorkingSet set = restoreWorkingSet(mementos[i]);
            if (set != null) {
                sets.add(set);
            }
        }

        setInput(sets.toArray(new IWorkingSet[sets.size()]));

        try {
            String lastUsedFile = file.getCanonicalPath();
            IPreferenceStore store = AnyEditToolsPlugin.getDefault().getPreferenceStore();
            store.setValue(IAnyEditConstants.LAST_USED_WS_FILE, lastUsedFile);
        } catch (IOException e) {
            AnyEditToolsPlugin.logError(null, e);
        }
        return null;
    }

    // see org.eclipse.ui.internal.AbstractWorkingSet
    protected IWorkingSet restoreWorkingSet(IMemento memento) {
        String factoryID = memento.getString(IWorkbenchConstants.TAG_FACTORY_ID);

        if (factoryID == null) {
            // if the factory id was not set in the memento
            // then assume that the memento was created using
            // IMemento.saveState, and should be restored using WorkingSetFactory
            factoryID = "org.eclipse.ui.internal.WorkingSetFactory";
        }
        IElementFactory factory = PlatformUI.getWorkbench().getElementFactory(factoryID);
        if (factory == null) {
            AnyEditToolsPlugin.logError(
                    "Unable to restore working set - cannot instantiate factory: "
                            + factoryID, null);
            return null;
        }
        IAdaptable adaptable = factory.createElement(memento);
        if (adaptable == null) {
            AnyEditToolsPlugin.logError(
                    "Unable to restore working set - cannot instantiate working set: "
                            + factoryID, null);
            return null;
        }
        if (!(adaptable instanceof IWorkingSet)) {
            AnyEditToolsPlugin.logError(
                    "Unable to restore working set - element is not an IWorkingSet: "
                            + factoryID, null);
            return null;
        }
        return (IWorkingSet) adaptable;
    }

    public boolean finish() {
        importSelectedSets();
        return true;
    }

    private void importSelectedSets() {
        Object[] selected = getSelectedWorkingSets();
        if (selected == null) {
            return;
        }
        IWorkingSetManager workingSetManager = PlatformUI.getWorkbench()
                .getWorkingSetManager();
        List<IWorkingSet> added = new ArrayList<IWorkingSet>();
        for (int i = 0; i < selected.length; i++) {
            IWorkingSet workingSet = (IWorkingSet) selected[i];
            IWorkingSet oldWorkingSet = workingSetManager.getWorkingSet(workingSet.getName());
            if (oldWorkingSet == null) {
                removeNonExistingChildren(workingSet);
                workingSetManager.addWorkingSet(workingSet);
                added.add(workingSet);
            } else if(isMerge) {
                removeNonExistingChildren(workingSet);
                mergeWorkingSets(oldWorkingSet, workingSet);
            }
        }
        if (added.size() > 0) {
            try {
                /*
                 * Reflection required because some people do not have JDT installed, and
                 * thus current class would not be loaded at all with the direct reference
                 * to the action
                 */
                Class<?> actClass = Class.forName(
                        "de.loskutov.anyedit.jdt.SelectWorkingSetsAction", true,
                        getClass().getClassLoader());
                IWSAction action = (IWSAction) actClass.newInstance();
                action.setWorkingSets(added);
                action.run();
            } catch (NoClassDefFoundError e) {
                AnyEditToolsPlugin.logError("JDT not installed", e);
            } catch (ClassNotFoundException e) {
                AnyEditToolsPlugin.logError("JDT not installed", e);
            } catch (Throwable e) {
                AnyEditToolsPlugin.logError("Failed to activate imported working sets"
                        + " in Package Explorer view", e);
            }
        }
    }

    private void removeNonExistingChildren(IWorkingSet workingSet) {
        IAdaptable[] elements = workingSet.getElements();
        List<IResource> existing = new ArrayList<IResource>();
        for (int i = 0; i < elements.length; i++) {
            IResource resource = (IResource) elements[i].getAdapter(IResource.class);
            if (resource != null && resource.exists()) {
                existing.add(resource);
            }
        }
        workingSet.setElements(existing.toArray(new IAdaptable[existing.size()]));
    }

    public static class WorkingSetContentProvider implements ITreeContentProvider {
        private IWorkingSet[] workingSets;

        public void dispose() {
            // noop
        }

        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
            if (newInput instanceof IWorkingSet[]) {
                workingSets = (IWorkingSet[]) newInput;
            }
        }

        public Object[] getElements(Object inputElement) {
            if (workingSets == null) {
                return new Object[0];
            }
            List<IWorkingSet> sets = new ArrayList<IWorkingSet>();
            for (int i = 0; i < workingSets.length; i++) {
                IWorkingSet workingSet = workingSets[i];
                if (!workingSet.isAggregateWorkingSet()) {
                    sets.add(workingSet);
                }
            }
            return sets.toArray(new IWorkingSet[sets.size()]);
        }

        public Object[] getChildren(Object parentElement) {
            return new Object[0];
        }

        public Object getParent(Object element) {
            return null;
        }

        public boolean hasChildren(Object element) {
            return false;
        }
    }

    @Override
    protected IStructuredContentProvider createContentProvider() {
        return new WorkingSetContentProvider();
    }

    @Override
    protected void selectionChanged() {
        String errorMessage = null;
        if (getSelectedWorkingSets().length == 0) {
            errorMessage = "Please select at least one working set";
        }
        setErrorMessage(errorMessage);
        setPageComplete(errorMessage == null);
    }

    @Override
    protected boolean validateInput() {
        String errorMessage = null;
        String text = getFileString();
        if (text == null) {
            errorMessage = "Please select file";
        }

        if (errorMessage != null) {
            setInput(new IWorkingSet[0]);
            setErrorMessage(errorMessage);
            setPageComplete(false);
            return false;
        }
        errorMessage = readSets();
        if (errorMessage != null) {
            errorMessage = "Working set import failed: " + errorMessage;
        } else if (getSelectedWorkingSets().length == 0) {
            errorMessage = "Please select at least one working set";
        }
        setErrorMessage(errorMessage);
        setPageComplete(errorMessage == null);
        if (errorMessage != null) {
            setInput(new IWorkingSet[0]);
        }
        return errorMessage == null;
    }

    public void setInitialSelection(IStructuredSelection selection) {
        if(selection == null) {
            return;
        }
        IResource resource = EclipseUtils.getResource(selection);
        if(resource == null) {
            return;
        }
        IPath location = resource.getLocation();
        if(location != null && "wst".equals(location.getFileExtension()) &&
                location.toFile().isFile()) {
            usedFiles.add(location.toOSString());
        }
    }

    private void mergeWorkingSets(IWorkingSet oldWorkingSet, IWorkingSet newWorkingSet) {
        if(!oldWorkingSet.isEditable()) {
            return;
        }
        IAdaptable[] elementsOld = oldWorkingSet.getElements();
        IAdaptable[] elementsNew = newWorkingSet.getElements();
        if(elementsNew == null || elementsOld == null || elementsNew.length == 0) {
            return;
        }
        LinkedHashSet<IAdaptable> set = new LinkedHashSet<IAdaptable>(Arrays.asList(elementsOld));
        ArrayList<IAdaptable> newList = new ArrayList<IAdaptable>(Arrays.asList(elementsNew));
        newList.removeAll(set);
        if(newList.size() == 0) {
            return;
        }
        elementsNew = oldWorkingSet.adaptElements(newList.toArray(new IAdaptable[newList.size()]));
        newList = new ArrayList<IAdaptable>(Arrays.asList(elementsNew));
        newList.removeAll(set);
        if(newList.size() == 0) {
            return;
        }
        set.addAll(newList);
        oldWorkingSet.setElements(set.toArray(new IAdaptable[set.size()]));
    }
}
