/*
 * Copyright 2010 JBoss Inc
 *
 * 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.rulebuilder.editors;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.drools.compiler.DrlParser;
import org.drools.eclipse.DroolsEclipsePlugin;
import org.drools.eclipse.dsl.editor.DSLAdapter;
import org.drools.eclipse.editors.DRLDocumentProvider;
import org.drools.eclipse.editors.DRLRuleEditor;
import org.drools.eclipse.util.ProjectClassLoader;
import org.drools.ide.common.client.modeldriven.SuggestionCompletionEngine;
import org.drools.ide.common.server.rules.SuggestionCompletionLoader;
import org.drools.ide.common.server.util.BRDRLPersistence;
import org.drools.ide.common.server.util.BRXMLPersistence;
import org.drools.lang.dsl.DSLTokenizedMappingFile;
import org.eclipse.core.internal.resources.Container;
import org.eclipse.core.resources.IFile;
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.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.texteditor.DocumentProviderRegistry;
import org.eclipse.ui.texteditor.IDocumentProvider;

public class RuleEditor extends FormEditor
    implements
    IResourceChangeListener {

    private BrlPage                    guidedEditor;

    private TextEditor                 xmlEditor          = new TextEditor();

    private SuggestionCompletionEngine completion;

    private SuggestionCompletionLoader loader;

    private FileEditorInput            packageEditorInput;

    private final Document             drlDocument;

    private IResourceChangeListener    packageFileTracker = new IResourceChangeListener() {

                                                              public void resourceChanged(IResourceChangeEvent event) {
                                                                  if (packageEditorInput != null) {
                                                                      IResourceDelta delta = getRootDelta( event.getDelta() );

                                                                      IPath p1 = delta.getFullPath();
                                                                      IPath p2 = packageEditorInput.getFile().getFullPath();
                                                                      if ( p1.equals( p2 ) ) {
                                                                          reloadCompletionEngine();
                                                                      }
                                                                  }                                                              }

                                                          };

    public RuleEditor() {
        super();
        ResourcesPlugin.getWorkspace().addResourceChangeListener( this );
        ResourcesPlugin.getWorkspace().addResourceChangeListener( packageFileTracker,
                                                                  IResourceChangeEvent.POST_CHANGE );
        drlDocument = new Document();

    }

    protected FormToolkit createToolkit(Display display) {
        // Create a toolkit that shares colors between editors.
        return new FormToolkit( DroolsEclipsePlugin.getDefault().getRuleBuilderFormColors( display ) );
    }

    protected void addPages() {
        guidedEditor = new BrlPage( this );
        try {
            addPage( guidedEditor );
            addPage( xmlEditor,
                     getEditorInput() );

            DRLRuleEditor drlEditor = new DRLRuleEditor() {
                protected IDocumentProvider createDocumentProvider() {
                    return new DRLDocumentProvider() {
                        public boolean isReadOnly(Object element) {
                            return true;
                        }

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

                        protected IDocument getParentDocument(Object element) {
                            return drlDocument;
                        }
                    };
                }

            };

            addPage( drlEditor,
                     xmlEditor.getEditorInput() );
            IPath packagePath = getCurrentDirectoryPath( getEditorInput() );
            if (packagePath != null) {
                packagePath = packagePath.append( "drools.package" );
                IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile( packagePath );
                IJavaProject javaProject = JavaCore.create( file.getProject() );
                ClassLoader classLoader = ProjectClassLoader.getProjectClassLoader( javaProject );
                loader = new SuggestionCompletionLoader( classLoader );
                if ( !file.exists() ) {
                    String defaultHeader = "//This is a package configuration file";
                    defaultHeader += "\n//Add imports, globals etc here which will be used by all the rule assets in this folder.";
                    InputStream is = new ByteArrayInputStream( defaultHeader.getBytes() );
                    try {
                        file.create( is,
                                     true,
                                     null );
                    } catch ( CoreException e ) {
                        DroolsEclipsePlugin.log( e );
                    }
                }
                packageEditorInput = new FileEditorInput( file );
            }
            reloadCompletionEngine();

            setPageText( 1,
                         "BRL Source" );

            setPageText( 2,
                         "Generated DRL (read-only)" );

            updateName( false );

        } catch ( PartInitException e ) {
            DroolsEclipsePlugin.log( e );
        }
    }

    private void updateName(boolean forced) {
        String name = xmlEditor.getTitle();
        setPartName( name );

        //TODO Add support for other than .brl extensions
        if ( guidedEditor.getModeller() != null && guidedEditor.getModeller().getModel() != null && (guidedEditor.getModeller().getModel().name == null || forced) ) {
            String shortName = name.substring( 0,
                                               name.length() - ".brl".length() );
            guidedEditor.getModeller().getModel().name = shortName;
        }

        updateDRLPage();

    }

    private IPath getCurrentDirectoryPath(IEditorInput editorInput) {
        if (editorInput instanceof FileEditorInput) {
            return ((FileEditorInput) editorInput).getFile().getFullPath().removeLastSegments( 1 ).addTrailingSeparator();
        }
        return null;
    }

    private void reloadCompletionEngine() {

        if (packageEditorInput == null) {
            completion = new SuggestionCompletionLoader( null ).getSuggestionEngine( "",
                new ArrayList(),
                new ArrayList() );
            return;
        }

        try {

            // Load all .dsl files from current dir
            IPath p = (packageEditorInput).getFile().getFullPath().removeLastSegments( 1 );

            Container folder = (Container) ResourcesPlugin.getWorkspace().getRoot().findMember( p,                                                                                                false );

            IResource[] files = folder.members( false );

            List dslList = new ArrayList();
            List enumList = new ArrayList();

            for ( int i = 0; i < files.length; i++ ) {
                String fn = files[i].getName();
                if ( fn.endsWith( ".dsl" ) ) {
                    String contents = getFileContents( (IFile) files[i] );
                    DSLTokenizedMappingFile dsl = new DSLTokenizedMappingFile();

                    if ( dsl.parseAndLoad( new StringReader( contents ) ) ) {
                        dslList.add( dsl );
                    } else {
                        //TODO report dsl parse error
                    }
                } else if ( fn.endsWith( ".enumeration" ) ) {
                    String contents = getFileContents( (IFile) files[i] );
                    enumList.add(contents);
                }
                
            }

            // Load suggestion engine
            String str = getFileContents( packageEditorInput.getFile() );

            completion = loader.getSuggestionEngine( str,
                                                     Collections.EMPTY_LIST,
                                                     dslList,
                                                     enumList);

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

    private String getFileContents(IFile file) {

        InputStream is;
        try {
            is = file.getContents();

            StringBuffer out = new StringBuffer();
            byte[] b = new byte[4096];
            for ( int n; (n = is.read( b )) != -1; ) {
                out.append( new String( b,
                                        0,
                                        n ) );
            }

            return out.toString();
        } catch ( CoreException e ) {
            //TODO Report problem with the file loading
            return "";
        } catch ( IOException e ) {
            //TODO Report problem with the file loading
            return "";
        }

    }

    public boolean isDirty() {
        return xmlEditor.isDirty() || guidedEditor.isDirty();
    }

    protected void pageChange(int newPageIndex) {
        super.pageChange( newPageIndex );

        IDocument document = getInputDocument();

        if ( newPageIndex == 0 ) {

            boolean newModel = guidedEditor.getModeller().getModel() == null;
            if ( xmlEditor.isDirty() || newModel ) {
                guidedEditor.setModelXML( document.get() );
                if ( newModel ) {
                    guidedEditor.getModeller().setDirty( false );
                }
                updateName( false );
            }

            guidedEditor.refresh();

        } else if ( newPageIndex == 1 ) {

            if ( guidedEditor.isDirty() ) {
                document.set( BRXMLPersistence.getInstance().marshal( guidedEditor.getRuleModel() ) );
            }
        } else if ( newPageIndex == 2 ) {

            //Updating main document for proper workflow when page is switched back to "0"
            if ( guidedEditor.isDirty() ) {
                document.set( BRXMLPersistence.getInstance().marshal( guidedEditor.getRuleModel() ) );
            } else if ( xmlEditor.isDirty() ) {
                guidedEditor.setModelXML( document.get() );
            }

            updateDRLPage();

            updateName( false );

        }

    }

    private void updateDRLPage() {

        String drl = "";
        try {
            drl = BRDRLPersistence.getInstance().marshal( guidedEditor.getRuleModel() );

            IResource resource = ResourceUtil.getResource( xmlEditor.getEditorInput() );

            Reader reader = DSLAdapter.getDSLContent( drl,
                                                      resource );
            DrlParser parser = new DrlParser();

            if ( reader != null ) {
                drl = parser.getExpandedDRL( drl,
                                             reader );
            }

        } catch ( Throwable t ) {

            StringWriter strwriter = new StringWriter();
            t.printStackTrace( new PrintWriter( strwriter ) );
            drl = "\nPROBLEM WITH THE DRL CONVERSION!\n\n\nDRL:\n" + drl + "\n\nSTACKTRACE:\n" + strwriter.toString();
        }
        drlDocument.set( drl );
    }

    public void doSave(IProgressMonitor monitor) {
        IDocument document = getInputDocument();

        if ( xmlEditor.isDirty() ) {
            guidedEditor.setModelXML( document.get() );
        } else if ( guidedEditor.isDirty() ) {
            document.set( BRXMLPersistence.getInstance().marshal( guidedEditor.getRuleModel() ) );
        }

        xmlEditor.doSave( monitor );

        guidedEditor.getModeller().setDirty( false );

        guidedEditor.refresh();

    }

    private IDocument getInputDocument() {
        IEditorInput input = getEditorInput();
        IDocumentProvider docProvider = DocumentProviderRegistry.getDefault().getDocumentProvider( input );
        IDocument document = docProvider.getDocument( input );
        return document;
    }

    public void doSaveAs() {
        xmlEditor.doSaveAs();
        guidedEditor.getModeller().setDirty( false );
        updateName( true );
        setInput( xmlEditor.getEditorInput() );

        guidedEditor.refresh();

    }

    public boolean isSaveAsAllowed() {
        return true;
    }

    public void resourceChanged(final IResourceChangeEvent event) {
        if ( event.getType() == IResourceChangeEvent.PRE_CLOSE ) {
            Display.getDefault().asyncExec( new Runnable() {
                public void run() {
                    IWorkbenchPage[] pages = getSite().getWorkbenchWindow().getPages();
                    for ( int i = 0; i < pages.length; i++ ) {
                        if ( ((FileEditorInput) xmlEditor.getEditorInput()).getFile().getProject().equals( event.getResource() ) ) {
                            IEditorPart editorPart = pages[i].findEditor( xmlEditor.getEditorInput() );
                            pages[i].closeEditor( editorPart,
                                                  true );
                        }
                    }
                }
            } );
        }
    }

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

    public void dirtyPropertyChanged() {
        firePropertyChange( IEditorPart.PROP_DIRTY );
        guidedEditor.refresh();
    }

    public SuggestionCompletionEngine getCompletionEngine() {
        return completion;
    }

    private IResourceDelta getRootDelta(IResourceDelta delta) {
        if ( delta.getAffectedChildren().length > 0 ) {
            return getRootDelta( delta.getAffectedChildren()[0] );
        }
        return delta;
    }

}
