1var emptyCharacter = '';
2
3var Breaks = require('../options/format').Breaks;
4var Spaces = require('../options/format').Spaces;
5
6var Marker = require('../tokenizer/marker');
7var Token = require('../tokenizer/token');
8
9function supportsAfterClosingBrace(token) {
10  return token[1][1] == 'background' || token[1][1] == 'transform' || token[1][1] == 'src';
11}
12
13function afterClosingBrace(token, valueIndex) {
14  return token[valueIndex][1][token[valueIndex][1].length - 1] == Marker.CLOSE_ROUND_BRACKET;
15}
16
17function afterComma(token, valueIndex) {
18  return token[valueIndex][1] == Marker.COMMA;
19}
20
21function afterSlash(token, valueIndex) {
22  return token[valueIndex][1] == Marker.FORWARD_SLASH;
23}
24
25function beforeComma(token, valueIndex) {
26  return token[valueIndex + 1] && token[valueIndex + 1][1] == Marker.COMMA;
27}
28
29function beforeSlash(token, valueIndex) {
30  return token[valueIndex + 1] && token[valueIndex + 1][1] == Marker.FORWARD_SLASH;
31}
32
33function inFilter(token) {
34  return token[1][1] == 'filter' || token[1][1] == '-ms-filter';
35}
36
37function disallowsSpace(context, token, valueIndex) {
38  return !context.spaceAfterClosingBrace && supportsAfterClosingBrace(token) && afterClosingBrace(token, valueIndex) ||
39    beforeSlash(token, valueIndex) ||
40    afterSlash(token, valueIndex) ||
41    beforeComma(token, valueIndex) ||
42    afterComma(token, valueIndex);
43}
44
45function rules(context, tokens) {
46  var store = context.store;
47
48  for (var i = 0, l = tokens.length; i < l; i++) {
49    store(context, tokens[i]);
50
51    if (i < l - 1) {
52      store(context, comma(context));
53    }
54  }
55}
56
57function body(context, tokens) {
58  var lastPropertyAt = lastPropertyIndex(tokens);
59
60  for (var i = 0, l = tokens.length; i < l; i++) {
61    property(context, tokens, i, lastPropertyAt);
62  }
63}
64
65function lastPropertyIndex(tokens) {
66  var index = tokens.length - 1;
67
68  for (; index >= 0; index--) {
69    if (tokens[index][0] != Token.COMMENT) {
70      break;
71    }
72  }
73
74  return index;
75}
76
77function property(context, tokens, position, lastPropertyAt) {
78  var store = context.store;
79  var token = tokens[position];
80
81  var propertyValue = token[2];
82  var isPropertyBlock = propertyValue && propertyValue[0] === Token.PROPERTY_BLOCK;
83
84  var needsSemicolon;
85  if ( context.format ) {
86    if ( context.format.semicolonAfterLastProperty || isPropertyBlock ) {
87      needsSemicolon = true;
88    } else if ( position < lastPropertyAt ) {
89      needsSemicolon = true;
90    } else {
91      needsSemicolon = false;
92    }
93  } else {
94    needsSemicolon = position < lastPropertyAt || isPropertyBlock;
95  }
96
97  var isLast = position === lastPropertyAt;
98
99  switch (token[0]) {
100    case Token.AT_RULE:
101      store(context, token);
102      store(context, semicolon(context, Breaks.AfterProperty, false));
103      break;
104    case Token.AT_RULE_BLOCK:
105      rules(context, token[1]);
106      store(context, openBrace(context, Breaks.AfterRuleBegins, true));
107      body(context, token[2]);
108      store(context, closeBrace(context, Breaks.AfterRuleEnds, false, isLast));
109      break;
110    case Token.COMMENT:
111      store(context, token);
112      break;
113    case Token.PROPERTY:
114      store(context, token[1]);
115      store(context, colon(context));
116      if (propertyValue) {
117        value(context, token);
118      }
119      store(context, needsSemicolon ? semicolon(context, Breaks.AfterProperty, isLast) : emptyCharacter);
120      break;
121    case Token.RAW:
122      store(context, token);
123  }
124}
125
126function value(context, token) {
127  var store = context.store;
128  var j, m;
129
130  if (token[2][0] == Token.PROPERTY_BLOCK) {
131    store(context, openBrace(context, Breaks.AfterBlockBegins, false));
132    body(context, token[2][1]);
133    store(context, closeBrace(context, Breaks.AfterBlockEnds, false, true));
134  } else {
135    for (j = 2, m = token.length; j < m; j++) {
136      store(context, token[j]);
137
138      if (j < m - 1 && (inFilter(token) || !disallowsSpace(context, token, j))) {
139        store(context, Marker.SPACE);
140      }
141    }
142  }
143}
144
145function allowsBreak(context, where) {
146  return context.format && context.format.breaks[where];
147}
148
149function allowsSpace(context, where) {
150  return context.format && context.format.spaces[where];
151}
152
153function openBrace(context, where, needsPrefixSpace) {
154  if (context.format) {
155    context.indentBy += context.format.indentBy;
156    context.indentWith = context.format.indentWith.repeat(context.indentBy);
157    return (needsPrefixSpace && allowsSpace(context, Spaces.BeforeBlockBegins) ? Marker.SPACE : emptyCharacter) +
158      Marker.OPEN_CURLY_BRACKET +
159      (allowsBreak(context, where) ? context.format.breakWith : emptyCharacter) +
160      context.indentWith;
161  } else {
162    return Marker.OPEN_CURLY_BRACKET;
163  }
164}
165
166function closeBrace(context, where, beforeBlockEnd, isLast) {
167  if (context.format) {
168    context.indentBy -= context.format.indentBy;
169    context.indentWith = context.format.indentWith.repeat(context.indentBy);
170    return (allowsBreak(context, Breaks.AfterProperty) || beforeBlockEnd && allowsBreak(context, Breaks.BeforeBlockEnds) ? context.format.breakWith : emptyCharacter) +
171      context.indentWith +
172      Marker.CLOSE_CURLY_BRACKET +
173      (isLast ? emptyCharacter : (allowsBreak(context, where) ? context.format.breakWith : emptyCharacter) + context.indentWith);
174  } else {
175    return Marker.CLOSE_CURLY_BRACKET;
176  }
177}
178
179function colon(context) {
180  return context.format ?
181    Marker.COLON + (allowsSpace(context, Spaces.BeforeValue) ? Marker.SPACE : emptyCharacter) :
182    Marker.COLON;
183}
184
185function semicolon(context, where, isLast) {
186  return context.format ?
187    Marker.SEMICOLON + (isLast || !allowsBreak(context, where) ? emptyCharacter : context.format.breakWith + context.indentWith) :
188    Marker.SEMICOLON;
189}
190
191function comma(context) {
192  return context.format ?
193    Marker.COMMA + (allowsBreak(context, Breaks.BetweenSelectors) ? context.format.breakWith : emptyCharacter) + context.indentWith :
194    Marker.COMMA;
195}
196
197function all(context, tokens) {
198  var store = context.store;
199  var token;
200  var isLast;
201  var i, l;
202
203  for (i = 0, l = tokens.length; i < l; i++) {
204    token = tokens[i];
205    isLast = i == l - 1;
206
207    switch (token[0]) {
208      case Token.AT_RULE:
209        store(context, token);
210        store(context, semicolon(context, Breaks.AfterAtRule, isLast));
211        break;
212      case Token.AT_RULE_BLOCK:
213        rules(context, token[1]);
214        store(context, openBrace(context, Breaks.AfterRuleBegins, true));
215        body(context, token[2]);
216        store(context, closeBrace(context, Breaks.AfterRuleEnds, false, isLast));
217        break;
218      case Token.NESTED_BLOCK:
219        rules(context, token[1]);
220        store(context, openBrace(context, Breaks.AfterBlockBegins, true));
221        all(context, token[2]);
222        store(context, closeBrace(context, Breaks.AfterBlockEnds, true, isLast));
223        break;
224      case Token.COMMENT:
225        store(context, token);
226        store(context, allowsBreak(context, Breaks.AfterComment) ? context.format.breakWith : emptyCharacter);
227        break;
228      case Token.RAW:
229        store(context, token);
230        break;
231      case Token.RULE:
232        rules(context, token[1]);
233        store(context, openBrace(context, Breaks.AfterRuleBegins, true));
234        body(context, token[2]);
235        store(context, closeBrace(context, Breaks.AfterRuleEnds, false, isLast));
236        break;
237    }
238  }
239}
240
241module.exports = {
242  all: all,
243  body: body,
244  property: property,
245  rules: rules,
246  value: value
247};
248