/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.drools.eclipse.flow.common.editor;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.EventObject;

import org.drools.eclipse.DroolsEclipsePlugin;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
import org.eclipse.draw2d.SWTGraphics;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.ContextMenuProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.EditPartFactory;
import org.eclipse.gef.KeyHandler;
import org.eclipse.gef.KeyStroke;
import org.eclipse.gef.LayerConstants;
import org.eclipse.gef.editparts.LayerManager;
import org.eclipse.gef.editparts.ScalableRootEditPart;
import org.eclipse.gef.editparts.ZoomManager;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.actions.ActionRegistry;
import org.eclipse.gef.ui.actions.AlignmentAction;
import org.eclipse.gef.ui.actions.DirectEditAction;
import org.eclipse.gef.ui.actions.GEFActionConstants;
import org.eclipse.gef.ui.actions.ToggleGridAction;
import org.eclipse.gef.ui.parts.GraphicalEditorWithPalette;
import org.eclipse.gef.ui.parts.GraphicalViewerKeyHandler;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IStorageEditorInput;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.dialogs.SaveAsDialog;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;

/**
 * Abstract implementation of a graphical editor.
 */
public abstract class GenericModelEditor extends GraphicalEditorWithPalette { // implements ITabbedPropertySheetPageContributor {

    private Object model;
    private boolean savePreviouslyNeeded = false;
    private KeyHandler sharedKeyHandler;
    private PaletteRoot root;
    private OverviewOutlinePage overviewOutlinePage;

    public GenericModelEditor() {
        setEditDomain(new DefaultEditDomain(this));
    }

    protected void setModel(Object model) {
        this.model = model;
    }

    public Object getModel() {
        return model;
    }

    @SuppressWarnings("unchecked")
    protected void createActions() {
        super.createActions();
        ActionRegistry registry = getActionRegistry();

        IAction action = new DirectEditAction((IWorkbenchPart) this);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.LEFT);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.CENTER);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.RIGHT);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.TOP);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.MIDDLE);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());

        action = new AlignmentAction((IWorkbenchPart) this,
                PositionConstants.BOTTOM);
        registry.registerAction(action);
        getSelectionActions().add(action.getId());
    }

    public void commandStackChanged(EventObject event) {
        if (isDirty()) {
            if (!savePreviouslyNeeded()) {
                setSavePreviouslyNeeded(true);
                firePropertyChange(IEditorPart.PROP_DIRTY);
            }
        } else {
            setSavePreviouslyNeeded(false);
            firePropertyChange(IEditorPart.PROP_DIRTY);
        }
        super.commandStackChanged(event);
    }

    protected abstract void writeModel(OutputStream os) throws IOException;

    protected void configureGraphicalViewer() {
        super.configureGraphicalViewer();
        getGraphicalViewer().setRootEditPart(new ScalableRootEditPart());
        getGraphicalViewer().setEditPartFactory(createEditPartFactory());
        getGraphicalViewer().setKeyHandler(
                new GraphicalViewerKeyHandler(getGraphicalViewer())
                        .setParent(getCommonKeyHandler()));

        IAction showGrid = new ToggleGridAction(getGraphicalViewer());
        getActionRegistry().registerAction(showGrid);

        ContextMenuProvider provider = new GenericContextMenuProvider(
                getGraphicalViewer(), getActionRegistry());
        getGraphicalViewer().setContextMenu(provider);
        getSite().registerContextMenu("org.drools.eclipse.flow.editor.contextmenu",
                provider, getGraphicalViewer());
    }

    protected abstract EditPartFactory createEditPartFactory();

    protected void initializeGraphicalViewer() {
        getGraphicalViewer().setContents(model);
    }

    public void doSave(IProgressMonitor monitor) {
        try {
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            writeModel(out);
            IFile file = ((IFileEditorInput) getEditorInput()).getFile();
            file.setContents(new ByteArrayInputStream(out.toByteArray()), true,
                    false, monitor);
            out.close();
            getCommandStack().markSaveLocation();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void doSaveAs() {
        SaveAsDialog dialog = new SaveAsDialog(getSite().getWorkbenchWindow()
                .getShell());
        dialog.setOriginalFile(((IFileEditorInput) getEditorInput()).getFile());
        dialog.open();
        IPath path = dialog.getResult();

        if (path == null) {
            return;
        }

        IWorkspace workspace = ResourcesPlugin.getWorkspace();
        final IFile file = workspace.getRoot().getFile(path);

        WorkspaceModifyOperation op = new WorkspaceModifyOperation() {
            public void execute(final IProgressMonitor monitor)
                    throws CoreException {
                try {
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    writeModel(out);
                    file.create(new ByteArrayInputStream(out.toByteArray()),
                            true, monitor);
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };

        try {
            new ProgressMonitorDialog(getSite().getWorkbenchWindow().getShell())
                    .run(false, true, op);
            setInput(new FileEditorInput(file));
            getCommandStack().markSaveLocation();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected KeyHandler getCommonKeyHandler() {
        if (sharedKeyHandler == null) {
            sharedKeyHandler = new KeyHandler();
            sharedKeyHandler
                    .put(KeyStroke.getPressed(SWT.DEL, 127, 0),
                            getActionRegistry().getAction(
                                    ActionFactory.DELETE.getId()));
            sharedKeyHandler.put(KeyStroke.getPressed(SWT.F2, 0),
                    getActionRegistry().getAction(
                            GEFActionConstants.DIRECT_EDIT));
        }
        return sharedKeyHandler;
    }

    public boolean isDirty() {
        return isSaveOnCloseNeeded();
    }

    public boolean isSaveAsAllowed() {
        return true;
    }

    public boolean isSaveOnCloseNeeded() {
        return getCommandStack().isDirty();
    }

    private boolean savePreviouslyNeeded() {
        return savePreviouslyNeeded;
    }

    private void setSavePreviouslyNeeded(boolean value) {
        savePreviouslyNeeded = value;
    }

    protected PaletteRoot getPaletteRoot() {
        if (root == null) {
            root = createPalette();
        }
        return root;
    }

    protected abstract PaletteRoot createPalette();

    protected void setInput(IEditorInput input) {
        super.setInput(input);

        if (input instanceof IFileEditorInput) {
            IFile file = getFile();
            if (file != null) {
                setPartName(file.getName());
            }
            try {
                InputStream is = file.getContents(false);
                createModel(is);
            } catch (Throwable t) {
                DroolsEclipsePlugin.log(t);
            }
        } else if (input instanceof IStorageEditorInput) {
            try {
                IStorage storage = ((IStorageEditorInput) input).getStorage();
                setPartName(storage.getName());
                InputStream is = storage.getContents();
                createModel(is);
            } catch (Throwable t) {
                DroolsEclipsePlugin.log(t);
            }
        }

        if (getGraphicalViewer() != null) {
            initializeGraphicalViewer();
        }
    }

    public IFile getFile() {
        IEditorInput input = getEditorInput();
        if (!(input instanceof IFileEditorInput)) {
            return null;
        }
        return ((IFileEditorInput) getEditorInput()).getFile();
    }

    public IProject getProject() {
        IFile file = getFile();
        if (file != null) {
            return file.getProject();
        }
        return null;
    }

    public IJavaProject getJavaProject() {
        IProject project = getProject();
        if (project != null) {
            try {
                if (project.getNature("org.eclipse.jdt.core.javanature") != null) {
                    IJavaProject javaProject = JavaCore.create(project);
                    if (javaProject.exists()){
                        return javaProject;
                    }
                }
            } catch (CoreException e) {
                DroolsEclipsePlugin.log(e);
            }
        }
        return null;
    }

    protected abstract void createModel(InputStream is);

    public Object getAdapter(@SuppressWarnings("rawtypes") Class type) {
        if (type == IContentOutlinePage.class) {
            return getOverviewOutlinePage();
        }
        if (type == ZoomManager.class) {
            return ((ScalableRootEditPart) getGraphicalViewer()
                    .getRootEditPart()).getZoomManager();
        }
//        if (type == IPropertySheetPage.class) {
//            return new TabbedPropertySheetPage(this);
//        }
        return super.getAdapter(type);
    }

    protected OverviewOutlinePage getOverviewOutlinePage() {
        if (null == overviewOutlinePage && null != getGraphicalViewer()) {
            ScalableRootEditPart rootEditPart = (ScalableRootEditPart) getGraphicalViewer()
                    .getRootEditPart();
            overviewOutlinePage = new OverviewOutlinePage(rootEditPart);
        }
        return overviewOutlinePage;
    }

    public String getContributorId() {
        return getSite().getId();
    }

    /**
     * Writes the content of this editor to the given stream.
     * Possible formats are for example SWT.IMAGE_BMP, IMAGE_GIF,
     * IMAGE_JPEG, IMAGE_PNG.
     * @param stream
     * @param format
     */
    public void createImage(OutputStream stream, int format) {
        SWTGraphics g = null;
        GC gc = null;
        Image image = null;
        LayerManager layerManager = (LayerManager)
            getGraphicalViewer().getEditPartRegistry().get(LayerManager.ID);
        IFigure figure = layerManager.getLayer(LayerConstants.PRINTABLE_LAYERS);
        Rectangle r = figure.getBounds();
        try {
            image = new Image(Display.getDefault(), r.width, r.height);
            gc = new GC(image);
            g = new SWTGraphics(gc);
            g.translate(r.x * -1, r.y * -1);
            figure.paint(g);
            ImageLoader imageLoader = new ImageLoader();
            imageLoader.data = new ImageData[] { image.getImageData() };
            imageLoader.save(stream, format);
        } catch (Throwable t) {
            DroolsEclipsePlugin.log(t);
        } finally {
            if (g != null) {
                g.dispose();
            }
            if (gc != null) {
                gc.dispose();
            }
            if (image != null) {
                image.dispose();
            }
        }
    }
}
