/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package cz.karry.dop.nbmodule.project;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.swing.Action;
import javax.swing.JSeparator;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.spi.project.SubprojectProvider;
import org.netbeans.spi.project.support.ant.AntProjectEvent;
import org.netbeans.spi.project.support.ant.AntProjectListener;
import org.netbeans.spi.project.ui.LogicalViewProvider;
import org.netbeans.spi.project.ui.support.CommonProjectActions;
import org.openide.ErrorManager;
import org.openide.filesystems.FileAttributeEvent;
import org.openide.filesystems.FileChangeListener;
import org.openide.filesystems.FileEvent;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileRenameEvent;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFilter;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.nodes.NodeNotFoundException;
import org.openide.nodes.NodeOp;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
import org.openide.util.lookup.Lookups;

/**
 *
 * @author karry
 */
public class DopLogicalViewProvider implements LogicalViewProvider, AntProjectListener {
    private SubprojectProvider mySubProjectProvider;
    private DopProject myProject;
    static final String SOURCE_ROOT_NODE_NAME = "LBL_DopFiles";
    //private final Map<Command, Action> myActionsByCommand;

    DopLogicalViewProvider(DopProject project, SubprojectProvider provider) {
        myProject = project;
        mySubProjectProvider = provider;
        //myActionsByCommand = new HashMap<Command, Action>();
        getProject().getHelper().addAntProjectListener(this);
    }

    public Node createLogicalView() {
        return new DopLogicalViewRootNode();
    }

    public Node findPath(Node root, Object target) {
        // Check each child node in turn.
        Node[] children = root.getChildren().getNodes(true);
        for (Node node : children) {
            if (target instanceof DataObject || target instanceof FileObject) {
                DataObject d = node.getLookup().lookup(DataObject.class);
                if (d == null) {
                    continue;
                }
                // Copied from
                // org.netbeans.spi.java.project.support.ui.TreeRootNode.PathFinder.findPath:
                FileObject kidFO = d.getPrimaryFile();
                FileObject targetFO = target instanceof DataObject ? ((DataObject) target).getPrimaryFile() : (FileObject) target;
                if (kidFO == targetFO) {
                    return node;
                } else if (FileUtil.isParentOf(kidFO, targetFO)) {
                    String relPath = FileUtil.getRelativePath(kidFO, targetFO);
                    List path = Collections.list(new /*<String>*/ StringTokenizer(relPath, "/")); // NOI18N
                    // XXX see original code for justification
                    path.set(path.size() - 1, targetFO.getName());
                    try {
                        Node found = NodeOp.findPath(node, Collections.enumeration(path));

                        if (hasObject(found, target)) {
                            return found;
                        }
                        Node parent = found.getParentNode();
                        Children kids = parent.getChildren();
                        children = kids.getNodes();
                        for (Node child : children) {
                            if (hasObject(child, target)) {
                                return child;
                            }
                        }
                    } catch (NodeNotFoundException e) {
                        return null;
                    }
                }
            }
        }
        return null;
        
    }

    public void configurationXmlChanged(AntProjectEvent ev) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void propertiesChanged(AntProjectEvent ev) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    private DopProject getProject() {
        return myProject;
    }

    private boolean hasObject(Node node, Object obj) {
        if (obj == null) {
            return false;
        }
        DataObject dataObject = node.getLookup().lookup(DataObject.class);
        if (dataObject == null) {
            return false;
        }
        if (obj instanceof DataObject) {
            if (dataObject.equals(obj)) {
                return true;
            }
            FileObject fileObject = ((DataObject) obj).getPrimaryFile();
            return hasObject(node, fileObject);
        } else if (obj instanceof FileObject) {
            FileObject fileObject = dataObject.getPrimaryFile();
            return obj.equals(fileObject);
        } else {
            return false;
        }
    }
    
    
    private class DopLogicalViewRootNode extends org.openide.nodes.AbstractNode {    

        private DopLogicalViewRootNode() {
            super(new LogicalViewChildren(), Lookups.fixed(new Object[]{getProject()}));
            
            setIconBaseWithExtension("/cz/karry/dop/nbmodule/microprocessor_small.png" );
            
            setName(ProjectUtils.getInformation(myProject).getDisplayName());
        }
        
        @Override
        public Action[] getActions(boolean context) {
            if (context) {
                return super.getActions(context);
            } else {
                return getAdditionalActions();
            }
        }

        private Action[] getAdditionalActions() {
            List<Action> list = new LinkedList<Action>();

            list.add(0, CommonProjectActions.newFileAction());

            // actions from provider
            /*
            for (Action action : getProviderActions()){
                list.add(action);
            }
             */

            // get our project actions
            /* removed run.script from project root node
             for (Action action : getProjectActions()){
                list.add(action);
            }*/
                
            // get standard project actions
            for (Action action : getStandardProjectActions()){
                list.add(action);
            }
	    
	       //Custom Actions for Project Nodes - #57874
            Collection<? extends Object> res = Lookups.forPath("Projects/Actions").lookupAll(Object.class); // NOI18N
            if (!res.isEmpty()) {
                list.add(null);
                for (Object next : res) {
                    if (next instanceof Action) {
                        list.add((Action) next);
                    } else if (next instanceof JSeparator) {
                        list.add(null);
                    }
                }
            }
            list.add(null);
            list.add(CommonProjectActions.customizeProjectAction());            
            return list.toArray(new Action[list.size()]);
        }
        
        private Action[] getStandardProjectActions(){
            // do not get keysList from actionProvider.getStandardProjectCommands()
            // because it will give only keysList of commands that we support outselves
            // and will not allow desired formatting
            return new Action[]{
                        null, 
                        CommonProjectActions.setAsMainProjectAction(), 
                        CommonProjectActions.openSubprojectsAction(), 
                        CommonProjectActions.closeProjectAction(), 
                        null, 
                        CommonProjectActions.renameProjectAction(), 
                        CommonProjectActions.moveProjectAction(), 
                        CommonProjectActions.copyProjectAction(), 
                        CommonProjectActions.deleteProjectAction()
            };
            
        }
        
        
    }
    
    private class LogicalViewChildren extends Children.Keys 
        implements ChangeListener, PropertyChangeListener, FileChangeListener{
        private ChangeListener sourcesListener;
        private java.util.Map groupsListeners;


        /* (non-Javadoc)
         * @see org.openide.nodes.Children#addNotify()
         */
        @Override
        protected void addNotify() {
            super.addNotify();
            getProject().getHelper().getProjectDirectory().addFileChangeListener(this);
            createNodes();
        }
        
        @Override
        protected Node[] createNodes(Object key) {
            Node node = null;
            if (key instanceof SourceGroup) {
                SourceGroup sourceGroup = (SourceGroup) key;
                DataFolder folder = getFolder(sourceGroup.getRootFolder());
                if (folder != null) {
                    /* no need to use sourceGroup.getDisplayName() while we have only one sourceRoot.
                     * Now it contains not good-looking label.
                     * We put label there in PhpSources.configureSources()
                     */
                    //node = new SrcNode(folder, sourceGroup.getDisplayName());
                    node = new SrcNode(folder);
                }
            }
            return node == null ? new Node[]{} : new Node[]{node};
        }
        
        private void createNodes() {
            // update Sources listeners
            Sources sources = ProjectUtils.getSources(myProject);
            updateSourceListeners(sources);

            // parse SG
            // update SG listeners
            // TODO check if this is necessary
        
            final SourceGroup[] sourceGroups = sources.getSourceGroups(Sources.TYPE_GENERIC);
            updateSourceGroupsListeners(sourceGroups);
            final SourceGroup[] groups = new SourceGroup[sourceGroups.length];
            System.arraycopy(sourceGroups, 0, groups, 0, sourceGroups.length);

            List<Object> keysList = new ArrayList<Object>(groups.length);
            //Set<FileObject> roots = new HashSet<FileObject>();
            FileObject fileObject = null;
            for (int i = 0; i < groups.length; i++) {
                fileObject = groups[i].getRootFolder();
                DataFolder srcDir = getFolder(fileObject);

                if (srcDir != null) {
                    keysList.add(groups[i]);
                }
                //roots.add(fileObject);
            }
            if (keysList.size() > 0) {
                setKeys(keysList);
            }
            // Seems that we do not need to implement FileStatusListener
            // to listen to source groups root folders changes.
            // look at RubyLogicalViewRootNode for example.
            //updateSourceRootsListeners(roots);
        }

        private DataFolder getFolder(FileObject fileObject) {
            if (fileObject != null && fileObject.isValid()) {
                try {
                    DataFolder dataFolder = DataFolder.findFolder(fileObject);
                    return dataFolder;
                } catch (Exception ex) {
                    ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
                }
            }
            return null;
        
        }

        private void updateSourceListeners(Sources sources) {
            if (sourcesListener == null) {
                sourcesListener = WeakListeners.change(this,sources);
                sources.addChangeListener(sourcesListener);
            }
        }

        private void updateSourceGroupsListeners(SourceGroup[] sourceGroups) {
            if (groupsListeners != null) {
                Iterator it = groupsListeners.keySet().iterator();
                while (it.hasNext()) {
                    SourceGroup group = (SourceGroup) it.next();
                    PropertyChangeListener pcl = (PropertyChangeListener) groupsListeners.get(group);
                    group.removePropertyChangeListener(pcl);
                }
            }
            groupsListeners = new HashMap();
            for (SourceGroup group : sourceGroups) {
                PropertyChangeListener pcl = WeakListeners.propertyChange(this, group);
                groupsListeners.put(group, pcl);
                group.addPropertyChangeListener(pcl);
            }
        }

        public void stateChanged(ChangeEvent e) {
            createNodes();
        }

        public void propertyChange(PropertyChangeEvent evt) {
             String property = evt.getPropertyName();
             if (DopProject.SRC_DIR.equals(property)) {
                createNodes();
             }
        }

        public void fileFolderCreated(FileEvent fe) {
            
        }

        public void fileDataCreated(FileEvent fe) {
            
        }

        public void fileChanged(FileEvent fe) {
            
        }

        public void fileDeleted(FileEvent fe) {

        }

        public void fileRenamed(FileRenameEvent fe) {
        }

        public void fileAttributeChanged(FileAttributeEvent fe) {
        }
        
    }
    
    private class SrcNode extends FilterNode {

        private SrcNode(DataFolder folder) {
            this(folder, NbBundle.getMessage(DopLogicalViewProvider.class, SOURCE_ROOT_NODE_NAME));
        }

        SrcNode(DataFolder folder, String name) {
            this(new FilterNode(folder.getNodeDelegate(), 
                                folder.createNodeChildren(new DopSourcesFilter()))
                 , name);
        }

        private SrcNode(FilterNode node, String name) {
            super(node, new FolderChildren(node));
            disableDelegation(DELEGATE_GET_DISPLAY_NAME 
                    | DELEGATE_SET_DISPLAY_NAME 
                    | DELEGATE_GET_SHORT_DESCRIPTION 
                    | DELEGATE_GET_ACTIONS);
            setDisplayName(name);
        }
        
        
    }
    
    
/**
     * Children for node that represents folder (SrcNode or PackageNode)
     */
    private class FolderChildren extends FilterNode.Children {

        FolderChildren(final Node originalNode) {
            super(originalNode);
        }
    }
    
    
    private class DopSourcesFilter implements DataFilter {

        public boolean acceptDataObject(DataObject obj) {
            return true;
        }
        
    }
    
}
