/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.netbeans.modules.xml.catalog;

import java.awt.event.*;
import java.awt.*;
import java.beans.*;
import java.util.*;
import java.io.*;
import java.net.*;

import javax.swing.event.*;

import org.openide.*;
import org.openide.nodes.*;
import org.openide.util.actions.*;
import org.openide.util.datatransfer.*;
import org.openide.actions.*;

import org.netbeans.modules.xml.catalog.spi.*;
import org.netbeans.modules.xml.catalog.impl.*;
import org.netbeans.modules.xml.catalog.settings.CatalogSettings;
import org.openide.util.HelpCtx;
import java.awt.event.ActionEvent;
import javax.swing.*;
import org.openide.util.NbBundle;

/**
 * Node representing catalog root in the Runtime tab. It retrieves all
 * mounted catalogs from current project settings.
 *
 * To be registered in manifest file as:
 * <pre>
 * Name: org.netbeans.modules.xml.catalog.CatalogNode.class
 * OpenIDE-Module-Class: Environment
 * </pre>
 *
 * <p><b>Implementation Note:</b>
 * <p>The node has session lifetime but its model has project lifetime, so there
 * is implemented a logic for model instance changing (see children).
 *
 * @author  Petr Kuzel
 * @version 1.0
 */
public final class CatalogRootNode extends AbstractNode implements Node.Cookie {

    /** Creates new CatalogNode */
    public CatalogRootNode() {
        super(new RootChildren());
        setName("XML-CATALOG");                                                 // NOI18N
        setDisplayName (NbBundle.getMessage(CatalogRootNode.class, "TEXT_catalog_root"));              // NOI18N
        setIconBaseWithExtension("org/netbeans/modules/xml/catalog/resources/catalog-root.gif"); // NOI18N
        setShortDescription(NbBundle.getMessage(CatalogRootNode.class, "PROP_catalog_root_desc"));
        getCookieSet().add(this);
    }
    
    protected SystemAction[] createActions() {
        return new SystemAction[] {
            SystemAction.get(CatalogRootNode.MountAction.class),
            null,
            SystemAction.get(PropertiesAction.class)
        };
    }

    /** We can mount entity catalogs. */
//    public NewType[] getNewTypes() {
//        return new NewType[] {new CatalogMounter()};
//    }
    
    /** 
     * Mounts new catalalog as specified by user. 
     */
    class CatalogMounter extends NewType implements ActionListener {

        CatalogMounterModel model = null;
        Dialog myDialog = null;
        
        public void create() throws IOException {
            
            Iterator it = ProvidersRegistry.getProviderClasses(new Class[] {CatalogReader.class});
            
            model = new CatalogMounterModel(it);
            Object rpanel = new CatalogMounterPanel(model);
            DialogDescriptor dd = new DialogDescriptor(rpanel,
                                  NbBundle.getMessage(CatalogRootNode.class, "PROP_Mount_Catalog"), true, this);
            dd.setHelpCtx(new HelpCtx(CatalogMounterPanel.class));
            myDialog = DialogDisplayer.getDefault().createDialog(dd);

            // set dialog size to 60x10 characters
            
            JTextArea template = new JTextArea();
            template.setColumns(60);
            template.setRows(8 + 2);  // 8 lines, 2 bottom line with buttons
            Dimension dimension = template.getPreferredSize();
            
            //#33996 this is insets size as prescribed by UI guidelines            
            final int insets = 12;
            
            // small fonts have problems that insets are times more important
            // then font size, include also insets
            int heightInsets = dimension.height + 10 * insets;  // 10 lines * 12 inset size
            int widthInsets = dimension.width + 4 * insets;
            Dimension fullDimension = new Dimension(widthInsets, heightInsets);
            myDialog.setSize(fullDimension);  //^ packing never creates bigger window :-(
            
            myDialog.setVisible(true);
        }

        public void actionPerformed(ActionEvent ae) {
            if (ae.getSource() == DialogDescriptor.OK_OPTION) {
                
                Object catalog = model.getCatalog();
                if (catalog == null) return;
                CatalogSettings mounted = CatalogSettings.getDefault();
                mounted.addCatalog((CatalogReader)catalog);
                
            }
            if (myDialog != null) {
                myDialog.dispose();
                myDialog = null;
            }
        }
        
        public String getName() {
            return NbBundle.getMessage(CatalogRootNode.class, "LBL_mount"); // NOI18N
        }
    }
    

    // ~~~~~~~~~~~~~~~~~~~~~~ Serialization stuff ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("Reading CatalogRoot node " + this); // NOI18N

        in.defaultReadObject();        
    }
    
    private void writeObject(ObjectOutputStream out) throws IOException {
        //if ( Util.THIS.isLoggable() ) /* then */ Util.THIS.debug("Writing " + this); // NOI18N

        out.defaultWriteObject();        
    }
    
    public HelpCtx getHelpCtx() {
        //return new HelpCtx(CatalogRootNode.class);
        return HelpCtx.DEFAULT_HELP;
    }
    
    // ~~~~~~~~~~~~~~~~~~~~~~ NODE KIDS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    

    /**
     * Kids driven by CatalogSettings. Only one instance may be used
     * since redefined equals() method.
     */
    private static class RootChildren extends Children.Keys implements Comparator, PropertyChangeListener {
        
        /** Contains CatalogReader instances. */
        private final Set<CatalogReader> keys = new TreeSet<>(this);
        
        /**
          * Create new keys, register itself as listener.
          */
        public synchronized void addNotify() {            
            CatalogSettings mounted = CatalogSettings.getDefault();
            mounted.addPropertyChangeListener(this);
            createKeys(mounted);                        
        }

        /**
          * Remove listener and keys.
          */
        public synchronized void removeNotify() {
            CatalogSettings mounted = CatalogSettings.getDefault();
            if (mounted != null) mounted.removePropertyChangeListener(this);
            keys.clear();
            setKeys(keys);
        }

        /**
          * Create CatalogNodes initialized by provider instance.
          */
        public Node[] createNodes(Object key) {        
            try {
                return new Node[] { new CatalogNode((CatalogReader)key) };
            } catch (IntrospectionException ex) {
                return new Node[] {};
            }
        }

        /**
          * The only instance (see equals) listens on ProvidersRegistry
          * for its state changes.
          */
        public synchronized void propertyChange(PropertyChangeEvent e) {
            if (CatalogSettings.PROP_MOUNTED_CATALOGS.equals(e.getPropertyName())) {
                createKeys((CatalogSettings)e.getSource());
            } 
        }
        
        /** 
          * Creates new keys according to CatalogSettings.
          */
        private void createKeys(CatalogSettings mounted) {
            keys.clear();
            if (mounted != null) {
                Iterator<CatalogReader> it = mounted.getCatalogs(new Class[] {CatalogReader.class});
                while (it.hasNext()) {
                    keys.add(it.next());    //!!! use immutable key wrappers, some
                                            // instances may overwrite equals() so
                                            // they cannot be used as a children key
                }
            }
            setKeys(keys);
        }
        
        /** 
          * We are also comparators. Use class based equality.
          */
        public boolean equals(java.lang.Object peer) {
            return peer.getClass().equals(getClass());
        }        
        
        /**
         * Compare keys giving highest priority to system catalog.
         * Other catalogs sort by display name if available.
         */
        public int compare(java.lang.Object one,java.lang.Object two) {
            if (one == two) return 0;
            if (one instanceof SystemCatalogReader) return -1;
            if (two instanceof SystemCatalogReader) return 1;
            if (one instanceof CatalogDescriptorBase && two instanceof CatalogDescriptorBase) {
                int test = (((CatalogDescriptorBase)one).getDisplayName()).compareTo(
                    ((CatalogDescriptorBase)two).getDisplayName()
                );
                if (test != 0) return test;
                
            } else {
                if (one instanceof CatalogDescriptorBase) return -1;
                if (two instanceof CatalogDescriptorBase) return 1;
            }
            // show all catalogs never return 0
            return (long)one.hashCode() - (long)two.hashCode() > 0L ? 1 : -1;
        }
        
    }

    
    /**
     * Give to action your own name
     */
    private static final class MountAction extends NodeAction {
        /** Serial Version UID */
        private static final long serialVersionUID = -3608629636833099065L;
        
        public MountAction() {
        }
        
        public String getName() {
            return NbBundle.getMessage(CatalogRootNode.class, "LBL_mount");
        }
        
        public HelpCtx getHelpCtx() {
            return new HelpCtx(MountAction.class);
        }
        
        protected synchronized boolean enable(Node[] activatedNodes) {
            return activatedNodes.length > 0;
        }
        
        protected boolean asynchronous() {
            return false;
        }
        
        protected synchronized void performAction(Node[] activatedNodes) {
            mountCatalog(activatedNodes);
        }
    }

    public static void mountCatalog(Node[] activatedNodes) {
        if (activatedNodes.length == 0) return;
        try {
            Node current = activatedNodes[0];
            CatalogRootNode me = current.getCookie(CatalogRootNode.class);
            CatalogMounter newType = me.new CatalogMounter();
            newType.create();
        } catch (IOException ex) {
            //Util.THIS.debug(ex);
        } finally {
        }
    }
}
