
/*!
 * Stylus - Normalizer
 * Copyright(c) 2010 LearnBoost <dev@learnboost.com>
 * MIT Licensed
 */

/**
 * Module dependencies.
 */

var Visitor = require('./')
  , nodes = require('../nodes')
  , utils = require('../utils')
  , fs = require('fs');

/**
 * Initialize a new `Normalizer` with the given `root` Node.
 *
 * This visitor implements the first stage of the duel-stage
 * compiler, tasked with stripping the "garbage" from
 * the evaluated nodes, ditching null rules, resolving
 * ruleset selectors etc. This step performs the logic
 * necessary to facilitate the "@extend" functionality,
 * as these must be resolved _before_ buffering output.
 *
 * @param {Node} root
 * @api public
 */

var Normalizer = module.exports = function Normalizer(root, options) {
  options = options || {};
  Visitor.call(this, root);
  this.stack = [];
  this.extends = {};
  this.map = {};
};

/**
 * Inherit from `Visitor.prototype`.
 */

Normalizer.prototype.__proto__ = Visitor.prototype;

/**
 * Normalize the node tree.
 *
 * @return {Node}
 * @api private
 */

Normalizer.prototype.normalize = function(){
  // normalize cached imports
  if ('block' == this.root.nodeName) this.root.constructor = nodes.Root;
  return this.visit(this.root);
};

/**
 * Visit Root.
 */

Normalizer.prototype.visitRoot = function(block){
  var ret = new nodes.Root
    , node;

  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    node = block.nodes[i];
    switch (node.nodeName) {
      case 'null':
      case 'expression':
      case 'function':
      case 'unit':
      case 'atblock':
        continue;
      default:
        ret.push(this.visit(node));
    }
  }

  return ret;
};

/**
 * Visit Block.
 */

Normalizer.prototype.visitBlock = function(block){
  var ret = new nodes.Block
    , node;

  if (block.hasProperties) {
    for (var i = 0, len = block.nodes.length; i < len; ++i) {
      this.last = len - 1 == i;
      node = block.nodes[i];
      switch (node.nodeName) {
        case 'null':
        case 'expression':
        case 'function':
        case 'group':
        case 'unit':
        case 'atblock':
          continue;
        default:
          ret.push(this.visit(node));
      }
    }
  }

  // nesting
  for (var i = 0, len = block.nodes.length; i < len; ++i) {
    node = block.nodes[i];
    // normalize cached imports
    if ('root' == node.nodeName) node.constructor = nodes.Block;
    ret.push(this.visit(node));
  }

  return block;
};

/**
 * Visit Group.
 */

Normalizer.prototype.visitGroup = function(group){
  var stack = this.stack
    , map = this.map
    , parts;

  // normalize interpolated selectors with comma
  group.nodes.forEach(function(selector, i){
    if (!~selector.val.indexOf(',')) return;
    if (~selector.val.indexOf('\\,')) {
      selector.val = selector.val.replace(/\\,/g, ',');
      return;
    }
    parts = selector.val.split(',');
    for (var k = 0, len = parts.length; k < len; ++k){
      var part = parts[k].trim();
      // Treat parts without `&` as interpolated from the root
      if (!~part.indexOf('&') && part.charAt(0) != '/') {
        part = '/' + part;
      }
      var s = new nodes.Selector([part]);
      s.val = part;
      s.block = group.block;
      group.nodes[i++] = s;
    }
  });
  stack.push(group.nodes);

  var selectors = utils.compileSelectors(stack, true);

  // map for extension lookup
  selectors.forEach(function(selector){
    map[selector] = map[selector] || [];
    map[selector].push(group);
  });

  // extensions
  this.extend(group, selectors);

  group.block = this.visit(group.block);
  stack.pop();
  return group;
};

/**
 * Visit Media.
 */

Normalizer.prototype.visitMedia = function(media){
  var props = []
    , other = []
    , self = this;

  function filterProps(block) {
    block.nodes.forEach(function(node) {
      node = self.visit(node);

      switch (node.nodeName) {
        case 'property':
          props.push(node);
          break;
        case 'block':
          filterProps(node);
          break;
        default:
          other.push(node);
      }
    });
  }

  filterProps(media.block);

  // Fake self-referencing group to contain
  // any props that are floating
  // directly on the @media declaration
  if (props.length) {
    var selfLiteral = new nodes.Literal('&');
    selfLiteral.lineno = media.lineno;
    selfLiteral.filename = media.filename;

    var selfSelector = new nodes.Selector([selfLiteral]);
    selfSelector.lineno = media.lineno;
    selfSelector.filename = media.filename;
    selfSelector.val = selfLiteral.val;

    var propertyGroup = new nodes.Group;
    propertyGroup.lineno = media.lineno;
    propertyGroup.filename = media.filename;

    var propertyBlock = new nodes.Block(media.block, propertyGroup);
    propertyBlock.lineno = media.lineno;
    propertyBlock.filename = media.filename;

    props.forEach(function(prop){
      propertyBlock.push(prop);
    });

    propertyGroup.push(selfSelector);
    propertyGroup.block = propertyBlock;

    media.block.nodes = [];
    media.block.push(propertyGroup);
    other.forEach(function(node){
      media.block.push(node);
    });
  }

  return media;
}

/**
 * Visit Keyframes.
 */

Normalizer.prototype.visitKeyframes = function(node){
  var frames = node.block.nodes.filter(function(frame){
    return frame.block && frame.block.hasProperties;
  });
  node.frames = frames.length;
  return node;
};

/**
 * Apply `group` extensions.
 *
 * @param {Group} group
 * @param {Array} selectors
 * @api private
 */

Normalizer.prototype.extend = function(group, selectors){
  var map = this.map
    , self = this;

  group.block.node.extends.forEach(function(extend){
    var groups = map[extend];
    if (!groups) throw new Error('Failed to @extend "' + extend + '"');
    selectors.forEach(function(selector){
      var node = new nodes.Selector;
      node.val = selector;
      node.inherits = false;
      groups.forEach(function(group){
        if (!group.nodes.some(function(n){ return n.val == selector })) {
          self.extend(group, selectors);
          group.push(node);
        }
      });
    });
  });
};
