
// Helper for tokenizing the contents of a CSS selector block

module.exports = (function() {
  var createTokenPrototype = function (processable) {
    var important = '!important';

    // Constructor for tokens
    function Token (prop, p2, p3) {
      this.prop = prop;
      if (typeof(p2) === 'string') {
        this.value = p2;
        this.isImportant = p3;
      }
      else {
        this.value = processable[prop].defaultValue;
        this.isImportant = p2;
      }
    }

    Token.prototype.prop = null;
    Token.prototype.value = null;
    Token.prototype.granularValues = null;
    Token.prototype.components = null;
    Token.prototype.position = null;
    Token.prototype.isImportant = false;
    Token.prototype.isDirty = false;
    Token.prototype.isShorthand = false;
    Token.prototype.isIrrelevant = false;
    Token.prototype.isReal = true;
    Token.prototype.isMarkedForDeletion = false;

    // Tells if this token is a component of the other one
    Token.prototype.isComponentOf = function (other) {
      if (!processable[this.prop] || !processable[other.prop])
        return false;
      if (!(processable[other.prop].components instanceof Array) || !processable[other.prop].components.length)
        return false;

      return processable[other.prop].components.indexOf(this.prop) >= 0;
    };

    // Clones a token
    Token.prototype.clone = function (isImportant) {
      var token = new Token(this.prop, this.value, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
      return token;
    };

    // Creates an irrelevant token with the same prop
    Token.prototype.cloneIrrelevant = function (isImportant) {
      var token = Token.makeDefault(this.prop, (typeof(isImportant) !== 'undefined' ? isImportant : this.isImportant));
      token.isIrrelevant = true;
      return token;
    };

    // Creates an array of property tokens with their default values
    Token.makeDefaults = function (props, important) {
      return props.map(function(prop) {
        return new Token(prop, important);
      });
    };

    // Parses one CSS property declaration into a token
    Token.tokenizeOne = function (fullProp) {
      // Find first colon
      var colonPos = fullProp.indexOf(':');

      if (colonPos < 0) {
        // This property doesn't have a colon, it's invalid. Let's keep it intact anyway.
        return new Token('', fullProp);
      }

      // Parse parts of the property
      var prop = fullProp.substr(0, colonPos).trim();
      var value = fullProp.substr(colonPos + 1).trim();
      var isImportant = false;
      var importantPos = value.indexOf(important);

      // Check if the property is important
      if (importantPos >= 1 && importantPos === value.length - important.length) {
        value = value.substr(0, importantPos).trim();
        isImportant = true;
      }

      // Return result
      var result = new Token(prop, value, isImportant);

      // If this is a shorthand, break up its values
      // NOTE: we need to do this for all shorthands because otherwise we couldn't remove default values from them
      if (processable[prop] && processable[prop].isShorthand) {
        result.isShorthand = true;
        result.components = processable[prop].breakUp(result);
        result.isDirty = true;
      }

      return result;
    };

    // Breaks up a string of CSS property declarations into tokens so that they can be handled more easily
    Token.tokenize = function (input) {
      // Split the input by semicolons and parse the parts
      var tokens = input.split(';').map(Token.tokenizeOne);
      return tokens;
    };

    // Transforms tokens back into CSS properties
    Token.detokenize = function (tokens) {
      // If by mistake the input is not an array, make it an array
      if (!(tokens instanceof Array)) {
        tokens = [tokens];
      }

      var result = '';

      // This step takes care of putting together the components of shorthands
      // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
      for (var i = 0; i < tokens.length; i++) {
        var t = tokens[i];
        if (t.isShorthand && t.isDirty) {
          var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
          Array.prototype.splice.apply(tokens, [i, 1].concat(news));
          t.isDirty = false;
          i--;
          continue;
        }

        if (t.prop)
          result += t.prop + ':';

        if (t.value)
          result += t.value;

        if (t.isImportant)
          result += important;

        result += ';';
      }

      return result.substr(0, result.length - 1);
    };

    // Gets the final (detokenized) length of the given tokens
    Token.getDetokenizedLength = function (tokens) {
      // If by mistake the input is not an array, make it an array
      if (!(tokens instanceof Array)) {
        tokens = [tokens];
      }

      var result = 0;

      // This step takes care of putting together the components of shorthands
      // NOTE: this is necessary to do for every shorthand, otherwise we couldn't remove their default values
      for (var i = 0; i < tokens.length; i++) {
        var t = tokens[i];
        if (t.isShorthand && t.isDirty) {
          var news = processable[t.prop].putTogether(t.prop, t.components, t.isImportant);
          Array.prototype.splice.apply(tokens, [i, 1].concat(news));
          t.isDirty = false;
          i--;
          continue;
        }

        if (t.prop) {
          result += t.prop.length + 1;
        }
        if (t.value) {
          result += t.value.length;
        }
        if (t.isImportant) {
          result += important.length;
        }
      }

      return result;
    };

    return Token;
  };

  return {
    createTokenPrototype: createTokenPrototype
  };

})();
