1var shallowClone = require('./clone').shallow;
2
3var Token = require('../../tokenizer/token');
4var Marker = require('../../tokenizer/marker');
5
6function isInheritOnly(values) {
7  for (var i = 0, l = values.length; i < l; i++) {
8    var value = values[i][1];
9
10    if (value != 'inherit' && value != Marker.COMMA && value != Marker.FORWARD_SLASH)
11      return false;
12  }
13
14  return true;
15}
16
17function background(property, compactable, lastInMultiplex) {
18  var components = property.components;
19  var restored = [];
20  var needsOne, needsBoth;
21
22  function restoreValue(component) {
23    Array.prototype.unshift.apply(restored, component.value);
24  }
25
26  function isDefaultValue(component) {
27    var descriptor = compactable[component.name];
28
29    if (descriptor.doubleValues && descriptor.defaultValue.length == 1) {
30      return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] == descriptor.defaultValue[0] : true);
31    } else if (descriptor.doubleValues && descriptor.defaultValue.length != 1) {
32      return component.value[0][1] == descriptor.defaultValue[0] && (component.value[1] ? component.value[1][1] : component.value[0][1]) == descriptor.defaultValue[1];
33    } else {
34      return component.value[0][1] == descriptor.defaultValue;
35    }
36  }
37
38  for (var i = components.length - 1; i >= 0; i--) {
39    var component = components[i];
40    var isDefault = isDefaultValue(component);
41
42    if (component.name == 'background-clip') {
43      var originComponent = components[i - 1];
44      var isOriginDefault = isDefaultValue(originComponent);
45
46      needsOne = component.value[0][1] == originComponent.value[0][1];
47
48      needsBoth = !needsOne && (
49        (isOriginDefault && !isDefault) ||
50        (!isOriginDefault && !isDefault) ||
51        (!isOriginDefault && isDefault && component.value[0][1] != originComponent.value[0][1]));
52
53      if (needsOne) {
54        restoreValue(originComponent);
55      } else if (needsBoth) {
56        restoreValue(component);
57        restoreValue(originComponent);
58      }
59
60      i--;
61    } else if (component.name == 'background-size') {
62      var positionComponent = components[i - 1];
63      var isPositionDefault = isDefaultValue(positionComponent);
64
65      needsOne = !isPositionDefault && isDefault;
66
67      needsBoth = !needsOne &&
68        (isPositionDefault && !isDefault || !isPositionDefault && !isDefault);
69
70      if (needsOne) {
71        restoreValue(positionComponent);
72      } else if (needsBoth) {
73        restoreValue(component);
74        restored.unshift([Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]);
75        restoreValue(positionComponent);
76      } else if (positionComponent.value.length == 1) {
77        restoreValue(positionComponent);
78      }
79
80      i--;
81    } else {
82      if (isDefault || compactable[component.name].multiplexLastOnly && !lastInMultiplex)
83        continue;
84
85      restoreValue(component);
86    }
87  }
88
89  if (restored.length === 0 && property.value.length == 1 && property.value[0][1] == '0')
90    restored.push(property.value[0]);
91
92  if (restored.length === 0)
93    restored.push([Token.PROPERTY_VALUE, compactable[property.name].defaultValue]);
94
95  if (isInheritOnly(restored))
96    return [restored[0]];
97
98  return restored;
99}
100
101function borderRadius(property, compactable) {
102  if (property.multiplex) {
103    var horizontal = shallowClone(property);
104    var vertical = shallowClone(property);
105
106    for (var i = 0; i < 4; i++) {
107      var component = property.components[i];
108
109      var horizontalComponent = shallowClone(property);
110      horizontalComponent.value = [component.value[0]];
111      horizontal.components.push(horizontalComponent);
112
113      var verticalComponent = shallowClone(property);
114      // FIXME: only shorthand compactor (see breakup#borderRadius) knows that border radius
115      // longhands have two values, whereas tokenizer does not care about populating 2nd value
116      // if it's missing, hence this fallback
117      verticalComponent.value = [component.value[1] || component.value[0]];
118      vertical.components.push(verticalComponent);
119    }
120
121    var horizontalValues = fourValues(horizontal, compactable);
122    var verticalValues = fourValues(vertical, compactable);
123
124    if (horizontalValues.length == verticalValues.length &&
125        horizontalValues[0][1] == verticalValues[0][1] &&
126        (horizontalValues.length > 1 ? horizontalValues[1][1] == verticalValues[1][1] : true) &&
127        (horizontalValues.length > 2 ? horizontalValues[2][1] == verticalValues[2][1] : true) &&
128        (horizontalValues.length > 3 ? horizontalValues[3][1] == verticalValues[3][1] : true)) {
129      return horizontalValues;
130    } else {
131      return horizontalValues.concat([[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]).concat(verticalValues);
132    }
133  } else {
134    return fourValues(property, compactable);
135  }
136}
137
138function font(property, compactable) {
139  var components = property.components;
140  var restored = [];
141  var component;
142  var componentIndex = 0;
143  var fontFamilyIndex = 0;
144
145  if (property.value[0][1].indexOf(Marker.INTERNAL) === 0) {
146    property.value[0][1] = property.value[0][1].substring(Marker.INTERNAL.length);
147    return property.value;
148  }
149
150  // first four components are optional
151  while (componentIndex < 4) {
152    component = components[componentIndex];
153
154    if (component.value[0][1] != compactable[component.name].defaultValue) {
155      Array.prototype.push.apply(restored, component.value);
156    }
157
158    componentIndex++;
159  }
160
161  // then comes font-size
162  Array.prototype.push.apply(restored, components[componentIndex].value);
163  componentIndex++;
164
165  // then may come line-height
166  if (components[componentIndex].value[0][1] != compactable[components[componentIndex].name].defaultValue) {
167    Array.prototype.push.apply(restored, [[Token.PROPERTY_VALUE, Marker.FORWARD_SLASH]]);
168    Array.prototype.push.apply(restored, components[componentIndex].value);
169  }
170
171  componentIndex++;
172
173  // then comes font-family
174  while (components[componentIndex].value[fontFamilyIndex]) {
175    restored.push(components[componentIndex].value[fontFamilyIndex]);
176
177    if (components[componentIndex].value[fontFamilyIndex + 1]) {
178      restored.push([Token.PROPERTY_VALUE, Marker.COMMA]);
179    }
180
181    fontFamilyIndex++;
182  }
183
184  if (isInheritOnly(restored)) {
185    return [restored[0]];
186  }
187
188  return restored;
189}
190
191function fourValues(property) {
192  var components = property.components;
193  var value1 = components[0].value[0];
194  var value2 = components[1].value[0];
195  var value3 = components[2].value[0];
196  var value4 = components[3].value[0];
197
198  if (value1[1] == value2[1] && value1[1] == value3[1] && value1[1] == value4[1]) {
199    return [value1];
200  } else if (value1[1] == value3[1] && value2[1] == value4[1]) {
201    return [value1, value2];
202  } else if (value2[1] == value4[1]) {
203    return [value1, value2, value3];
204  } else {
205    return [value1, value2, value3, value4];
206  }
207}
208
209function multiplex(restoreWith) {
210  return function (property, compactable) {
211    if (!property.multiplex)
212      return restoreWith(property, compactable, true);
213
214    var multiplexSize = 0;
215    var restored = [];
216    var componentMultiplexSoFar = {};
217    var i, l;
218
219    // At this point we don't know what's the multiplex size, e.g. how many background layers are there
220    for (i = 0, l = property.components[0].value.length; i < l; i++) {
221      if (property.components[0].value[i][1] == Marker.COMMA)
222        multiplexSize++;
223    }
224
225    for (i = 0; i <= multiplexSize; i++) {
226      var _property = shallowClone(property);
227
228      // We split multiplex into parts and restore them one by one
229      for (var j = 0, m = property.components.length; j < m; j++) {
230        var componentToClone = property.components[j];
231        var _component = shallowClone(componentToClone);
232        _property.components.push(_component);
233
234        // The trick is some properties has more than one value, so we iterate over values looking for
235        // a multiplex separator - a comma
236        for (var k = componentMultiplexSoFar[_component.name] || 0, n = componentToClone.value.length; k < n; k++) {
237          if (componentToClone.value[k][1] == Marker.COMMA) {
238            componentMultiplexSoFar[_component.name] = k + 1;
239            break;
240          }
241
242          _component.value.push(componentToClone.value[k]);
243        }
244      }
245
246      // No we can restore shorthand value
247      var lastInMultiplex = i == multiplexSize;
248      var _restored = restoreWith(_property, compactable, lastInMultiplex);
249      Array.prototype.push.apply(restored, _restored);
250
251      if (i < multiplexSize)
252        restored.push([Token.PROPERTY_VALUE, Marker.COMMA]);
253    }
254
255    return restored;
256  };
257}
258
259function withoutDefaults(property, compactable) {
260  var components = property.components;
261  var restored = [];
262
263  for (var i = components.length - 1; i >= 0; i--) {
264    var component = components[i];
265    var descriptor = compactable[component.name];
266
267    if (component.value[0][1] != descriptor.defaultValue || ('keepUnlessDefault' in descriptor) && !isDefault(components, compactable, descriptor.keepUnlessDefault)) {
268      restored.unshift(component.value[0]);
269    }
270  }
271
272  if (restored.length === 0)
273    restored.push([Token.PROPERTY_VALUE, compactable[property.name].defaultValue]);
274
275  if (isInheritOnly(restored))
276    return [restored[0]];
277
278  return restored;
279}
280
281function isDefault(components, compactable, propertyName) {
282  var component;
283  var i, l;
284
285  for (i = 0, l = components.length; i < l; i++) {
286    component = components[i];
287
288    if (component.name == propertyName && component.value[0][1] == compactable[propertyName].defaultValue) {
289      return true;
290    }
291  }
292
293  return false;
294}
295
296module.exports = {
297  background: background,
298  borderRadius: borderRadius,
299  font: font,
300  fourValues: fourValues,
301  multiplex: multiplex,
302  withoutDefaults: withoutDefaults
303};
304