1var populateComponents = require('./properties/populate-components');
2
3var wrapForOptimizing = require('../wrap-for-optimizing').single;
4var restoreFromOptimizing = require('../restore-from-optimizing');
5
6var Token = require('../../tokenizer/token');
7
8var animationNameRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation-name$/;
9var animationRegex = /^(\-moz\-|\-o\-|\-webkit\-)?animation$/;
10var keyframeRegex = /^@(\-moz\-|\-o\-|\-webkit\-)?keyframes /;
11var importantRegex = /\s{0,31}!important$/;
12var optionalMatchingQuotesRegex = /^(['"]?)(.*)\1$/;
13
14function normalize(value) {
15  return value
16    .replace(optionalMatchingQuotesRegex, '$2')
17    .replace(importantRegex, '');
18}
19
20function removeUnusedAtRules(tokens, context) {
21  removeUnusedAtRule(tokens, matchCounterStyle, markCounterStylesAsUsed, context);
22  removeUnusedAtRule(tokens, matchFontFace, markFontFacesAsUsed, context);
23  removeUnusedAtRule(tokens, matchKeyframe, markKeyframesAsUsed, context);
24  removeUnusedAtRule(tokens, matchNamespace, markNamespacesAsUsed, context);
25}
26
27function removeUnusedAtRule(tokens, matchCallback, markCallback, context) {
28  var atRules = {};
29  var atRule;
30  var atRuleTokens;
31  var atRuleToken;
32  var zeroAt;
33  var i, l;
34
35  for (i = 0, l = tokens.length; i < l; i++) {
36    matchCallback(tokens[i], atRules);
37  }
38
39  if (Object.keys(atRules).length === 0) {
40    return;
41  }
42
43  markUsedAtRules(tokens, markCallback, atRules, context);
44
45  for (atRule in atRules) {
46    atRuleTokens = atRules[atRule];
47
48    for (i = 0, l = atRuleTokens.length; i < l; i++) {
49      atRuleToken = atRuleTokens[i];
50      zeroAt = atRuleToken[0] == Token.AT_RULE ? 1 : 2;
51      atRuleToken[zeroAt] = [];
52    }
53  }
54}
55
56function markUsedAtRules(tokens, markCallback, atRules, context) {
57  var boundMarkCallback = markCallback(atRules);
58  var i, l;
59
60  for (i = 0, l = tokens.length; i < l; i++) {
61    switch (tokens[i][0]) {
62      case Token.RULE:
63        boundMarkCallback(tokens[i], context);
64        break;
65      case Token.NESTED_BLOCK:
66        markUsedAtRules(tokens[i][2], markCallback, atRules, context);
67    }
68  }
69}
70
71function matchCounterStyle(token, atRules) {
72  var match;
73
74  if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1].indexOf('@counter-style') === 0) {
75    match = token[1][0][1].split(' ')[1];
76    atRules[match] = atRules[match] || [];
77    atRules[match].push(token);
78  }
79}
80
81function markCounterStylesAsUsed(atRules) {
82  return function (token, context) {
83    var property;
84    var wrappedProperty;
85    var i, l;
86
87    for (i = 0, l = token[2].length; i < l; i++) {
88      property = token[2][i];
89
90      if (property[1][1] == 'list-style') {
91        wrappedProperty = wrapForOptimizing(property);
92        populateComponents([wrappedProperty], context.validator, context.warnings);
93
94        if (wrappedProperty.components[0].value[0][1] in atRules) {
95          delete atRules[property[2][1]];
96        }
97
98        restoreFromOptimizing([wrappedProperty]);
99      }
100
101      if (property[1][1] == 'list-style-type' && property[2][1] in atRules) {
102        delete atRules[property[2][1]];
103      }
104    }
105  };
106}
107
108function matchFontFace(token, atRules) {
109  var property;
110  var match;
111  var i, l;
112
113  if (token[0] == Token.AT_RULE_BLOCK && token[1][0][1] == '@font-face') {
114    for (i = 0, l = token[2].length; i < l; i++) {
115      property = token[2][i];
116
117      if (property[1][1] == 'font-family') {
118        match = normalize(property[2][1].toLowerCase());
119        atRules[match] = atRules[match] || [];
120        atRules[match].push(token);
121        break;
122      }
123    }
124  }
125}
126
127function markFontFacesAsUsed(atRules) {
128  return function (token, context) {
129    var property;
130    var wrappedProperty;
131    var component;
132    var normalizedMatch;
133    var i, l;
134    var j, m;
135
136    for (i = 0, l = token[2].length; i < l; i++) {
137      property = token[2][i];
138
139      if (property[1][1] == 'font') {
140        wrappedProperty = wrapForOptimizing(property);
141        populateComponents([wrappedProperty], context.validator, context.warnings);
142        component = wrappedProperty.components[6];
143
144        for (j = 0, m = component.value.length; j < m; j++) {
145          normalizedMatch = normalize(component.value[j][1].toLowerCase());
146
147          if (normalizedMatch in atRules) {
148            delete atRules[normalizedMatch];
149          }
150        }
151
152        restoreFromOptimizing([wrappedProperty]);
153      }
154
155      if (property[1][1] == 'font-family') {
156        for (j = 2, m = property.length; j < m; j++) {
157          normalizedMatch = normalize(property[j][1].toLowerCase());
158
159          if (normalizedMatch in atRules) {
160            delete atRules[normalizedMatch];
161          }
162        }
163      }
164    }
165  };
166}
167
168function matchKeyframe(token, atRules) {
169  var match;
170
171  if (token[0] == Token.NESTED_BLOCK && keyframeRegex.test(token[1][0][1])) {
172    match = token[1][0][1].split(' ')[1];
173    atRules[match] = atRules[match] || [];
174    atRules[match].push(token);
175  }
176}
177
178function markKeyframesAsUsed(atRules) {
179  return function (token, context) {
180    var property;
181    var wrappedProperty;
182    var component;
183    var i, l;
184    var j, m;
185
186    for (i = 0, l = token[2].length; i < l; i++) {
187      property = token[2][i];
188
189      if (animationRegex.test(property[1][1])) {
190        wrappedProperty = wrapForOptimizing(property);
191        populateComponents([wrappedProperty], context.validator, context.warnings);
192        component = wrappedProperty.components[7];
193
194        for (j = 0, m = component.value.length; j < m; j++) {
195          if (component.value[j][1] in atRules) {
196            delete atRules[component.value[j][1]];
197          }
198        }
199
200        restoreFromOptimizing([wrappedProperty]);
201      }
202
203      if (animationNameRegex.test(property[1][1])) {
204        for (j = 2, m = property.length; j < m; j++) {
205          if (property[j][1] in atRules) {
206            delete atRules[property[j][1]];
207          }
208        }
209      }
210    }
211  };
212}
213
214function matchNamespace(token, atRules) {
215  var match;
216
217  if (token[0] == Token.AT_RULE && token[1].indexOf('@namespace') === 0) {
218    match = token[1].split(' ')[1];
219    atRules[match] = atRules[match] || [];
220    atRules[match].push(token);
221  }
222}
223
224function markNamespacesAsUsed(atRules) {
225  var namespaceRegex = new RegExp(Object.keys(atRules).join('\\\||') + '\\\|', 'g');
226
227  return function (token) {
228    var match;
229    var scope;
230    var normalizedMatch;
231    var i, l;
232    var j, m;
233
234    for (i = 0, l = token[1].length; i < l; i++) {
235      scope = token[1][i];
236      match = scope[1].match(namespaceRegex);
237
238      for (j = 0, m = match.length; j < m; j++) {
239        normalizedMatch = match[j].substring(0, match[j].length - 1);
240
241        if (normalizedMatch in atRules) {
242          delete atRules[normalizedMatch];
243        }
244      }
245    }
246  };
247}
248
249module.exports = removeUnusedAtRules;
250