1var everyValuesPair = require('./every-values-pair');
2var hasInherit = require('./has-inherit');
3var populateComponents = require('./populate-components');
4
5var compactable = require('../compactable');
6var deepClone = require('../clone').deep;
7var restoreWithComponents = require('../restore-with-components');
8
9var restoreFromOptimizing = require('../../restore-from-optimizing');
10var wrapSingle = require('../../wrap-for-optimizing').single;
11
12var serializeBody = require('../../../writer/one-time').body;
13var Token = require('../../../tokenizer/token');
14
15function mergeIntoShorthands(properties, validator) {
16  var candidates = {};
17  var descriptor;
18  var componentOf;
19  var property;
20  var i, l;
21  var j, m;
22
23  // there is no shorthand property made up of less than 3 longhands
24  if (properties.length < 3) {
25    return;
26  }
27
28  for (i = 0, l = properties.length; i < l; i++) {
29    property = properties[i];
30    descriptor = compactable[property.name];
31
32    if (property.unused) {
33      continue;
34    }
35
36    if (property.hack) {
37      continue;
38    }
39
40    if (property.block) {
41      continue;
42    }
43
44    invalidateOrCompact(properties, i, candidates, validator);
45
46    if (descriptor && descriptor.componentOf) {
47      for (j = 0, m = descriptor.componentOf.length; j < m; j++) {
48        componentOf = descriptor.componentOf[j];
49
50        candidates[componentOf] = candidates[componentOf] || {};
51        candidates[componentOf][property.name] = property;
52      }
53    }
54  }
55
56  invalidateOrCompact(properties, i, candidates, validator);
57}
58
59function invalidateOrCompact(properties, position, candidates, validator) {
60  var invalidatedBy = properties[position];
61  var shorthandName;
62  var shorthandDescriptor;
63  var candidateComponents;
64
65  for (shorthandName in candidates) {
66    if (undefined !== invalidatedBy && shorthandName == invalidatedBy.name) {
67      continue;
68    }
69
70    shorthandDescriptor = compactable[shorthandName];
71    candidateComponents = candidates[shorthandName];
72    if (invalidatedBy && invalidates(candidates, shorthandName, invalidatedBy)) {
73      delete candidates[shorthandName];
74      continue;
75    }
76
77    if (shorthandDescriptor.components.length > Object.keys(candidateComponents).length) {
78      continue;
79    }
80
81    if (mixedImportance(candidateComponents)) {
82      continue;
83    }
84
85    if (!overridable(candidateComponents, shorthandName, validator)) {
86      continue;
87    }
88
89    if (!mergeable(candidateComponents)) {
90      continue;
91    }
92
93    if (mixedInherit(candidateComponents)) {
94      replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator);
95    } else {
96      replaceWithShorthand(properties, candidateComponents, shorthandName, validator);
97    }
98  }
99}
100
101function invalidates(candidates, shorthandName, invalidatedBy) {
102  var shorthandDescriptor = compactable[shorthandName];
103  var invalidatedByDescriptor = compactable[invalidatedBy.name];
104  var componentName;
105
106  if ('overridesShorthands' in shorthandDescriptor && shorthandDescriptor.overridesShorthands.indexOf(invalidatedBy.name) > -1) {
107    return true;
108  }
109
110  if (invalidatedByDescriptor && 'componentOf' in invalidatedByDescriptor) {
111    for (componentName in candidates[shorthandName]) {
112      if (invalidatedByDescriptor.componentOf.indexOf(componentName) > -1) {
113        return true;
114      }
115    }
116  }
117
118  return false;
119}
120
121function mixedImportance(components) {
122  var important;
123  var componentName;
124
125  for (componentName in components) {
126    if (undefined !== important && components[componentName].important != important) {
127      return true;
128    }
129
130    important = components[componentName].important;
131  }
132
133  return false;
134}
135
136function overridable(components, shorthandName, validator) {
137  var descriptor = compactable[shorthandName];
138  var newValuePlaceholder = [
139    Token.PROPERTY,
140    [Token.PROPERTY_NAME, shorthandName],
141    [Token.PROPERTY_VALUE, descriptor.defaultValue]
142  ];
143  var newProperty = wrapSingle(newValuePlaceholder);
144  var component;
145  var mayOverride;
146  var i, l;
147
148  populateComponents([newProperty], validator, []);
149
150  for (i = 0, l = descriptor.components.length; i < l; i++) {
151    component = components[descriptor.components[i]];
152    mayOverride = compactable[component.name].canOverride;
153
154    if (!everyValuesPair(mayOverride.bind(null, validator), newProperty.components[i], component)) {
155      return false;
156    }
157  }
158
159  return true;
160}
161
162function mergeable(components) {
163  var lastCount = null;
164  var currentCount;
165  var componentName;
166  var component;
167  var descriptor;
168  var values;
169
170  for (componentName in components) {
171    component = components[componentName];
172    descriptor = compactable[componentName];
173
174    if (!('restore' in descriptor)) {
175      continue;
176    }
177
178    restoreFromOptimizing([component.all[component.position]], restoreWithComponents);
179    values = descriptor.restore(component, compactable);
180
181    currentCount = values.length;
182
183    if (lastCount !== null && currentCount !== lastCount) {
184      return false;
185    }
186
187    lastCount = currentCount;
188  }
189
190  return true;
191}
192
193function mixedInherit(components) {
194  var componentName;
195  var lastValue = null;
196  var currentValue;
197
198  for (componentName in components) {
199    currentValue = hasInherit(components[componentName]);
200
201    if (lastValue !== null && lastValue !== currentValue) {
202      return true;
203    }
204
205    lastValue = currentValue;
206  }
207
208  return false;
209}
210
211function replaceWithInheritBestFit(properties, candidateComponents, shorthandName, validator) {
212  var viaLonghands = buildSequenceWithInheritLonghands(candidateComponents, shorthandName, validator);
213  var viaShorthand = buildSequenceWithInheritShorthand(candidateComponents, shorthandName, validator);
214  var longhandTokensSequence = viaLonghands[0];
215  var shorthandTokensSequence = viaShorthand[0];
216  var isLonghandsShorter = serializeBody(longhandTokensSequence).length < serializeBody(shorthandTokensSequence).length;
217  var newTokensSequence = isLonghandsShorter ? longhandTokensSequence : shorthandTokensSequence;
218  var newProperty = isLonghandsShorter ? viaLonghands[1] : viaShorthand[1];
219  var newComponents = isLonghandsShorter ? viaLonghands[2] : viaShorthand[2];
220  var all = candidateComponents[Object.keys(candidateComponents)[0]].all;
221  var componentName;
222  var oldComponent;
223  var newComponent;
224  var newToken;
225
226  newProperty.position = all.length;
227  newProperty.shorthand = true;
228  newProperty.dirty = true;
229  newProperty.all = all;
230  newProperty.all.push(newTokensSequence[0]);
231
232  properties.push(newProperty);
233
234  for (componentName in candidateComponents) {
235    oldComponent = candidateComponents[componentName];
236    oldComponent.unused = true;
237
238    if (oldComponent.name in newComponents) {
239      newComponent = newComponents[oldComponent.name];
240      newToken = findTokenIn(newTokensSequence, componentName);
241
242      newComponent.position = all.length;
243      newComponent.all = all;
244      newComponent.all.push(newToken);
245
246      properties.push(newComponent);
247    }
248  }
249}
250
251function buildSequenceWithInheritLonghands(components, shorthandName, validator) {
252  var tokensSequence = [];
253  var inheritComponents = {};
254  var nonInheritComponents = {};
255  var descriptor = compactable[shorthandName];
256  var shorthandToken = [
257    Token.PROPERTY,
258    [Token.PROPERTY_NAME, shorthandName],
259    [Token.PROPERTY_VALUE, descriptor.defaultValue]
260  ];
261  var newProperty = wrapSingle(shorthandToken);
262  var component;
263  var longhandToken;
264  var newComponent;
265  var nameMetadata;
266  var i, l;
267
268  populateComponents([newProperty], validator, []);
269
270  for (i = 0, l = descriptor.components.length; i < l; i++) {
271    component = components[descriptor.components[i]];
272
273    if (hasInherit(component)) {
274      longhandToken = component.all[component.position].slice(0, 2);
275      Array.prototype.push.apply(longhandToken, component.value);
276      tokensSequence.push(longhandToken);
277
278      newComponent = deepClone(component);
279      newComponent.value = inferComponentValue(components, newComponent.name);
280
281      newProperty.components[i] = newComponent;
282      inheritComponents[component.name] = deepClone(component);
283    } else {
284      newComponent = deepClone(component);
285      newComponent.all = component.all;
286      newProperty.components[i] = newComponent;
287
288      nonInheritComponents[component.name] = component;
289    }
290  }
291
292  nameMetadata = joinMetadata(nonInheritComponents, 1);
293  shorthandToken[1].push(nameMetadata);
294
295  restoreFromOptimizing([newProperty], restoreWithComponents);
296
297  shorthandToken = shorthandToken.slice(0, 2);
298  Array.prototype.push.apply(shorthandToken, newProperty.value);
299
300  tokensSequence.unshift(shorthandToken);
301
302  return [tokensSequence, newProperty, inheritComponents];
303}
304
305function inferComponentValue(components, propertyName) {
306  var descriptor = compactable[propertyName];
307
308  if ('oppositeTo' in descriptor) {
309    return components[descriptor.oppositeTo].value;
310  } else {
311    return [[Token.PROPERTY_VALUE, descriptor.defaultValue]];
312  }
313}
314
315function joinMetadata(components, at) {
316  var metadata = [];
317  var component;
318  var originalValue;
319  var componentMetadata;
320  var componentName;
321
322  for (componentName in components) {
323    component = components[componentName];
324    originalValue = component.all[component.position];
325    componentMetadata = originalValue[at][originalValue[at].length - 1];
326
327    Array.prototype.push.apply(metadata, componentMetadata);
328  }
329
330  return metadata.sort(metadataSorter);
331}
332
333function metadataSorter(metadata1, metadata2) {
334  var line1 = metadata1[0];
335  var line2 = metadata2[0];
336  var column1 = metadata1[1];
337  var column2 = metadata2[1];
338
339  if (line1 < line2) {
340    return -1;
341  } else if (line1 === line2) {
342    return column1 < column2 ? -1 : 1;
343  } else {
344    return 1;
345  }
346}
347
348function buildSequenceWithInheritShorthand(components, shorthandName, validator) {
349  var tokensSequence = [];
350  var inheritComponents = {};
351  var nonInheritComponents = {};
352  var descriptor = compactable[shorthandName];
353  var shorthandToken = [
354    Token.PROPERTY,
355    [Token.PROPERTY_NAME, shorthandName],
356    [Token.PROPERTY_VALUE, 'inherit']
357  ];
358  var newProperty = wrapSingle(shorthandToken);
359  var component;
360  var longhandToken;
361  var nameMetadata;
362  var valueMetadata;
363  var i, l;
364
365  populateComponents([newProperty], validator, []);
366
367  for (i = 0, l = descriptor.components.length; i < l; i++) {
368    component = components[descriptor.components[i]];
369
370    if (hasInherit(component)) {
371      inheritComponents[component.name] = component;
372    } else {
373      longhandToken = component.all[component.position].slice(0, 2);
374      Array.prototype.push.apply(longhandToken, component.value);
375      tokensSequence.push(longhandToken);
376
377      nonInheritComponents[component.name] = deepClone(component);
378    }
379  }
380
381  nameMetadata = joinMetadata(inheritComponents, 1);
382  shorthandToken[1].push(nameMetadata);
383
384  valueMetadata = joinMetadata(inheritComponents, 2);
385  shorthandToken[2].push(valueMetadata);
386
387  tokensSequence.unshift(shorthandToken);
388
389  return [tokensSequence, newProperty, nonInheritComponents];
390}
391
392function findTokenIn(tokens, componentName) {
393  var i, l;
394
395  for (i = 0, l = tokens.length; i < l; i++) {
396    if (tokens[i][1][1] == componentName) {
397      return tokens[i];
398    }
399  }
400}
401
402function replaceWithShorthand(properties, candidateComponents, shorthandName, validator) {
403  var descriptor = compactable[shorthandName];
404  var nameMetadata;
405  var valueMetadata;
406  var newValuePlaceholder = [
407    Token.PROPERTY,
408    [Token.PROPERTY_NAME, shorthandName],
409    [Token.PROPERTY_VALUE, descriptor.defaultValue]
410  ];
411  var all;
412
413  var newProperty = wrapSingle(newValuePlaceholder);
414  newProperty.shorthand = true;
415  newProperty.dirty = true;
416
417  populateComponents([newProperty], validator, []);
418
419  for (var i = 0, l = descriptor.components.length; i < l; i++) {
420    var component = candidateComponents[descriptor.components[i]];
421
422    newProperty.components[i] = deepClone(component);
423    newProperty.important = component.important;
424
425    all = component.all;
426  }
427
428  for (var componentName in candidateComponents) {
429    candidateComponents[componentName].unused = true;
430  }
431
432  nameMetadata = joinMetadata(candidateComponents, 1);
433  newValuePlaceholder[1].push(nameMetadata);
434
435  valueMetadata = joinMetadata(candidateComponents, 2);
436  newValuePlaceholder[2].push(valueMetadata);
437
438  newProperty.position = all.length;
439  newProperty.all = all;
440  newProperty.all.push(newValuePlaceholder);
441
442  properties.push(newProperty);
443}
444
445module.exports = mergeIntoShorthands;
446