1var isMergeable = require('./is-mergeable');
2
3var optimizeProperties = require('./properties/optimize');
4
5var cloneArray = require('../../utils/clone-array');
6
7var Token = require('../../tokenizer/token');
8
9var serializeBody = require('../../writer/one-time').body;
10var serializeRules = require('../../writer/one-time').rules;
11
12function reduceNonAdjacent(tokens, context) {
13  var options = context.options;
14  var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
15  var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
16  var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging;
17  var candidates = {};
18  var repeated = [];
19
20  for (var i = tokens.length - 1; i >= 0; i--) {
21    var token = tokens[i];
22
23    if (token[0] != Token.RULE) {
24      continue;
25    } else if (token[2].length === 0) {
26      continue;
27    }
28
29    var selectorAsString = serializeRules(token[1]);
30    var isComplexAndNotSpecial = token[1].length > 1 &&
31      isMergeable(selectorAsString, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging);
32    var wrappedSelectors = wrappedSelectorsFrom(token[1]);
33    var selectors = isComplexAndNotSpecial ?
34      [selectorAsString].concat(wrappedSelectors) :
35      [selectorAsString];
36
37    for (var j = 0, m = selectors.length; j < m; j++) {
38      var selector = selectors[j];
39
40      if (!candidates[selector])
41        candidates[selector] = [];
42      else
43        repeated.push(selector);
44
45      candidates[selector].push({
46        where: i,
47        list: wrappedSelectors,
48        isPartial: isComplexAndNotSpecial && j > 0,
49        isComplex: isComplexAndNotSpecial && j === 0
50      });
51    }
52  }
53
54  reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context);
55  reduceComplexNonAdjacentCases(tokens, candidates, options, context);
56}
57
58function wrappedSelectorsFrom(list) {
59  var wrapped = [];
60
61  for (var i = 0; i < list.length; i++) {
62    wrapped.push([list[i][1]]);
63  }
64
65  return wrapped;
66}
67
68function reduceSimpleNonAdjacentCases(tokens, repeated, candidates, options, context) {
69  function filterOut(idx, bodies) {
70    return data[idx].isPartial && bodies.length === 0;
71  }
72
73  function reduceBody(token, newBody, processedCount, tokenIdx) {
74    if (!data[processedCount - tokenIdx - 1].isPartial)
75      token[2] = newBody;
76  }
77
78  for (var i = 0, l = repeated.length; i < l; i++) {
79    var selector = repeated[i];
80    var data = candidates[selector];
81
82    reduceSelector(tokens, data, {
83      filterOut: filterOut,
84      callback: reduceBody
85    }, options, context);
86  }
87}
88
89function reduceComplexNonAdjacentCases(tokens, candidates, options, context) {
90  var mergeablePseudoClasses = options.compatibility.selectors.mergeablePseudoClasses;
91  var mergeablePseudoElements = options.compatibility.selectors.mergeablePseudoElements;
92  var multiplePseudoMerging = options.compatibility.selectors.multiplePseudoMerging;
93  var localContext = {};
94
95  function filterOut(idx) {
96    return localContext.data[idx].where < localContext.intoPosition;
97  }
98
99  function collectReducedBodies(token, newBody, processedCount, tokenIdx) {
100    if (tokenIdx === 0)
101      localContext.reducedBodies.push(newBody);
102  }
103
104  allSelectors:
105  for (var complexSelector in candidates) {
106    var into = candidates[complexSelector];
107    if (!into[0].isComplex)
108      continue;
109
110    var intoPosition = into[into.length - 1].where;
111    var intoToken = tokens[intoPosition];
112    var reducedBodies = [];
113
114    var selectors = isMergeable(complexSelector, mergeablePseudoClasses, mergeablePseudoElements, multiplePseudoMerging) ?
115      into[0].list :
116      [complexSelector];
117
118    localContext.intoPosition = intoPosition;
119    localContext.reducedBodies = reducedBodies;
120
121    for (var j = 0, m = selectors.length; j < m; j++) {
122      var selector = selectors[j];
123      var data = candidates[selector];
124
125      if (data.length < 2)
126        continue allSelectors;
127
128      localContext.data = data;
129
130      reduceSelector(tokens, data, {
131        filterOut: filterOut,
132        callback: collectReducedBodies
133      }, options, context);
134
135      if (serializeBody(reducedBodies[reducedBodies.length - 1]) != serializeBody(reducedBodies[0]))
136        continue allSelectors;
137    }
138
139    intoToken[2] = reducedBodies[0];
140  }
141}
142
143function reduceSelector(tokens, data, context, options, outerContext) {
144  var bodies = [];
145  var bodiesAsList = [];
146  var processedTokens = [];
147
148  for (var j = data.length - 1; j >= 0; j--) {
149    if (context.filterOut(j, bodies))
150      continue;
151
152    var where = data[j].where;
153    var token = tokens[where];
154    var clonedBody = cloneArray(token[2]);
155
156    bodies = bodies.concat(clonedBody);
157    bodiesAsList.push(clonedBody);
158    processedTokens.push(where);
159  }
160
161  optimizeProperties(bodies, true, false, outerContext);
162
163  var processedCount = processedTokens.length;
164  var propertyIdx = bodies.length - 1;
165  var tokenIdx = processedCount - 1;
166
167  while (tokenIdx >= 0) {
168     if ((tokenIdx === 0 || (bodies[propertyIdx] && bodiesAsList[tokenIdx].indexOf(bodies[propertyIdx]) > -1)) && propertyIdx > -1) {
169      propertyIdx--;
170      continue;
171    }
172
173    var newBody = bodies.splice(propertyIdx + 1);
174    context.callback(tokens[processedTokens[tokenIdx]], newBody, processedCount, tokenIdx);
175
176    tokenIdx--;
177  }
178}
179
180module.exports = reduceNonAdjacent;
181