/*
 * 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.core.multiview;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import javax.swing.Action;
import javax.swing.SwingUtilities;
import org.netbeans.core.multiview.MultiViewModel.ActionRequestObserverFactory;
import org.netbeans.core.spi.multiview.CloseOperationHandler;
import org.netbeans.core.spi.multiview.MultiViewDescription;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.netbeans.core.spi.multiview.MultiViewElementCallback;
import org.netbeans.core.spi.multiview.SourceViewMarker;
import org.openide.awt.UndoRedo;
import org.openide.text.CloneableEditorSupport;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.TopComponent;


/** Special subclass of TopComponent which shows and handles set of
 * MultiViewElements, shows them in switchable toggle buttons style, along
 * with toolbars and actions associated with individual view elements.
 *
 *
 * @author Dafe Simonek, Milos Kleint
 */


public final class MultiViewCloneableTopComponent extends CloneableTopComponent 
                                                  implements ActionRequestObserverFactory, 
                                                             CloneableEditorSupport.Pane,
                                                             Splitable {

    MultiViewPeer peer;
                                           
    public MultiViewCloneableTopComponent() {
        super();
        peer = new MultiViewPeer(this, this);
        // initializes the multiview component.
        peer.initComponents();
        setFocusCycleRoot(false);
        setName("");
        // assocuate lookup needs to come after the init.. initComponents() initializes actionMap        
        associateLookup(peer.getLookup());
        
//        setFocusable(false);
    }
    
    public <T extends Serializable & Lookup.Provider> void setMimeLookup(String mimeType, T context) {
        peer.setMimeLookup(mimeType, context);
    }
    
    public void setMultiViewDescriptions(MultiViewDescription[] descriptions, MultiViewDescription defaultDesc) {
        peer.setMultiViewDescriptions(descriptions, defaultDesc);
    }
    
    public void setCloseOperationHandler(CloseOperationHandler handler) {
        peer.setCloseOperationHandler(handler);
    }
    
    private void setDeserializedMultiViewDescriptions(MultiViewDescription[] descriptions, 
                                                      MultiViewDescription defaultDesc, Map existingElements) {
        peer.setDeserializedMultiViewDescriptions(-1, descriptions, defaultDesc, null, existingElements);
    }
    
    MultiViewModel getModel() {
        return peer.getModel();
    }

    @Override
    public Lookup getLookup() {
        peer.assignLookup((MultiViewTopComponentLookup)super.getLookup());
        return super.getLookup();
    }
    
    protected void componentClosed() {
        super.componentClosed();
        peer.peerComponentClosed();
    }
    
    protected void componentShowing() {
        super.componentShowing();
        peer.peerComponentShowing();
    }
    
    protected void componentHidden() {
        super.componentHidden();
        peer.peerComponentHidden();
    }
    
    protected void componentDeactivated() {
        super.componentDeactivated();
        peer.peerComponentDeactivated();
    }
    
    protected void componentActivated() {
        super.componentActivated();
        peer.peerComponentActivated();
    }
    
    protected void componentOpened() {
        super.componentOpened();
        peer.peerComponentOpened();
    }

    /**
     * delegate to the apppropriate active element's component
     */
    public boolean requestFocusInWindow() {
        return peer.requestFocusInWindow();
    }

    /**
     * delegate to the apppropriate active element's component
     */
    public void requestFocus() {
        peer.requestFocus();
    }    
    
    /**
     * merge action for the topcomponent and the enclosed MultiViewElement..
     * 
     */
    @Override
    public Action[] getActions() {
        //TEMP don't delegate to element's actions..
        Action[] superActions = super.getActions();
        List<Action> acts = new ArrayList<Action>(Arrays.asList(peer.peerGetActions(superActions)));
        
        if( !acts.isEmpty() )
            acts.add(null);
        acts.add( new EditorsAction() );
        if( canSplit() ) {
            acts.add( new SplitAction(true) );
        }
        return acts.toArray(new Action[acts.size()]);
    }
    
    @Override
    public boolean canSplit() {
        return null != peer.model && peer.model.canSplit();
    } 
    
    public MultiViewHandlerDelegate getMultiViewHandlerDelegate() {
        // TODO have one handler only or create a new one each time?
        return peer.getMultiViewHandlerDelegate();
    }
    
    /**
     * Delegates the value to the element descriptions.
     */
    public int getPersistenceType() {
        return peer.getPersistenceType();
    }  
    
    protected String preferredID() {
        return peer.preferredID();
    }

    @Override
    protected CloneableTopComponent createClonedObject() {
        MultiViewCloneableTopComponent tc = new MultiViewCloneableTopComponent();
	tc.setMultiViewDescriptions(peer.model.getDescriptions(), getActiveDescription());
        tc.setCloseOperationHandler(peer.closeHandler);
        tc.peer.copyMimeContext(peer);
	if(getSplitOrientation() != -1) {
	    tc.splitComponent(getSplitOrientation(), -1);
	    tc.updateName();
	}
        return tc;
    }

    private MultiViewDescription getActiveDescription() {
	int splitOrientation = getSplitOrientation();
	MultiViewDescription activeDescription = peer.model.getActiveDescription();
	if (splitOrientation != -1) {
	    boolean isSplitDescription = false;
	    if (activeDescription instanceof ContextAwareDescription) {
		isSplitDescription = ((ContextAwareDescription) activeDescription).isSplitDescription();
	    }
	    if (isSplitDescription) {
		for (MultiViewDescription descr : peer.model.getDescriptions()) {
		    if (descr.getDisplayName().equals(activeDescription.getDisplayName())) {
			activeDescription = descr;
			break;
		    }
		}
	    }
	}
	return activeDescription;
    }

    @Override
    public TopComponent splitComponent(int orientation, int splitPosition) {
	peer.peerSplitComponent(orientation, splitPosition);
	return this;
    }

    @Override
    public TopComponent clearSplit(int splitElementToActivate) {
	peer.peerClearSplit(splitElementToActivate);
	return this;
    }

    @Override
    public int getSplitOrientation() {
	return peer.getSplitOrientation();
    }
    
    /** Serialize this top component.
    * Subclasses wishing to store state must call the super method, then write to the stream.
    * @param out the stream to serialize to
    */
    public void writeExternal (ObjectOutput out) throws IOException {
        super.writeExternal(out);
        peer.peerWriteExternal(out);
    }

    /** Deserialize this top component.
    * Subclasses wishing to store state must call the super method, then read from the stream.
    * @param in the stream to deserialize from
    */
    public void readExternal (ObjectInput in) throws IOException, ClassNotFoundException {
        super.readExternal(in);
        peer.peerReadExternal(in);
    }    
    
    
    Action[] getDefaultTCActions() {
        return super.getActions();
    }
    
    public MultiViewElementCallback createElementCallback(MultiViewDescription desc) {
        return SpiAccessor.DEFAULT.createCallback(new ActReqObserver(desc));
    }
    
    public CloneableTopComponent getComponent() {
        return this;
    }    
    
    public javax.swing.JEditorPane getEditorPane() {
        if (peer == null || peer.model == null) {
            return null;
        }
        MultiViewElement paneEl = findPaneElement();
        if (paneEl != null) {
            CloneableEditorSupport.Pane pane = (CloneableEditorSupport.Pane)paneEl.getVisualRepresentation();
            return pane.getEditorPane();
        }
        // hopeless case, don't try to create new elements. it's users responsibility to
        // switch to the editor element before getEditorPane() 
        return null;
    }
    
    private MultiViewElement findPaneElement() {
        MultiViewElement el = peer.model.getActiveElement(false);
        if (el != null && el.getVisualRepresentation() instanceof CloneableEditorSupport.Pane) {
            return el;
        }
        // now try a best guess.. iterate the already created elements and check if any of
        // them is a Pane
        Collection<MultiViewElement> col = peer.model.getCreatedElements();
        Iterator<MultiViewElement> it = col.iterator();
        while (it.hasNext()) {
            el = it.next();
            if (el.getVisualRepresentation() instanceof CloneableEditorSupport.Pane) {
                // fingers crossed and hope for the best... could result in bad results once
                // we have multiple editors in the multiview component.
                return el;
            }
        }
        
        MultiViewDescription[] descs = peer.model.getDescriptions();
        for (MultiViewDescription desc : descs) {
            if (isSourceView(desc)) {
                el = peer.model.getElementForDescription(desc);
                if (el.getVisualRepresentation() instanceof CloneableEditorSupport.Pane) {
                    return el;
                } else {
                    Logger.getLogger(getClass().getName()).info("MultiViewDescription " + desc.getDisplayName() + "(" + desc.getClass() + 
                            ") claimed to contain sources, but it's MutliViewElement.getVisualRepresentation() didn't return a valid CloeanbleEditorSupport.Pane instance.");
                }
            }
        }
        // hopeless case, don't try to create new elements. it's users responsibility to
        // switch to the editor element before getEditorPane() 
        return null;
    }
    
    public HelpCtx getHelpCtx() {
        return peer.getHelpCtx();
    }
   
    public String toString() {
        return "MVCTC[name=" + getDisplayName() + ", peer=" + peer + "]"; // NOI18N
    }
 
    /**
     * Get the undo/redo support for this component.
     * The default implementation returns a dummy support that cannot
     * undo anything.
     *
     * @return undoable edit for this component
     */
    public UndoRedo getUndoRedo() {
        UndoRedo retValue;
        retValue = peer.peerGetUndoRedo();
        if (retValue == null) {
            retValue = super.getUndoRedo();
        }
        return retValue;
    }    
    
    /**
     * This method is called when last <code>CloneableTopComponent</code> is about to close.
     * Delegates to CloseOperationHandler.
     */
    @Override
    protected boolean closeLast() {
        return peer.canClose();
    }
    
    // from CloneableEditor.Pane
    public void updateName() {
        // ensure to trigger update name from AWT -> #44012 - will ultimately trigger winsys.
        if (peer != null) {
            if (SwingUtilities.isEventDispatchThread() ) {
                peer.updateName();
            } else {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        peer.updateName();
                    }
                });
            }
        }
    }
    
    /**
     * callback for the Pane implementation to adjust itself to the openAt() request.
     */
    public void ensureVisible() {
        MultiViewElement paneEl = findPaneElement();
        if (paneEl != null) {
            open();
            MultiViewElementCallback call = peer.getModel().getCallbackForElement(paneEl);
            call.requestVisible();
        }
    }

    static boolean isSourceView(MultiViewDescription desc) {
        if (desc instanceof SourceViewMarker) {
            return true;
        }
        if (desc instanceof SourceCheckDescription) {
            return ((SourceCheckDescription)desc).isSourceView();
        }
        return false;
    }
    
    @Override
    public SubComponent[] getSubComponents() {
        return MultiViewTopComponent.getSubComponents( peer );
    }
    
    /**
     * implementation of the MultiViewElement.ActionRequestObserver, manages activatation of the elements
     * and the TC itself based on requests from the elements.
     */
    class ActReqObserver implements Serializable, MultiViewElementCallbackDelegate {
        
        private static final long serialVersionUID =-3126744916624172415L;        
        private MultiViewDescription description;
        
        ActReqObserver(MultiViewDescription desc) {
            description = desc;
        }
        
        public void requestActive() {
            boolean activated = peer.isActivated();
            if (!activated) {
                MultiViewCloneableTopComponent.this.requestActive();
            }
            if (peer.model.getActiveDescription() != description) {
                if (activated) {
                    peer.model.getActiveElement().componentDeactivated();
                } 
                peer.tabs.changeActiveManually(description);
                if (activated) {
                    peer.model.getActiveElement().componentActivated();
                }
            }

        }
        
        public void requestVisible() {
            peer.tabs.changeVisibleManually(description);
        }
        
        public Action[] createDefaultActions() {
            return MultiViewCloneableTopComponent.this.getDefaultTCActions();
        }
        
        public void updateTitle(String title) {
            MultiViewCloneableTopComponent.this.setDisplayName(title);
        }
        
        /** replace as null - should not be stored and read..*/
        public Object writeReplace() throws ObjectStreamException {
            return null;
        }

        /** Resolve as null -should not be stored and read..*/
        public Object readResolve() throws ObjectStreamException {
            return null;
        }
        
        public boolean isSelectedElement() {
            return (description.equals(peer.model.getActiveDescription()));
        }
        
        public TopComponent getTopComponent() {
            return MultiViewCloneableTopComponent.this;
        }
        
    }
    
}
