/*
 * Copyright (c) 2009, Tony Kohar. All Rights Reserved.
 *
 * 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.qi4j.envisage.tree;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.GridBagConstraints;
import java.awt.Insets;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ResourceBundle;
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.*;
import org.qi4j.tools.model.descriptor.ApplicationDetailDescriptor;

/**
 * Application Model View as Swing Component.
 * It support 2 view:
 * <pre><code>
 * - by Structure
 * - by Type
 * </code></pre>
 */
public final class TreeModelPane
    extends JPanel
{
    private static final String STRUCTURE_VIEW = "Structure";
    private static final String TYPE_VIEW = "Type";

    private static final ResourceBundle BUNDLE = ResourceBundle.getBundle( TreeModelPane.class.getName() );

    private JPanel mainPane;
    private CardLayout cardLayout;
    private JTree structureTree;
    private JTree typeTree;
    private JComboBox viewAsCombo;

    private boolean selectionInProgress;

    public TreeModelPane()
    {
        setLayout( new BorderLayout() );

        // init mainPane
        structureTree = new JTree();
        structureTree.setRootVisible( false );
        structureTree.setShowsRootHandles( true );
        structureTree.setExpandsSelectedPaths( true );
        structureTree.setScrollsOnExpand( true );
        structureTree.setName( STRUCTURE_VIEW );
        structureTree.setCellRenderer( new TreeModelCellRenderer() );

        typeTree = new JTree();
        typeTree.setRootVisible( false );
        typeTree.setShowsRootHandles( true );
        typeTree.setExpandsSelectedPaths( true );
        typeTree.setScrollsOnExpand( true );
        typeTree.setName( TYPE_VIEW );
        typeTree.setCellRenderer( new TreeModelCellRenderer() );

        mainPane = new JPanel();
        cardLayout = new CardLayout();
        mainPane.setLayout( cardLayout );
        mainPane.add( new JScrollPane( structureTree ), STRUCTURE_VIEW );
        mainPane.add( new JScrollPane( typeTree ), TYPE_VIEW );
        add( mainPane, BorderLayout.CENTER );

        // init viewAsCombo
        JPanel viewAsPane = new JPanel();
        viewAsPane.setBorder( BorderFactory.createEmptyBorder( 3, 6, 3, 0 ) );
        viewAsPane.setLayout( new java.awt.GridBagLayout() );

        GridBagConstraints gridBagConstraints;
        JLabel viewAsLabel = new JLabel( BUNDLE.getString( "CTL_ViewAs.Text" ) );
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.anchor = GridBagConstraints.WEST;
        gridBagConstraints.insets = new Insets( 0, 0, 0, 6 );
        viewAsPane.add( viewAsLabel, gridBagConstraints );

        viewAsCombo = new JComboBox( new DefaultComboBoxModel( new String[]
        {
            STRUCTURE_VIEW, TYPE_VIEW
        } ) );
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.fill = GridBagConstraints.HORIZONTAL;
        gridBagConstraints.weightx = 1.0;
        viewAsPane.add( viewAsCombo, gridBagConstraints );

        viewAsCombo.addItemListener( new ItemListener()
        {
            @Override
            public void itemStateChanged( ItemEvent evt )
            {
                if( evt.getStateChange() == ItemEvent.DESELECTED )
                {
                    return;
                }
                cardLayout.show( mainPane, evt.getItem().toString() );
                repaint();
            }
        } );

        add( viewAsPane, BorderLayout.PAGE_START );
    }

    /**
     * Initialize Qi4J for this component
     *
     * @param descriptor the Application descriptor
     */
    public void initQi4J( ApplicationDetailDescriptor descriptor )
    {
        // traverse the model and build JTree representation
        MutableTreeNode rootNode1 = StructureModelBuilder.build( descriptor );
        MutableTreeNode rootNode2 = TypeModelBuilder.build( descriptor );

        structureTree.setModel( new DefaultTreeModel( rootNode1 ) );
        typeTree.setModel( new DefaultTreeModel( rootNode2 ) );

        structureTree.addTreeSelectionListener( new TreeSelectionListener()
        {
            @Override
            public void valueChanged( TreeSelectionEvent evt )
            {
                structureTreeValueChanged();
            }
        } );

        typeTree.addTreeSelectionListener( new TreeSelectionListener()
        {
            @Override
            public void valueChanged( TreeSelectionEvent evt )
            {
                typeTreeValueChanged();
            }
        } );
    }

    public Object getLastSelected()
    {
        Object obj = structureTree.getLastSelectedPathComponent();
        if( obj != null )
        {
            return ( (DefaultMutableTreeNode) obj ).getUserObject();
        }
        return null;
    }

    public void setSelectedValue( Object obj )
    {
        if( obj == null )
        {
            return;
        }

        TreeNode node = findNode( structureTree, obj );
        if( node != null )
        {
            DefaultTreeModel treeModel = (DefaultTreeModel) structureTree.getModel();
            TreePath treePath = new TreePath( treeModel.getPathToRoot( node ) );
            structureTree.setSelectionPath( treePath );
            structureTree.scrollPathToVisible( treePath );
        }
        else
        {
            structureTree.clearSelection();
        }
    }

    /**
     * Just a helper method to find the node which contains the userObject
     *
     * @param tree   the JTree to search into
     * @param object the user object
     *
     * @return TreeNode or null
     */
    protected TreeNode findNode( JTree tree, Object object )
    {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getModel().getRoot();
        return findNode( node, object );
    }

    /**
     * Recurvice search or find node that contains the obj
     *
     * @param node DefaultMutableTreeNode
     * @param obj  userObject
     *
     * @return TreeNode or null if could not find
     */
    private TreeNode findNode( DefaultMutableTreeNode node, Object obj )
    {
        if( obj instanceof String )
        {
            if( node.getUserObject().toString().equals( obj.toString() ) )
            {
                return node;
            }
        }
        else if( node.getUserObject().equals( obj ) )
        {
            return node;
        }

        TreeNode foundNode = null;
        for( int i = 0; i < node.getChildCount(); i++ )
        {
            DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt( i );
            foundNode = findNode( childNode, obj );
            if( foundNode != null )
            {
                break;
            }
        }

        return foundNode;
    }

    public final void addTreeSelectionListener( TreeSelectionListener listener )
    {
        structureTree.addTreeSelectionListener( listener );
    }

    public final void removeTreeSelectionListener( TreeSelectionListener listener )
    {
        structureTree.removeTreeSelectionListener( listener );
    }

    protected void structureTreeValueChanged()
    {
        if( selectionInProgress )
        {
            return;
        }

        Object userObject = getLastSelected();
        if( userObject == null )
        {
            return;
        }
        TreeNode node = findNode( typeTree, userObject );
        if( node != null )
        {
            DefaultTreeModel treeModel = (DefaultTreeModel) typeTree.getModel();
            TreePath treePath = new TreePath( treeModel.getPathToRoot( node ) );
            typeTree.setSelectionPath( treePath );
            typeTree.scrollPathToVisible( treePath );
        }
    }

    protected void typeTreeValueChanged()
    {
        Object obj = typeTree.getLastSelectedPathComponent();
        if( obj == null )
        {
            return;
        }
        Object userObject = ( (DefaultMutableTreeNode) obj ).getUserObject();
        TreeNode node = findNode( structureTree, userObject );
        if( node != null )
        {
            DefaultTreeModel treeModel = (DefaultTreeModel) structureTree.getModel();
            TreePath treePath = new TreePath( treeModel.getPathToRoot( node ) );

            selectionInProgress = true;
            try
            {
                structureTree.setSelectionPath( treePath );
            }
            finally
            {
                selectionInProgress = false;
            }
            structureTree.scrollPathToVisible( treePath );
        }
    }
}
