/*
 * JBoss, Home of Professional Open Source.
 *
 * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing.
 *
 * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors.
 */
package com.metamatrix.modeler.diagram.ui.layout;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import com.metamatrix.modeler.diagram.ui.connection.NodeConnectionModel;
import com.metamatrix.modeler.diagram.ui.model.DiagramModelNode;

public class MmTreeLayout extends DiagramLayout {

    static final String CLASS_NAME = "MmTreeLayout"; //$NON-NLS-1$
    public static final int COMPACT_METHOD = 0;
    public static final int SEPARATE_METHOD = 1;

    public static final int LIST_ORDER = 0;
    public static final int POSITION_ORDER = 1;

    public static final int ORIENTATION_ROOT_LEFT = 0;
    public static final int ORIENTATION_ROOT_TOP = 1;
    public static final int ORIENTATION_ROOT_RIGHT = 2;
    public static final int ORIENTATION_ROOT_BOTTOM = 3;

    public static final int ERROR_TREE_HAS_NO_ROOT = 1;
    public static final int ERROR_ROOT_NOT_MANAGED = 2;
    public static final int ERROR_NOT_A_TREE = 3;

    private DiagramModelNode modelRoot = null;
    private int _depth = -1;
    private int _order = 1;
    private int _orientation = 0;
    private int _method = 0;
    private boolean _useObjectsSizes = false;
    private boolean _fixedSpacing = true;
    private double _fixedXSpacing = 500.0;
    private double _fixedYSpacing = 400.0;
    private transient Hashtable _compToNode;
    private transient TreeNode[] rawTreeNodes;

    public MmTreeLayout() {
        _compToNode = new Hashtable();
        rawTreeNodes = null;
    }

    @Override
    protected int run() {
        if (this.getComponentCount() == 0) return SUCCESSFUL;

        int i = buildTree();

        if (i != 0) return i;

        TreeNode treenode = (TreeNode)_compToNode.get(modelRoot);
        if (treenode == null) return ERROR_ROOT_NOT_MANAGED;

        TreeNode[] treenodes = new TreeNode[_depth + 1];
        treenode.setNeighbors(treenodes);

        double d = 0.0;
        double d_85_ = 0.0;
        double d_86_ = 0.0;
        double d_87_ = 1.0;
        double d_88_ = 0.0;
        double[] ds = new double[_depth + 1];

        if (_method == 0) {
            double[] ds_89_ = new double[_depth + 1];
            double[] ds_90_ = new double[_depth + 1];
            treenode.calculatePosition(ds_89_, ds_90_);
            treenode.setEven();
            for (int i_91_ = 0; i_91_ <= _depth; i_91_++)
                d_86_ = Math.max(d_86_, ds_90_[i_91_]);
            if (_useObjectsSizes) {
                treenode.getMaximumWidths(ds);
                for (int i_92_ = 0; i_92_ <= _depth; i_92_++) {
                    d_88_ = ds[i_92_];
                    ds[i_92_] += d - 0.5 * d_88_;
                    d += d_88_;
                    d_85_ = Math.max(d_85_, ds_89_[i_92_]);
                }
            }
        } else {
            double[] ds_93_ = new double[1];
            double[] ds_94_ = new double[1];
            treenode.calculateSeparatePosition(ds_93_, ds_94_);
            d_86_ = ds_94_[0];
            if (_useObjectsSizes) {
                treenode.getMaximumWidths(ds);
                for (int i_95_ = 0; i_95_ <= _depth; i_95_++) {
                    d_88_ = ds[i_95_];
                    ds[i_95_] += d - 0.5 * d_88_;
                    d += d_88_;
                }
                d_85_ = ds_93_[0];
            }
        }

        double d_96_ = 0.0;
        double d_97_ = 0.0;
        double d_98_ = this.getWidth();
        double d_99_ = this.getHeight();
        double d_100_ = this.getXOrigin();
        double d_101_ = this.getYOrigin();
        if (_useObjectsSizes) {
            d_96_ = 1.0;
            d_97_ = 1.0;
        }
        if (_orientation == ORIENTATION_ROOT_TOP || _orientation == ORIENTATION_ROOT_BOTTOM) {
            d_88_ = d_98_;
            d_98_ = d_99_;
            d_99_ = d_88_;
            d_88_ = d_100_;
            d_100_ = d_101_;
            d_101_ = d_88_;
        }
        if (_orientation == ORIENTATION_ROOT_RIGHT || _orientation == ORIENTATION_ROOT_BOTTOM) {
            d_100_ += d_98_;
            d_101_ += d_99_;
            d_96_ = -d_96_;
            d_97_ = -d_97_;
            d_87_ = -1.0;
        }
        if (_fixedSpacing) {
            if (_orientation == ORIENTATION_ROOT_TOP || _orientation == ORIENTATION_ROOT_BOTTOM) {
                d_98_ = d_87_ * _fixedYSpacing;
                d_99_ = d_87_ * _fixedXSpacing;
            } else {
                d_98_ = d_87_ * _fixedXSpacing;
                d_99_ = d_87_ * _fixedYSpacing;
            }
        } else {
            d_98_ = d_87_ * (d_98_ - d) / (_depth + 1);
            d_99_ = d_87_ * (d_99_ - d_85_) / d_86_;
        }
        d_100_ += 0.5 * d_98_;
        d_101_ -= 0.5 * d_99_;
        for (int i_102_ = 0; i_102_ <= _depth; i_102_++)
            ds[i_102_] = d_100_ + d_96_ * ds[i_102_] + d_98_ * i_102_;

        DiagramModelNode[] modelNodes = getNodeArray();
        int i_103_ = modelNodes.length;
        if (_orientation == ORIENTATION_ROOT_RIGHT || _orientation == ORIENTATION_ROOT_LEFT) {
            for (int i_104_ = 0; i_104_ < i_103_; i_104_++) {
                DiagramModelNode modelNode = modelNodes[i_104_];
                treenode = rawTreeNodes[i_104_];
                if (treenode._level != -1) {
                    int newX = (int)ds[treenode._level];
                    int newY = (int)(treenode._absolutePosition * d_97_ + treenode._relativePosition * d_99_ + d_101_);
                    modelNode.setCenterXY(newX, newY);
                }
            }
        } else {
            for (int i_105_ = 0; i_105_ < i_103_; i_105_++) {
                DiagramModelNode modelNode = modelNodes[i_105_];
                treenode = rawTreeNodes[i_105_];
                if (treenode._level != -1) {
                    int newX = (int)ds[treenode._level];
                    int newY = (int)(treenode._absolutePosition * d_97_ + treenode._relativePosition * d_99_ + d_101_);
                    modelNode.setCenterXY(newX, newY);
                }
            }
        }
        // if (this.getPropagate())
        // this.propagate();

        return SUCCESSFUL;
    }

    private int buildTree() {
        _compToNode.clear();
        int i = this.getComponentCount();
        if (i == 0) return 0;
        DiagramModelNode[] modelNodes = getNodeArray();
        rawTreeNodes = new TreeNode[i];
        for (int iiNode = 0; iiNode < i; iiNode++) {
            DiagramModelNode modelNode = modelNodes[iiNode];
            rawTreeNodes[iiNode] = new TreeNode(modelNode);
            _compToNode.put(modelNode, rawTreeNodes[iiNode]);
        }

        if (_order == LIST_ORDER) {
            for (int i_107_ = 0; i_107_ < i; i_107_++)
                rawTreeNodes[i_107_]._orderVar = i_107_;
        } else if (_orientation == ORIENTATION_ROOT_LEFT) {
            for (int i_108_ = 0; i_108_ < i; i_108_++)
                rawTreeNodes[i_108_]._orderVar = modelNodes[i_108_].getCenterY();
        } else if (_orientation == ORIENTATION_ROOT_TOP) {
            for (int i_109_ = 0; i_109_ < i; i_109_++)
                rawTreeNodes[i_109_]._orderVar = modelNodes[i_109_].getCenterX();
        } else if (_orientation == ORIENTATION_ROOT_RIGHT) {
            for (int i_110_ = 0; i_110_ < i; i_110_++)
                rawTreeNodes[i_110_]._orderVar = -modelNodes[i_110_].getCenterY();
        } else {
            for (int i_111_ = 0; i_111_ < i; i_111_++)
                rawTreeNodes[i_111_]._orderVar = -modelNodes[i_111_].getCenterX();
        }

        if (_orientation == ORIENTATION_ROOT_LEFT || _orientation == ORIENTATION_ROOT_RIGHT) {
            for (int i_112_ = 0; i_112_ < i; i_112_++) {
                rawTreeNodes[i_112_]._width = modelNodes[i_112_].getWidth();
                rawTreeNodes[i_112_]._height = modelNodes[i_112_].getHeight();
            }
        } else {
            for (int i_113_ = 0; i_113_ < i; i_113_++) {
                rawTreeNodes[i_113_]._width = modelNodes[i_113_].getHeight();
                rawTreeNodes[i_113_]._height = modelNodes[i_113_].getWidth();
            }
        }

        for (int iiNode = 0; iiNode < i; iiNode++) {
            DiagramModelNode sourceNode = modelNodes[iiNode];
            Vector sourceConnections = sourceNode.getSourceConnections();
            TreeNode sourceTreeNode = (TreeNode)_compToNode.get(sourceNode);
            if (!sourceConnections.isEmpty()) {
                Iterator iter = sourceConnections.iterator();
                NodeConnectionModel nextConnection = null;
                while (iter.hasNext()) {
                    nextConnection = (NodeConnectionModel)iter.next();
                    TreeNode targetTreeNode = (TreeNode)_compToNode.get(nextConnection.getTargetNode());
                    if (sourceTreeNode != null && targetTreeNode != null) {
                        sourceTreeNode.addLinkToNode(targetTreeNode);
                        targetTreeNode.addLinkToNode(sourceTreeNode);
                    }
                }
            }
        }

        if (modelRoot == null) return ERROR_TREE_HAS_NO_ROOT;

        TreeNode treenode = (TreeNode)_compToNode.get(modelRoot);
        if (treenode == null) return ERROR_ROOT_NOT_MANAGED;

        _depth = -1;

        try {
            _depth = treenode.setParent(null);
        } catch (NotATreeException notatreeexception) {
            return ERROR_NOT_A_TREE;
        }

        return SUCCESSFUL;
    }

    public boolean getFixedSpacing() {
        return _fixedSpacing;
    }

    public double getFixedXSpacing() {
        return _fixedXSpacing;
    }

    public double getFixedYSpacing() {
        return _fixedYSpacing;
    }

    public int getMethod() {
        return _method;
    }

    public int getOrder() {
        return _order;
    }

    public int getOrientation() {
        return _orientation;
    }

    public DiagramModelNode getRoot() {
        return modelRoot;
    }

    public boolean getUseObjectsSizes() {
        return _useObjectsSizes;
    }

    public void setFixedSpacing( boolean bool ) {
        _fixedSpacing = bool;
    }

    public void setFixedXSpacing( double d ) {
        _fixedXSpacing = d;
    }

    public void setFixedYSpacing( double d ) {
        _fixedYSpacing = d;
    }

    public void setMethod( int i ) {
        _method = i;
    }

    public void setOrder( int i ) {
        _order = i;
    }

    public void setOrientation( int i ) {
        _orientation = i;
    }

    public void setRoot( DiagramModelNode modelNode ) {
        modelRoot = modelNode;
    }

    public void setUseObjectsSizes( boolean bool ) {
        _useObjectsSizes = bool;
    }

    private class TreeNode {
        int _level;
        double _orderVar;
        double _width;
        double _height;
        double _absolutePosition;
        private double _absoluteSpaceLeft;
        double _relativePosition;
        private double _relativeSpaceLeft;
        // private final DiagramModelNode _modelNode;
        private TreeNode _parent;
        private final Vector _children;
        private TreeNode _upNeighbor;
        private TreeNode _downNeighbor;
        private boolean _upSibling;
        private boolean _downSibling;

        public TreeNode( DiagramModelNode modelNode ) {
            _level = -1;
            _orderVar = 0.0;
            _absolutePosition = 0.0;
            _absoluteSpaceLeft = 0.0;
            _relativePosition = 0.0;
            _relativeSpaceLeft = 0.0;
            // _modelNode = modelNode;
            _parent = null;
            _children = new Vector();
            _upNeighbor = null;
            _downNeighbor = null;
            _upSibling = false;
            _downSibling = false;
            _width = 0.0;
            _height = 0.0;
        }

        public void calculatePosition( double[] ds,
                                       double[] ds_0_ ) {
            int i = _children.size();
            if (i == 0) {
                _absolutePosition = ds[_level] + 0.5 * _height;
                ds[_level] += _height;
                _relativePosition = ds_0_[_level] + 1.0;
                ds_0_[_level] = _relativePosition;
            } else {
                double d = 0.0;
                double d_1_ = 0.0;
                for (int i_2_ = 0; i_2_ < i; i_2_++) {
                    TreeNode treenode_3_ = (TreeNode)_children.get(i_2_);
                    treenode_3_.calculatePosition(ds, ds_0_);
                    d = treenode_3_._absolutePosition;
                    d_1_ = treenode_3_._relativePosition;
                }
                int[] is = new int[1];
                double[] ds_4_ = new double[2];
                for (int i_5_ = i - 2; i_5_ >= 0; i_5_--) {
                    TreeNode treenode_6_ = (TreeNode)_children.get(i_5_);
                    is[0] = _level;
                    treenode_6_.calculateSpaceDown(is, ds_4_);
                    double d_7_ = ds_4_[0];
                    double d_8_ = ds_4_[1] - 1.0;
                    if (d_7_ < 0.0) d_7_ = 0.0;
                    if (d_8_ < 0.0) d_8_ = 0.0;
                    if (d_7_ > 0.0 || d_8_ > 0.0) {
                        treenode_6_.shiftDown(ds, ds_0_, d_7_, d_8_);
                        treenode_6_._absoluteSpaceLeft = d_7_;
                        treenode_6_._relativeSpaceLeft = d_8_;
                    }
                }
                TreeNode treenode_9_ = (TreeNode)_children.get(0);
                _absolutePosition = 0.5 * (treenode_9_._absolutePosition + d);
                _relativePosition = 0.5 * (treenode_9_._relativePosition + d_1_);
                double d_10_ = treenode_9_._absoluteSpaceLeft;
                double d_11_ = treenode_9_._relativeSpaceLeft;
                for (int i_12_ = 1; i_12_ < i && treenode_9_._absoluteSpaceLeft > 0.0; i_12_++) {
                    treenode_9_._absoluteSpaceLeft -= d_10_;
                    if (treenode_9_._absoluteSpaceLeft < 0.0) treenode_9_._absoluteSpaceLeft = 0.0;
                    treenode_9_ = (TreeNode)_children.get(i_12_);
                }
                treenode_9_ = (TreeNode)_children.get(0);
                for (int i_13_ = 1; i_13_ < i && treenode_9_._relativeSpaceLeft > 0.0; i_13_++) {
                    treenode_9_._relativeSpaceLeft -= d_11_;
                    if (treenode_9_._relativeSpaceLeft < 0.0) treenode_9_._relativeSpaceLeft = 0.0;
                    treenode_9_ = (TreeNode)_children.get(i_13_);
                }
                double d_14_ = ds[_level] + 0.5 * _height - _absolutePosition;
                double d_15_ = ds_0_[_level] + 1.0 - _relativePosition;
                if (d_14_ < 0.0) d_14_ = 0.0;
                if (d_15_ < 0.0) d_15_ = 0.0;
                if (d_14_ > 0.0 || d_15_ > 0.0) shiftDown(ds, ds_0_, d_14_, d_15_);
                else {
                    ds[_level] = _absolutePosition + 0.5 * _height;
                    ds_0_[_level] = _relativePosition;
                }
            }
        }

        public void calculateSeparatePosition( double[] ds,
                                               double[] ds_16_ ) {
            int i = _children.size();
            if (i == 0) {
                _absolutePosition = ds[0] + 0.5 * _height;
                ds[0] += _height;
                _relativePosition = ds_16_[0] + 1.0;
                ds_16_[0] = _relativePosition;
            } else {
                double d = ds[0];
                double d_17_ = 0.0;
                double d_18_ = 0.0;
                for (int i_19_ = 0; i_19_ < i; i_19_++) {
                    TreeNode treenode_20_ = (TreeNode)_children.get(i_19_);
                    treenode_20_.calculateSeparatePosition(ds, ds_16_);
                    d_17_ = treenode_20_._absolutePosition;
                    d_18_ = treenode_20_._relativePosition;
                }
                TreeNode treenode_21_ = (TreeNode)_children.get(0);
                _absolutePosition = 0.5 * (treenode_21_._absolutePosition + d_17_);
                _relativePosition = 0.5 * (treenode_21_._relativePosition + d_18_);
                d -= _absolutePosition - 0.5 * _height;
                if (d > 0.0) shiftSeparateDown(ds, d);
                ds[0] = Math.max(_absolutePosition + 0.5 * _height, ds[0]);
            }
        }

        private void calculateSpaceDown( int[] is,
                                         double[] ds ) {
            double d = -1.0;
            double d_22_ = -1.0;
            if (_level > is[0]) {
                is[0] = _level;
                if (_downNeighbor != null) {
                    d = (_downNeighbor._absolutePosition - _absolutePosition - 0.5 * (_height + _downNeighbor._height));
                    d_22_ = _downNeighbor._relativePosition - _relativePosition;
                    if (d <= 0.0 && d_22_ <= 1.0) {
                        ds[0] = d;
                        ds[1] = d_22_;
                        return;
                    }
                }
            }
            int i = _children.size();
            for (int i_23_ = i - 1; i_23_ >= 0; i_23_--) {
                TreeNode treenode_24_ = (TreeNode)_children.get(i_23_);
                treenode_24_.calculateSpaceDown(is, ds);
                if (ds[0] >= 0.0 && (ds[0] < d || d < 0.0)) d = ds[0];
                if (ds[1] >= 1.0 && (ds[1] < d_22_ || d_22_ < 0.0)) d_22_ = ds[1];
                if (d <= 0.0 && d_22_ <= 1.0) {
                    ds[0] = d;
                    ds[1] = d_22_;
                    return;
                }
            }
            ds[0] = d;
            ds[1] = d_22_;
        }

        private double absoluteSetEven( double d,
                                        int i ) {
            double d_25_ = 0.0;
            double d_26_ = Math.min(d, _absoluteSpaceLeft);
            if (_downSibling && _downNeighbor._absoluteSpaceLeft > 0.0) d_25_ = _downNeighbor.absoluteSetEven(d_26_, i + 1);
            d_25_ += (d_26_ - d_25_) / (i + 1);
            if (d_25_ > 0.0) shiftUpAbsolute(d_25_);
            _absoluteSpaceLeft = 0.0;
            return d_25_;
        }

        private double relativeSetEven( double d,
                                        int i ) {
            double d_27_ = 0.0;
            double d_28_ = Math.min(d, _relativeSpaceLeft);
            if (_downSibling && _downNeighbor._relativeSpaceLeft > 0.0) d_27_ = _downNeighbor.relativeSetEven(d_28_, i + 1);
            d_27_ += (d_28_ - d_27_) / (i + 1);
            if (d_27_ > 0.0) shiftUpRelative(d_27_);
            _relativeSpaceLeft = 0.0;
            return d_27_;
        }

        void getMaximumWidths( double[] ds ) {
            ds[_level] = Math.max(ds[_level], _width);
            int i = _children.size();
            for (int i_29_ = 0; i_29_ < i; i_29_++)
                ((TreeNode)_children.get(i_29_)).getMaximumWidths(ds);
        }

        public void setEven() {
            int i = _children.size();
            for (int i_30_ = 0; i_30_ < i; i_30_++) {
                TreeNode treenode_31_ = (TreeNode)_children.get(i_30_);
                treenode_31_.setEven();
                if (treenode_31_._absoluteSpaceLeft > 0.0) treenode_31_.absoluteSetEven(treenode_31_._absoluteSpaceLeft, 1);
                if (treenode_31_._relativeSpaceLeft > 0.0) treenode_31_.relativeSetEven(treenode_31_._relativeSpaceLeft, 1);
            }
        }

        public void addLinkToNode( TreeNode treenode_32_ ) {
            int i = _children.size();
            int i_33_;
            for (i_33_ = 0; i_33_ < i && (((TreeNode)_children.get(i_33_))._orderVar < treenode_32_._orderVar); i_33_++) {
                /* empty */
            }
            if (i_33_ >= i || (TreeNode)_children.get(i_33_) != treenode_32_) _children.add(i_33_, treenode_32_);
        }

        public void setNeighbors( TreeNode[] treenodes ) {
            if (treenodes[_level] != null) {
                _upNeighbor = treenodes[_level];
                _upSibling = _upNeighbor._parent == _parent;
                _upNeighbor._downNeighbor = this;
                _upNeighbor._downSibling = _upSibling;
            }
            treenodes[_level] = this;
            int i = _children.size();
            for (int i_34_ = 0; i_34_ < i; i_34_++)
                ((TreeNode)_children.get(i_34_)).setNeighbors(treenodes);
        }

        public int setParent( TreeNode treenode_35_ ) throws NotATreeException {
            if (_parent != null) throw new NotATreeException();
            _parent = treenode_35_;
            if (treenode_35_ != null) _level = treenode_35_._level + 1;
            else _level = 0;
            _children.remove(treenode_35_);
            int i = _children.size();
            int i_36_ = _level;
            for (int i_37_ = 0; i_37_ < i; i_37_++)
                i_36_ = Math.max(i_36_, ((TreeNode)_children.get(i_37_)).setParent(this));
            return i_36_;
        }

        private void shiftDown( double[] ds,
                                double[] ds_47_,
                                double d,
                                double d_48_ ) {
            _absolutePosition += d;
            _relativePosition += d_48_;
            ds[_level] = Math.max(ds[_level], _absolutePosition + 0.5 * _height);
            ds_47_[_level] = Math.max(ds_47_[_level], _relativePosition);
            int i = _children.size();
            for (int i_49_ = 0; i_49_ < i; i_49_++)
                ((TreeNode)_children.get(i_49_)).shiftDown(ds, ds_47_, d, d_48_);
        }

        private void shiftSeparateDown( double[] ds,
                                        double d ) {
            _absolutePosition += d;
            ds[0] = Math.max(ds[0], _absolutePosition + 0.5 * _height);
            int i = _children.size();
            for (int i_50_ = 0; i_50_ < i; i_50_++)
                ((TreeNode)_children.get(i_50_)).shiftSeparateDown(ds, d);
        }

        private void shiftUpAbsolute( double d ) {
            _absolutePosition -= d;
            int i = _children.size();
            for (int i_51_ = 0; i_51_ < i; i_51_++)
                ((TreeNode)_children.get(i_51_)).shiftUpAbsolute(d);
        }

        private void shiftUpRelative( double d ) {
            _relativePosition -= d;
            int i = _children.size();
            for (int i_52_ = 0; i_52_ < i; i_52_++)
                ((TreeNode)_children.get(i_52_)).shiftUpRelative(d);
        }
    }

    private static class NotATreeException extends Exception {
        /**
         */
        private static final long serialVersionUID = 1L;

        public NotATreeException() {
            /* empty */
        }
    }
}
