1 // ==========================================================================
  2 // Project:   SC.Statechart - A Statechart Framework for SproutCore
  3 // Copyright: ©2010, 2011 Michael Cohen, and contributors.
  4 //            Portions @2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /*globals SC */
  9 
 10 SC.StatechartSequenceMatcher = SC.Object.extend({
 11   
 12   statechartMonitor: null,
 13   
 14   match: null,
 15   
 16   MISMATCH: {},
 17   
 18   begin: function() {
 19     this._stack = [];
 20     this.beginSequence();
 21     this._start = this._stack[0];
 22     return this;
 23   },
 24   
 25   end: function() {
 26     this.endSequence();
 27     
 28     if (this._stack.length > 0) {
 29       throw new Error("can not match sequence. sequence matcher has been left in an invalid state");
 30     }
 31     
 32     var monitor = this.statechartMonitor,
 33         result = this._matchSequence(this._start, 0) === monitor.sequence.length;
 34 
 35     this.set('match', result);
 36     
 37     return result;
 38   },
 39   
 40   entered: function() {
 41     this._addStatesToCurrentGroup('entered', arguments);
 42     return this;
 43   },
 44   
 45   exited: function() {
 46     this._addStatesToCurrentGroup('exited', arguments);
 47     return this;
 48   },
 49   
 50   beginConcurrent: function() {
 51     var group = {
 52       type: 'concurrent',
 53       values: []
 54     };
 55     if (this._peek()) this._peek().values.push(group);
 56     this._stack.push(group);
 57     return this;
 58   },
 59   
 60   endConcurrent: function() {
 61     this._stack.pop();
 62     return this;
 63   },
 64   
 65   beginSequence: function() {
 66     var group = {
 67       type: 'sequence',
 68       values: []
 69     };
 70     if (this._peek()) this._peek().values.push(group);
 71     this._stack.push(group);
 72     return this;
 73   },
 74   
 75   endSequence: function() {
 76     this._stack.pop();
 77     return this;
 78   },
 79   
 80   _peek: function() {
 81     var len = this._stack.length;
 82     return len === 0 ? null : this._stack[len - 1];
 83   },
 84   
 85   _addStatesToCurrentGroup: function(action, states) {
 86     var group = this._peek(), len = states.length, i = 0;
 87     for (; i < len; i += 1) {
 88       group.values.push({ action: action, state: states[i] });
 89     }
 90   },
 91   
 92   _matchSequence: function(sequence, marker) {
 93     var values = sequence.values, 
 94         len = values.length, 
 95         i = 0, val,
 96         monitor = this.statechartMonitor;
 97         
 98     if (len === 0) return marker;
 99     if (marker > monitor.sequence.length) return this.MISMATCH;
100         
101     for (; i < len; i += 1) {
102       val = values[i];
103       
104       if (val.type === 'sequence') {
105         marker = this._matchSequence(val, marker);
106       } else if (val.type === 'concurrent') {
107         marker = this._matchConcurrent(val, marker);
108       } else if (!this._matchItems(val, monitor.sequence[marker])){
109         return this.MISMATCH;
110       } else {
111         marker += 1;
112       }
113       
114       if (marker === this.MISMATCH) return this.MISMATCH;
115     }
116     
117     return marker;
118   },
119 
120   // A
121   // B (concurrent [X, Y])
122   //   X
123   //     M
124   //     N
125   //   Y
126   //     O
127   //     P
128   // C
129   // 
130   // 0 1  2 3 4   5 6 7  8
131   //      ^       ^
132   // A B (X M N) (Y O P) C
133   //      ^       ^
134   // A B (Y O P) (X M N) C
135   
136   _matchConcurrent: function(concurrent, marker) {
137     var values = SC.clone(concurrent.values), 
138         len = values.length, 
139         i = 0, val, tempMarker = marker, match = false,
140         monitor = this.statechartMonitor;
141         
142     if (len === 0) return marker;
143     if (marker > monitor.sequence.length) return this.MISMATCH;
144     
145     while (values.length > 0) {
146       for (i = 0; i < len; i += 1) {
147         val = values[i];
148       
149         if (val.type === 'sequence') {
150           tempMarker = this._matchSequence(val, marker);
151         } else if (val.type === 'concurrent') {
152           tempMarker = this._matchConcurrent(val, marker);
153         } else if (!this._matchItems(val, monitor.sequence[marker])){
154           tempMarker = this.MISMATCH;
155         } else {
156           tempMarker = marker + 1;
157         }
158       
159         if (tempMarker !== this.MISMATCH) break;
160       }
161       
162       if (tempMarker === this.MISMATCH) return this.MISMATCH;
163       values.removeAt(i);
164       len = values.length;
165       marker = tempMarker;
166     }
167     
168     return marker;
169   },
170   
171   _matchItems: function(matcherItem, monitorItem) {
172     if (!matcherItem || !monitorItem) return false;
173   
174     if (matcherItem.action !== monitorItem.action) {
175       return false;
176     }
177     
178     if (SC.typeOf(matcherItem.state) === SC.T_OBJECT && matcherItem.state === monitorItem.state) {
179       return true;      
180     }
181     
182     if (matcherItem.state === monitorItem.state.get('name')) {
183       return true;
184     }
185   
186     return false;
187   }
188   
189 });