1 var define = require("../define").define,
  2         Tree = require("./Tree"),
  3         base = require("../base"),
  4         multiply = base.string.multiply;
  5 
  6 var abs = Math.abs;
  7 
  8 
  9 
 10 var makeNode = function(data) {
 11     return {
 12         data : data,
 13         balance : 0,
 14         left : null,
 15         right : null
 16     }
 17 };
 18 
 19 var rotateSingle = function(root, dir, otherDir) {
 20     var save = root[otherDir];
 21     root[otherDir] = save[dir];
 22     save[dir] = root;
 23     return save;
 24 };
 25 
 26 
 27 var rotateDouble = function(root, dir, otherDir) {
 28     root[otherDir] = rotateSingle(root[otherDir], otherDir, dir);
 29     return rotateSingle(root, dir, otherDir);
 30 };
 31 
 32 var adjustBalance = function(root, dir, bal) {
 33     var otherDir = dir == "left" ? "right" : "left";
 34     var n = root[dir], nn = n[otherDir];
 35     if (nn.balance == 0)
 36         root.balance = n.balance = 0;
 37     else if (nn.balance == bal) {
 38         root.balance = -bal;
 39         n.balance = 0;
 40     }
 41     else { /* nn.balance == -bal */
 42         root.balance = 0;
 43         n.balance = bal;
 44     }
 45     nn.balance = 0;
 46 };
 47 
 48 var insertAdjustBalance = function(root, dir) {
 49     var otherDir = dir == "left" ? "right" : "left";
 50 
 51     var n = root[dir];
 52     var bal = dir == "left" ? -1 : +1;
 53 
 54     if (n.balance == bal) {
 55         root.balance = n.balance = 0;
 56         root = rotateSingle(root, otherDir, dir);
 57     }
 58     else {
 59         adjustBalance(root, dir, bal);
 60         root = rotateDouble(root, otherDir, dir);
 61     }
 62 
 63     return root;
 64 
 65 };
 66 
 67 var removeAdjustBalance = function(root, dir, done) {
 68     var otherDir = dir == "left" ? "right" : "left";
 69     var n = root[otherDir];
 70     var bal = dir == "left" ? -1 : 1;
 71     if (n.balance == -bal) {
 72         root.balance = n.balance = 0;
 73         root = rotateSingle(root, dir, otherDir);
 74     }
 75     else if (n.balance == bal) {
 76         adjustBalance(root, otherDir, -bal);
 77         root = rotateDouble(root, dir, otherDir);
 78     }
 79     else { /* n.balance == 0 */
 80         root.balance = -bal;
 81         n.balance = bal;
 82         root = rotateSingle(root, dir, otherDir);
 83         done.done = true;
 84     }
 85     return root;
 86 };
 87 
 88 var insert = function(root, data, done, compare) {
 89     if (root == null || root == undefined)
 90         root = makeNode(data);
 91     else {
 92         var dir = compare(data, root.data) == -1 ? "left" : "right";
 93         root[dir] = insert(root[dir], data, done, compare);
 94 
 95         if (!done.done) {
 96             /* Update balance factors */
 97             root.balance += dir == "left" ? -1 : 1;
 98             /* Rebalance as necessary and terminate */
 99             if (root.balance == 0)
100                 done.done = true;
101             else if (abs(root.balance) > 1) {
102                 root = insertAdjustBalance(root, dir);
103                 done.done = true;
104             }
105         }
106     }
107 
108     return root;
109 };
110 
111 var remove = function(root, data, done, compare) {
112     var dir, cmp, save, b;
113     if (root) {
114         //Remove node
115         cmp = compare(data, root.data);
116         if (cmp === 0) {
117             // Unlink and fix parent
118             var l = root.left, r = root.right;
119             if (!l || !r) {
120                 dir = !l ? "right" : "left";
121                 save = root[dir];
122                 return save;
123             }
124             else {
125                 var heir = l, r;
126                 while ((r = heir.right) != null) {
127                     heir = r;
128                 }
129                 root.data = heir.data;
130                 //reset and start searching
131                 data = heir.data;
132             }
133         }
134         dir = compare(root.data, data) == -1 ? "right" : "left";
135         root[dir] = remove(root[dir], data, done, compare);
136         if (!done.done) {
137             /* Update balance factors */
138             b = (root.balance += (dir == "left" ? 1 : -1));
139             /* Terminate or rebalance as necessary */
140             var a = abs(b);
141             if (a === 1)
142                 done.done = true;
143             else if (a > 1)
144                 root = removeAdjustBalance(root, dir, done);
145         }
146     }
147     return root;
148 };
149 
150 
151 /**
152  * @class <p>An AVL tree is a self-balancing binary search tree.
153  *    In an AVL tree, the heights of the two child subtrees of any node differ by at most one.
154  *    Lookup, insertion, and deletion all take O(log n) time in both the average and worst cases,
155  *    where n is the number of nodes in the tree prior to the operation.
156  *    Insertions and deletions may require the tree to be rebalanced by one or more tree rotations.</p>
157  * <p>AVL trees are more rigidly balanced than red-black trees, leading to slower insertion and removal but faster retrieval</p>
158  *
159  * <b>Performance</b>
160  * <table>
161  *     <tr><td></td><td>Best</td><td>Worst</td></tr>
162  *     <tr><td>Space</td><td>O(n)</td><td>O(n)</td></tr>
163  *     <tr><td>Search</td><td>O(log n)</td><td>O(log n)</td></tr>
164  *     <tr><td>Insert</td><td>O(log n)</td><td>O(log n)</td></tr>
165  *     <tr><td>Delete</td><td>O(log n)</td><td>O(log n)</td></tr>
166  * <table>
167  * @name AVLTree
168  * @augments comb.collections.Tree
169  * @memberOf comb.collections
170  */
171 module.exports = exports  = define(Tree, {
172     instance : {
173         /**@lends comb.collections.AVLTree.prototype*/
174 
175         insert : function(data) {
176             var done = {done : false};
177             this.__root = insert(this.__root, data, done, this.compare);
178         },
179 
180 
181         remove : function(data) {
182             this.__root = remove(this.__root, data, {done : false}, this.compare);
183         },
184 
185         __printNode : function(node, level) {
186             var str = [];
187             if (node == null) {
188                 str.push(multiply('\t', level));
189                 str.push("~");
190                 console.log(str.join(""));
191             } else {
192                 this.__printNode(node.right, level + 1);
193                 str.push(multiply('\t', level));
194                 str.push(node.data + ":" + node.balance + "\n");
195                 console.log(str.join(""));
196                 this.__printNode(node.left, level + 1);
197             }
198         }
199 
200     }
201 });
202 
203