/*
 * 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.view.rules;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.drools.compiler.lang.descr.FunctionDescr;
import org.drools.compiler.lang.descr.GlobalDescr;
import org.drools.compiler.lang.descr.QueryDescr;
import org.drools.compiler.lang.descr.RuleDescr;
import org.drools.eclipse.DRLInfo;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.ProcessInfo;
import org.drools.eclipse.core.DroolsElement;
import org.drools.eclipse.core.DroolsModelBuilder;
import org.drools.eclipse.core.Function;
import org.drools.eclipse.core.Global;
import org.drools.eclipse.core.Package;
import org.drools.eclipse.core.Process;
import org.drools.eclipse.core.Query;
import org.drools.eclipse.core.Rule;
import org.drools.eclipse.core.RuleSet;
import org.drools.eclipse.core.ui.DroolsContentProvider;
import org.drools.eclipse.core.ui.DroolsLabelProvider;
import org.drools.eclipse.core.ui.DroolsTreeSorter;
import org.drools.eclipse.core.ui.FilterActionGroup;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.viewers.DoubleClickEvent;
import org.eclipse.jface.viewers.IDoubleClickListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.part.ViewPart;
import org.eclipse.ui.texteditor.ITextEditor;

public class RulesView extends ViewPart implements IDoubleClickListener, IResourceVisitor, IResourceChangeListener {

    private final RuleSet ruleSet = DroolsModelBuilder.createRuleSet();

    private Map<IFile, List<DroolsElement>> resourcesMap = new HashMap<IFile, List<DroolsElement>>();
    private TreeViewer treeViewer;

    public void createPartControl(Composite parent) {
        treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL);
        treeViewer.setContentProvider(new DroolsContentProvider());
        treeViewer.setLabelProvider(new DroolsLabelProvider());
        treeViewer.setSorter(new DroolsTreeSorter());
        treeViewer.addDoubleClickListener(this);
        treeViewer.setUseHashlookup(true);
        treeViewer.setInput(ruleSet);
        FilterActionGroup filterActionGroup = new FilterActionGroup(
            treeViewer, "org.drools.eclipse.view.rules.RulesView");
        filterActionGroup.fillActionBars(getViewSite().getActionBars());
    }

    public void init(IViewSite site, IMemento memento) throws PartInitException {
        super.init(site, memento);
        try {
            ResourcesPlugin.getWorkspace().getRoot().accept(this);
        } catch (CoreException e) {
            DroolsEclipsePlugin.log(e);
        }
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this);
    }

    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    public void setFocus() {
        treeViewer.getControl().setFocus();
    }

    public boolean visit(IResource resource) throws CoreException {
        return updateResource(resource);
    }

    private boolean updateResource(IResource resource) {
        IProject project = resource.getProject();
        if (project != null) {
            IJavaProject javaProject = JavaCore.create(project);
            if (!javaProject.exists()) {
                return false;
            }
            if (resource instanceof IFile
                    && javaProject.isOnClasspath(resource)) {
                IFile file = (IFile) resource;
                if ("drl".equals(resource.getFileExtension())
                            || "dslr".equals(resource.getFileExtension())) {
                    try {
                        DRLInfo drlInfo = DroolsEclipsePlugin.getDefault().parseResource(resource, false);
                        String packageName = drlInfo.getPackageName();
                        Package pkg = ruleSet.getPackage(packageName);
                        if (pkg == null) {
                            pkg = DroolsModelBuilder.addPackage(ruleSet, packageName, 0, 0);
                        }
                        if (drlInfo.getBuilderErrors().length > 0 || drlInfo.getPackageDescr() == null) {
                            return false;
                        }
                        // add rules
                        List<RuleDescr> rules = drlInfo.getPackageDescr().getRules();
                        for (RuleDescr ruleDescr : rules) {
                            boolean isQuery = ruleDescr instanceof QueryDescr;
                            String ruleName = ruleDescr.getName();
                            if (!isQuery) {
                                Rule rule = DroolsModelBuilder.addRule(
                                    pkg, ruleName, file, ruleDescr.getStartCharacter(),
                                    ruleDescr.getEndCharacter() - ruleDescr.getStartCharacter() + 1, null);
                                // create link between resource and created rule nodes
                                List<DroolsElement> droolsElements = resourcesMap.get(file);
                                if (droolsElements == null) {
                                    droolsElements = new ArrayList<DroolsElement>();
                                    resourcesMap.put(file, droolsElements);
                                }
                                droolsElements.add(rule);
                            } else {
                                Query query = DroolsModelBuilder.addQuery(
                                    pkg, ruleName, file, ruleDescr.getStartCharacter(),
                                    ruleDescr.getEndCharacter() - ruleDescr.getStartCharacter() + 1);
                                // create link between resource and created rule nodes
                                List<DroolsElement> droolsElements = resourcesMap.get(file);
                                if (droolsElements == null) {
                                    droolsElements = new ArrayList<DroolsElement>();
                                    resourcesMap.put(file, droolsElements);
                                }
                                droolsElements.add(query);
                            }
                        }
                        // add globals
                        List<GlobalDescr> globals = drlInfo.getPackageDescr().getGlobals();
                        for (GlobalDescr globalDescr : globals) {
                            Global global = DroolsModelBuilder.addGlobal(
                                pkg, globalDescr.getIdentifier(), file, globalDescr.getStartCharacter(),
                                globalDescr.getEndCharacter() - globalDescr.getStartCharacter() + 1);
                            // create link between resource and created rule nodes
                            List<DroolsElement> droolsElements = resourcesMap.get(file);
                            if (droolsElements == null) {
                                droolsElements = new ArrayList<DroolsElement>();
                                resourcesMap.put(file, droolsElements);
                            }
                            droolsElements.add(global);
                        }
                        // add functions
                        List<FunctionDescr> functions = drlInfo.getPackageDescr().getFunctions();
                        for (FunctionDescr functionDescr : functions) {
                            String functionName = functionDescr.getName();
                            Function function = DroolsModelBuilder.addFunction(
                                pkg, functionName, file, functionDescr.getStartCharacter(),
                                functionDescr.getEndCharacter() - functionDescr.getStartCharacter() + 1);
                            // create link between resource and created rule nodes
                            List<DroolsElement> droolsElements = resourcesMap.get(file);
                            if (droolsElements == null) {
                                droolsElements = new ArrayList<DroolsElement>();
                                resourcesMap.put(file, droolsElements);
                            }
                            droolsElements.add(function);
                        }
                    } catch (Throwable t) {
                        DroolsEclipsePlugin.log(t);
                    }
                    return false;
                } else if ("rf".equals(resource.getFileExtension())) {
                    try {
                        String processString = convertToString(file.getContents());
                        ProcessInfo processInfo = DroolsEclipsePlugin.getDefault().parseProcess(processString, resource);
                        if (processInfo != null && processInfo.getProcess() != null) {
                            String packageName = processInfo.getProcess().getPackageName();
                            Package pkg = ruleSet.getPackage(packageName);
                            if (pkg == null) {
                                pkg = DroolsModelBuilder.addPackage(ruleSet, packageName, 0, 0);
                            }
                            Process process = DroolsModelBuilder.addProcess(pkg, processInfo.getProcess().getId(), file);
                            List<DroolsElement> droolsElements = resourcesMap.get(file);
                            if (droolsElements == null) {
                                droolsElements = new ArrayList<DroolsElement>();
                                resourcesMap.put(file, droolsElements);
                            }
                            droolsElements.add(process);
                        }
                    } catch (Throwable t) {
                        DroolsEclipsePlugin.log(t);
                    }
                    return false;
                }
            }
        }
        return true;
    }

    public void resourceChanged(final IResourceChangeEvent event) {
        try {
            if (event.getType() == IResourceChangeEvent.POST_CHANGE) {
                IResourceDelta delta = event.getDelta();
                if (delta != null) {
                    delta.accept(new IResourceDeltaVisitor() {
                        public boolean visit(IResourceDelta delta) throws CoreException {
                            IResource resource = delta.getResource();
                            removeElementsFromResource(resource);
                            boolean result = true;
                            if (delta.getKind() != IResourceDelta.REMOVED) {
                                result = updateResource(resource);
                            }
                            treeViewer.getControl().getDisplay().asyncExec(
                                new Runnable() {
                                    public void run() {
                                        if (!treeViewer.getControl().isDisposed()) {
                                            treeViewer.refresh();
                                        }
                                    }
                                }
                            );
                            return result;
                        }
                    });
                }
            } else if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
                IResource resource = event.getResource();
                if (resource != null) {
                    resource.accept(new IResourceVisitor() {
                        public boolean visit(IResource resource) throws CoreException {
                            removeElementsFromResource(resource);
                            return true;
                        }
                    });
                }
            } else if (event.getType() == IResourceChangeEvent.PRE_CLOSE) {
                IResource resource = event.getResource();
                if (resource != null) {
                    resource.accept(new IResourceVisitor() {
                        public boolean visit(IResource resource) throws CoreException {
                            removeElementsFromResource(resource);
                            return true;
                        }
                    });
                }
            }
        } catch (Throwable t) {
            DroolsEclipsePlugin.log(t);
        }
    }

    private void removeElementsFromResource(IResource resource) {
        List<DroolsElement> droolsElements = resourcesMap.get(resource);
        if (droolsElements != null) {
            for (DroolsElement droolsElement : droolsElements) {
                DroolsModelBuilder.removeElement(droolsElement);
            }
            resourcesMap.remove(resource);
        }
    }

    public void doubleClick(DoubleClickEvent event) {
        ISelection selection = event.getSelection();
        if (selection instanceof IStructuredSelection) {
            Object selected = ((StructuredSelection) selection).getFirstElement();
            if (selected != null && selected instanceof DroolsElement) {
                DroolsElement droolsSelected = (DroolsElement) selected;
                IFile file = droolsSelected.getFile();
                if (file != null) {
                    try {
                        IEditorPart editor = IDE.openEditor(getSite().getPage(), file);
                        if (editor instanceof FormEditor) {
                            editor = ((FormEditor) editor).getActiveEditor();
                        }
                        if (editor instanceof ITextEditor) {
                            ((ITextEditor)editor).selectAndReveal(
                                droolsSelected.getOffset(), droolsSelected.getLength());
                        }
                    } catch (Throwable t) {
                        DroolsEclipsePlugin.log(t);
                    }
                }
            }
        }
    }

    private static String convertToString(final InputStream inputStream) throws IOException {
        Reader reader = new InputStreamReader(inputStream);
        final StringBuffer text = new StringBuffer();
        final char[] buf = new char[1024];
        int len = 0;
        while ((len = reader.read(buf)) >= 0) {
            text.append(buf, 0, len);
        }
        return text.toString();
    }
}
