/*-
 * Copyright (C) 2007 Erik Larsson
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.catacombae.hfsexplorer.gui;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.io.PrintStream;
import java.util.LinkedList;
import java.util.List;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingConstants;
import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.ExpandVetoException;
import javax.swing.tree.TreePath;
import org.catacombae.csjc.PrintableStruct;
import org.catacombae.csjc.StructElements;
import org.catacombae.csjc.structelements.Dictionary;
import org.catacombae.hfs.BTreeFile;
import org.catacombae.hfs.types.hfscommon.CommonBTLeafRecord;
import org.catacombae.hfsexplorer.FileSystemBrowser.NoLeafMutableTreeNode;
import org.catacombae.hfs.types.hfscommon.CommonBTNode;
import org.catacombae.hfs.types.hfscommon.CommonBTNodeDescriptor;
import org.catacombae.hfsexplorer.io.JTextAreaOutputStream;
import org.catacombae.util.Util.Pair;

/**
 * @author <a href="https://catacombae.org" target="_top">Erik Larsson</a>
 */
public abstract class BTreeInfoPanel
        <R extends CommonBTLeafRecord, B extends BTreeFile>
        extends javax.swing.JPanel
{
    private static final int UNIT_INCREMENT = 10;

    protected final String INDEX_NAME = "index";
    protected final String LEAF_NAME = "leaf";
    protected final String PRINT_FIELDS_AREA_NAME = "printfieldsarea";
    protected final String ERROR_STRING_NAME = "error_string";
    protected final String OTHER_NAME = "other";
    protected final String STRUCT_VIEW_PANEL_NAME = "structview";

    private final JLabel errorStringLabel =
            new JLabel("Error while processing leaf record.",
            SwingConstants.CENTER);
    protected final JPanel leafPanel;
    protected final CardLayout clLeaf;

    /** Creates new form BTreeInfoPanel */
    public BTreeInfoPanel(final B bTree) {

        initComponents();

        descriptionLabel.setText(getHeaderText());

        // Populate the root
	/*
         * What we need is a method that gets us the children of the "current" node.
         * A B-tree starts with a header node,
         */
        CommonBTNode iNode = bTree.getRootNode(); // Get root index node.

        if(iNode == null) {
            DefaultTreeModel model = new DefaultTreeModel(new NoLeafMutableTreeNode("<empty>"));
            bTreeFileTree.setModel(model);

            leafPanel = null;
            clLeaf = null;

            return;
        }

        DefaultMutableTreeNode rootNode =
                new NoLeafMutableTreeNode(new BTNodeStorage(
                bTree.getRootNodeNumber(), iNode, getRootNodeName()));
        expandNode(rootNode, iNode, bTree);

        DefaultTreeModel model = new DefaultTreeModel(rootNode);
        bTreeFileTree.setModel(model);

        bTreeFileTree.addTreeWillExpandListener(new TreeWillExpandListener() {

            public void treeWillExpand(TreeExpansionEvent e)
                    throws ExpandVetoException {

                TreePath tp = e.getPath();
                Object obj = tp.getLastPathComponent();
                if(obj instanceof DefaultMutableTreeNode) {
                    DefaultMutableTreeNode dmtn = ((DefaultMutableTreeNode) obj);
                    Object obj2 = dmtn.getUserObject();
                    if(obj2 instanceof BTNodeStorage) {
                        CommonBTNode node = ((BTNodeStorage) obj2).getNode();
                        expandNode(dmtn, node, bTree);
                    }
                    else
                        throw new RuntimeException("Wrong user object type in expandable node!");
                }
                else
                    throw new RuntimeException("Wrong node type in tree!");
            }

            public void treeWillCollapse(TreeExpansionEvent e) {
            }
        });

        final CardLayout clRoot = new CardLayout();
        leafPanel = new JPanel();

        clLeaf = new CardLayout();
        leafPanel.setLayout(clLeaf);

        leafPanel.add(errorStringLabel, ERROR_STRING_NAME);
        leafPanel.add(new JLabel("INTERNAL ERROR!", SwingConstants.CENTER), OTHER_NAME);

        final JScrollPane structViewPanelScroller = new JScrollPane();
        structViewPanelScroller.getVerticalScrollBar().setUnitIncrement(UNIT_INCREMENT);
        structViewPanelScroller.getHorizontalScrollBar().setUnitIncrement(UNIT_INCREMENT);
        leafPanel.add(structViewPanelScroller, STRUCT_VIEW_PANEL_NAME);

        LinkedList<Pair<JPanel, String>> customPanelsList =
                new LinkedList<Pair <JPanel, String>>();
        createCustomPanels(customPanelsList);

        for(Pair<JPanel, String> panel : customPanelsList) {
            JScrollPane customPanelScroller = new JScrollPane(panel.getA());
            customPanelScroller.getVerticalScrollBar().setUnitIncrement(
                    UNIT_INCREMENT);
            customPanelScroller.getHorizontalScrollBar().setUnitIncrement(
                    UNIT_INCREMENT);
            leafPanel.add(customPanelScroller, panel.getB());
        }

        final LeafInfoPanel printFieldsTextAreaHeader = new LeafInfoPanel();
        final JTextArea printFieldsTextArea = new JTextArea(0, 0);
        printFieldsTextArea.setEditable(false);
        printFieldsTextArea.setLineWrap(false);
        printFieldsTextArea.setOpaque(false);
        final JPanel printFieldsTextAreaContainer =
                new JPanel(new BorderLayout());
        printFieldsTextAreaContainer.add(printFieldsTextAreaHeader,
                BorderLayout.NORTH);
        printFieldsTextAreaContainer.add(printFieldsTextArea,
                BorderLayout.CENTER);

        JScrollPane printFieldsTextAreaScroller =
                new JScrollPane(printFieldsTextAreaContainer);
        printFieldsTextAreaScroller.getVerticalScrollBar().setUnitIncrement(UNIT_INCREMENT);
        printFieldsTextAreaScroller.getHorizontalScrollBar().setUnitIncrement(UNIT_INCREMENT);
        leafPanel.add(printFieldsTextAreaScroller, PRINT_FIELDS_AREA_NAME);

        infoPanel.setLayout(clRoot);
        final JLabel indexNodeLabel = new JLabel("No selection.", SwingConstants.CENTER);
        infoPanel.add(indexNodeLabel, INDEX_NAME);
        infoPanel.add(leafPanel, LEAF_NAME);
        //infoScroller.setViewportView(infoPanel);

        bTreeFileTree.addTreeSelectionListener(new TreeSelectionListener() {
            public void valueChanged(TreeSelectionEvent te) {
                //System.err.println("Tree selection");
                Object o = te.getPath().getLastPathComponent();
                if(o instanceof DefaultMutableTreeNode) {
                    Object o2 = ((DefaultMutableTreeNode) o).getUserObject();
                    if(o2 instanceof BTNodeStorage) {
                        final BTNodeStorage nodeStorage = (BTNodeStorage) o2;
                        final CommonBTNode btn = nodeStorage.getNode();
                        CommonBTNodeDescriptor btnd = btn.getNodeDescriptor();
                        switch(btnd.getNodeType()) {
                            case INDEX:
                                indexNodeLabel.setText("Index node " +
                                        nodeStorage.getNodeNumber() + " " +
                                        "with " +
                                        btnd.getNumberOfRecords() + " " +
                                        "records.");
                                break;
                            case LEAF:
                                indexNodeLabel.setText("Leaf node " +
                                        nodeStorage.getNodeNumber() + " with " +
                                        btnd.getNumberOfRecords() + " " +
                                        "records.");
                                break;
                            default:
                                indexNodeLabel.setText("Unknown error!");
                        }

                        clRoot.show(infoPanel, INDEX_NAME);
                    }
                    else if(o2 instanceof BTLeafStorage) {
                        final BTLeafStorage leafStorage = (BTLeafStorage) o2;
                        final CommonBTLeafRecord rec = leafStorage.getRecord();

                        if(handleLeafRecord(leafStorage));
                        else if(rec instanceof StructElements) {
                            Dictionary dict = ((StructElements) rec).getStructElements();
                            String label = dict.getTypeDescription();
                            if(label == null)
                                label = dict.getTypeName();

                            final LeafInfoPanel leafInfoPanel =
                                    new LeafInfoPanel();
                            leafInfoPanel.setRecordNumber(
                                    leafStorage.getRecordNumber());
                            leafInfoPanel.setRecordOffset(
                                    leafStorage.getRecordOffset());
                            leafInfoPanel.setRecordSize(
                                    leafStorage.getRecordSize());

                            final JPanel containerPanel =
                                    new JPanel(new BorderLayout());
                            containerPanel.add(leafInfoPanel, BorderLayout.NORTH);
                            containerPanel.add(
                                    new StructViewPanel(label + ":", dict),
                                    BorderLayout.CENTER);

                            structViewPanelScroller.setViewportView(
                                    containerPanel);
                            clLeaf.show(leafPanel, STRUCT_VIEW_PANEL_NAME);
                        }
                        else if(rec instanceof PrintableStruct) {
                            PrintStream ps = new PrintStream(new JTextAreaOutputStream(System.err, printFieldsTextArea));
                            printFieldsTextAreaHeader.setRecordNumber(
                                    leafStorage.getRecordNumber());
                            printFieldsTextAreaHeader.setRecordOffset(
                                    leafStorage.getRecordOffset());
                            printFieldsTextAreaHeader.setRecordSize(
                                    leafStorage.getRecordSize());
                            printFieldsTextArea.setText("");
                            ((PrintableStruct) rec).print(ps, "");
                            ps.close();
                            printFieldsTextArea.setCaretPosition(0);
                            clLeaf.show(leafPanel, PRINT_FIELDS_AREA_NAME);

                        }
                        else {
                            System.err.println("BTreeInfoPanel: Could not show record type " + rec.getClass());
                            clLeaf.show(leafPanel, OTHER_NAME);
                        }
                        clRoot.show(infoPanel, LEAF_NAME);

                    }
                    else if(o2 instanceof String) {
                        errorStringLabel.setText((String) o2);
                        clLeaf.show(leafPanel, ERROR_STRING_NAME);
                        clRoot.show(infoPanel, LEAF_NAME);
                    }
                    else {
                        System.err.println("BTreeInfoPanel: Unexpected user " +
                                "object type " + o2.getClass() + " for " +
                                "selected tree node.");
                        clLeaf.show(leafPanel, OTHER_NAME);
                        clRoot.show(infoPanel, LEAF_NAME);
                    }
                }
                else
                    System.err.println("WARNING: Unknown node type in " +
                            "bTreeFileTree - " + o.getClass().toString());
            }
        });
    }

    protected abstract String getRootNodeName();

    protected abstract String getHeaderText();

    protected abstract void createCustomPanels(
            List<Pair<JPanel, String>> customPanelsList);

    protected abstract void expandNode(DefaultMutableTreeNode dmtn,
            CommonBTNode node, B bTree);

    protected abstract boolean handleLeafRecord(BTLeafStorage leafStorage);

    protected static class BTNodeStorage {

        private long nodeNumber;
        private CommonBTNode node;
        private String text;

        public BTNodeStorage(long nodeNumber, CommonBTNode node, String text) {
            this.nodeNumber = nodeNumber;
            this.node = node;
            this.text = text;
        }

        public long getNodeNumber() {
            return nodeNumber;
        }

        public CommonBTNode getNode() {
            return node;
        }

        @Override
        public String toString() {
            return text;
        }
    }

    protected static class BTLeafStorage {

        private final int recordNumber;
        private final int recordOffset;
        private final int recordSize;
        private CommonBTLeafRecord rec;
        private String text;

        public BTLeafStorage(int recordNumber, int recordOffset, int recordSize,
                CommonBTLeafRecord rec, String text)
        {
            this.recordNumber = recordNumber;
            this.recordOffset = recordOffset;
            this.recordSize = recordSize;
            this.rec = rec;
            this.text = text;
        }

        public int getRecordNumber() {
            return recordNumber;
        }

        public int getRecordOffset() {
            return recordOffset;
        }

        public int getRecordSize() {
            return recordSize;
        }

        public CommonBTLeafRecord getRecord() {
            return rec;
        }

        @Override
        public String toString() {
            return text;
        }
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jSplitPane1 = new javax.swing.JSplitPane();
        jPanel1 = new javax.swing.JPanel();
        bTreeFileTreeScroller = new javax.swing.JScrollPane();
        bTreeFileTree = new javax.swing.JTree();
        infoPanel = new javax.swing.JPanel();
        descriptionLabel = new javax.swing.JLabel();

        jSplitPane1.setDividerLocation(330);
        jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT);
        jSplitPane1.setResizeWeight(1.0);

        bTreeFileTreeScroller.setViewportView(bTreeFileTree);

        org.jdesktop.layout.GroupLayout jPanel1Layout = new org.jdesktop.layout.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(bTreeFileTreeScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 359, Short.MAX_VALUE)
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(org.jdesktop.layout.GroupLayout.TRAILING, bTreeFileTreeScroller, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 329, Short.MAX_VALUE)
        );

        jSplitPane1.setLeftComponent(jPanel1);

        infoPanel.setPreferredSize(new java.awt.Dimension(100, 140));

        org.jdesktop.layout.GroupLayout infoPanelLayout = new org.jdesktop.layout.GroupLayout(infoPanel);
        infoPanel.setLayout(infoPanelLayout);
        infoPanelLayout.setHorizontalGroup(
            infoPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 359, Short.MAX_VALUE)
        );
        infoPanelLayout.setVerticalGroup(
            infoPanelLayout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(0, 101, Short.MAX_VALUE)
        );

        jSplitPane1.setRightComponent(infoPanel);

        descriptionLabel.setText("View of the B*-tree:");

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(jSplitPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 361, Short.MAX_VALUE)
                    .add(descriptionLabel))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(descriptionLabel)
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(jSplitPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 437, Short.MAX_VALUE)
                .addContainerGap())
        );
    }// </editor-fold>//GEN-END:initComponents
    // Variables declaration - do not modify//GEN-BEGIN:variables
    public javax.swing.JTree bTreeFileTree;
    private javax.swing.JScrollPane bTreeFileTreeScroller;
    private javax.swing.JLabel descriptionLabel;
    private javax.swing.JPanel infoPanel;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JSplitPane jSplitPane1;
    // End of variables declaration//GEN-END:variables
}
