// This file contains various helper methods.


// Sugar functions follow, some of which were ispired by
// [ http://www.crockford.com/javascript/inheritance.html ]
Function.prototype.method = function( name, lambda ){
  this.prototype[name] = lambda;
  return this;
};

// To be used as in ChildClass.inherits( ParentClass )
Function.method( 'inherits', function( parent ) {
  var d = {};
  var p = ( this.prototype = new parent() );

  this.method( '_super', function _super( name ){
    if( !( name in d ) ){
      d[name] = 0;
    }        
    var f, r, t = d[name];
    var v = parent.prototype;
    if( t ){
      while( t ){
        v = v.constructor.prototype;
        t -= 1;
      }
      f = v[name];
    } else {
      f = p[name];
      if( f == this[name] ){
        f = v[name];
      }
    }
    d[name] += 1;
    r = f.apply( this, Array.prototype.slice.apply( arguments, [1] ) );
    d[name] -= 1;
    return r;
  });
  return this;
});

Function.method( 'swiss', function( parent ){
  for( var i = 1; i < arguments.length; i++ ){
    var name = arguments[i];
    this.prototype[name] = parent.prototype[name];
  }
  return this;
});

Function.prototype.bind = function( object ){
  var method = this;
  var temp = function() {
    return method.apply( object, arguments );
   };
  return temp;
} 

// Camel case is useful for generating dynamic functions.
String.method( 'toCamelCase', function(){
  if( this.length < 1 ){
    return this;
  }
  var newString = '';
  var parts = this.split( /[^a-zA-Z0-9]/ );
  for( var i = 0; i < parts.length; i++ ){
    var part = parts[i];
    if( part.length > 0 ){
      newString += ( part[0].toUpperCase() + part.slice( 1 ) );
    }
  }
  return newString;
});

// This is just to test to make sure that my Psuedo-class structure is sound.
DummyPepperClass = function(){
  this.dummyAttr = null;
};
DummyPepperClass.method( 'getDummyAttr', function(){
  return this.dummyAttr;
});
DummyPepperClass.method( 'setDummyAttr', function( newValue ){
  this.dummyAttr = newValue;
  return this;
});

// Surprised that javascript doesn't have a function like .includes?()
Array.method( 'hasElement', function ( element ){
  for( var i = 0; i < this.length; i++ ){
    if( element == this[i] ){
      return true;
    }
  }
  return false;
});
Sassijs = function( template ){
  this.template = template;
};

Sassijs.method( 'getTree', function(){
  // Lazy-load.
  if( this.tree == null ){
    this.determineTree();
  }
  return this.tree;
});

Sassijs.method( 'getTemplate', function(){
  return this.template;
});

Sassijs.method( 'determineTree', function(){
  this.tree = new SassijsTree( this.getTemplate() );
});

Sassijs.method( 'getStyleElement', function(){
  return '<style type="text/css"><!--\n' + this.getTree().getRoot().getCss().join( '\n' ) + '\n--></style>';
});

Sassijs.method( 'writeToDocument', function(){
  var style = document.createElement( 'style' );
  var definition = this.getTree().getRoot().getCss().join( '\n' );
  style.setAttribute( "type", "text/css" );
  if( style.styleSheet ){
    // IE
    style.styleSheet.cssText = definition;
  } else {
    // Good browsers.
    var text = document.createTextNode( definition );
    style.appendChild( text );
  }
  document.getElementsByTagName('head')[0].appendChild( style );
});

Helper = function(){};

//    def self.handle_interpolation(str)
//      scan = StringScanner.new(str)
//      yield scan while scan.scan(/(.*?)(\\*)\#\{/)
//      scan.rest
//    end
Helper.method( 'handle_interpolation', function( string ){
  
});

// CAN'T FIND THIS METHOD BEING CALLED ANYWHERE
//    def self.balance(scanner, start, finish, count = 0)
//      str = ''
//      scanner = StringScanner.new(scanner) unless scanner.is_a? StringScanner
//      regexp = Regexp.new("(.*?)[\\#{start.chr}\\#{finish.chr}]", Regexp::MULTILINE)
//      while scanner.scan(regexp)
//        str << scanner.matched
//        count += 1 if scanner.matched[-1] == start
//        count -= 1 if scanner.matched[-1] == finish
//        return [str.strip, scanner.rest] if count == 0
//      end
//    end


// This is used to generate an error that reads something like "Inconsistent indentation: space
// used for indentation, but the rest of the document was indented using tab."
Helper.method( 'human_indentation', function( indentation, was ){
  was = ( was == true ) ? true : false;
  if( indentation.indexOf( '\t' ) == -1 ){
    noun = ' space';
  } else if( indentation.indexOf( ' ' ) == -1 ){
    noun = ' tab';
  } else {
    return "'" + indentation + ( was ? "' was" : "'" );
  }
  
  // Check and see how many spaces or tabs there were for verb agreement.
  singular = ( ( indentation.length == 1 ) ? true : false );
  if( was ){
    was = ( singular ? ' was' : ' were' );
  } else {
    was = '';
  }
  
  return ( indentation.length.toString() + noun + ( singular ? '' : 's' ) + was );
});

Sassijs.prototype.helper = new Helper();

// This class is inspired by the Ruby class of the same name.  Essentially, this
// is a string which keeps track of an index and only scans the remainder of the
// string from that index forward.  Think of a StringIO.
StringScanner = function( string ){
  this.original = string;
  this.index = 0;
};

StringScanner.method( 'advanceIndex', function( advance ){
  this.index = this.index + advance;
  return this;
});

StringScanner.method( 'getCurrent', function(){
  return this.original.slice( this.index );
});

StringScanner.method( 'scan', function( pattern ){
  var newIndex = this.getCurrent().search( pattern );
  if( newIndex >= 0 ){
    var result = this.getCurrent().match( pattern );
    this.advanceIndex( newIndex + result.toString().length );
    return result.toString();
  }
  return false;
});

StringScanner.method( 'scanIndex', function( pattern ){
  var newIndex = this.getCurrent().search( pattern );
  if( newIndex >= 0 ){
    var result = this.getCurrent().match( pattern );
    this.advanceIndex( newIndex + result.toString().length );
    return this.index;
  }
  return false;
});
SassijError = function( newComment, newLineNumber, supressRaise ){
  this.setComment( newComment == undefined ? "Unspecified Error" : newComment );
  this.setLineNumber( newLineNumber == undefined ? "None" : newLineNumber  );
  
  if( supressRaise != true ){
    throw( this.toString() );
  }
}

SassijError.method( 'getComment', function(){
  return this.comment;
});

SassijError.method( 'getLineNumber', function(){
  return this.lineNumber;
});

SassijError.method( 'setComment', function( newValue ){
  this.comment = newValue;
  return this;
});

SassijError.method( 'setLineNumber', function( newValue ){
  this.lineNumber = newValue;
  return this;
});

SassijError.method( 'shout', function(){
  alert( this.toString() );
});

SassijError.method( 'toString', function(){
  return ( this.getComment() + ": " + this.getLineNumber() );
});
SassijEnvironment = function(){
  this.parent = null;
  this.vars = null;
  this.mixins = null;
}
SassijsTreeNode = function( line, newOptions ){ 
  this.children = [];
  if( newOptions ){
    this.options = newOptions;
  } else {
    this.options = { style: 'none' };
  }
  this.style = this.options.style;
  this.line = line;
};

// It's an odd kind of tree, but we can only traverse out,
// and these poor children will never know their parents.
SassijsTreeNode.method( 'getChildren', function(){
  return this.children;
});

SassijsTreeNode.method( 'getLine', function(){
  return this.line;
});

SassijsTreeNode.method( 'setChildren', function( newValue ){
  this.children = newValue;
  return this;
});

SassijsTreeNode.method( 'appendChild', function( child ){
  if( error = this.isInvalidChild( child ) ) {
    throw( new SassijError( error, child.line ) );
  }
  this.children.push( child );
  return this;
});
  
SassijsTreeNode.method( 'getLastChild', function() {
  return this.getChildren()[ this.getChildren().length - 1 ];
});


SassijsTreeNode.method( 'getSpecies', function(){
  return this.species;
});

SassijsTreeNode.method( 'toS', function() {
  var result = "";
  for( child in this.getChildren() ){
    if( child.isType( AttrNode ) ){
      throw( new SassijError( 'Attributes aren\'t allowed at the root of a document.', child.line ) );
    } else {
      //    result << "#{child.to_s(1)}" + (@style == :compressed ? '' : "\n")
      // need to add a @style indicator at some point
      result += child.toS( 1 );
    }
  }
      // @style == :compressed ? result+"\n" : result[0...-1]
  return result;
});
  
  // This method should be overridden by subclasses to return an error message
  // if the given child node is invalid,
  // and false or nil otherwise.
SassijsTreeNode.method( 'isInvalidChild', function( child ){
  return false;
});

// This is a funny function, because it has to take the tree and flip
// it around into an array such that each leaf is a new array entry,
// with its lineage prepended.  The catch is that nodes don't know
// their parents, so this has to be done in one large recursive crawl.
SassijsTreeNode.method( 'getCss', function(){
  var css = '';
  var ruleChildren = [];
  var attributeChildren = [];
  for( var i = 0; i < this.getChildren().length; i++ ){
    switch( this.getChildren()[ i ].getSpecies() ){
      case 'rule':
        ruleChildren = ruleChildren.concat( this.getChildren()[ i ].getCss() );
        break;
      case 'attribute':
        attributeChildren.push( this.getChildren()[ i ].getCss() );
        break;
      default:
        break;
    }
  }
  if( attributeChildren.length > 0 ){
    ruleChildren.unshift( "{ " + attributeChildren.join( ' ' ) + " }" );
  }
  if( ruleChildren.length > 0 ){
    // Rules should always have ruleChildren, either as subchildren or as 
    // a collection of attributes.
    for( var i = 0; i < ruleChildren.length; i++ ){
      prefixRule = this.getLine().getSyntax();
      if( prefixRule.length > 0 ){
        ruleChildren[ i ] = prefixRule + " " + ruleChildren[ i ];
      }
    }
    return ruleChildren;
  } else {
    // This is returned by an attribute.
//    return this.getLine().getSyntax();
  }
});

SassijsTreeNodeAttribute = function( line ){
  this.children = [];
  this.species = 'attribute';
  this.line = line;
  // The regex that matches and extracts data from  attributes 
  // of the form <tt>:name attr</tt>.
  this.regex = /^:([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;
  // The regex that matches and extracts data from attributes 
  // of the form <tt>name: attr</tt>.
  this.regexAlternate = /^([^\s=:]+)(\s*=|:)(?:\s+|$)(.*)/;
};

SassijsTreeNodeAttribute.inherits( SassijsTreeNode );

SassijsTreeNodeAttribute.method( 'getParts', function(){
  if( this.parts != null ){ 
    return this.parts; 
  }
  this.parts = this.getLine().getSyntax().match( this.regex );
  // If we don't get a match, try the alternate syntax, which is much closer
  // to original CSS.  This is one of the only places where we may differ 
  // from the Ruby SASS implementation, because they don't appear to allow
  // mixing of the normal and alternate sytax.
  if( this.parts == null ){
    this.parts = this.getLine().getSyntax().match( this.regexAlternate );
  }
  return this.parts;
});

SassijsTreeNodeAttribute.method( 'getKey', function(){
  // Lazy load the key.
  if( this.key == null ){
    this.key = this.getParts()[1];
  }
  return this.key;
});

SassijsTreeNodeAttribute.method( 'getValue', function(){
  // The character that designates that an attribute should be assigned 
  // to a SassScript expression.
  var expressionChar = '!';

  // Lazy load the value.
  if( this.value != null ){
    return this.value;
  }
  if( this.getParts()[2] == expressionChar ){
// Expressions are for evaluation.
  } else {
    this.value = this.getParts()[3];
  }
  return this.value;
});

SassijsTreeNodeAttribute.method( 'getCss', function(){
  return this.getKey() + ": " + this.getValue() + ";";
});

SassijsTreeNodeComment = function( line ){
  this.children = [];
  this.species = 'comment';
  this.line = line;
};

SassijsTreeNodeComment.inherits( SassijsTreeNode );

SassijsTreeNodeComment.method( 'getCss', function(){
  var css = this.getLine().getSyntax() + " */";
  return css; 
});

SassijsTreeNodeDirective = function( line ){
  this.children = [];
  this.species = 'directive';
  this.line = line;
};

SassijsTreeNodeDirective.inherits( SassijsTreeNode );

SassijsTreeNodeDirective.method( 'getCss', function(){
    // directives of the form <tt>@import file</tt>.
  var directive = /^@([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;

  parts = this.getLine().getSyntax().match( directive );
  // If we don't get a match, we should raise an error?
  if( parts == null ){
    //raise error, I guess;
  }
  var css = '@' + parts[1] + ' url(' + parts[3] + '.css);';
  return css;
});

SassijsTreeNodeMixinDefinition = function( line ){
  this.species = 'mixinDefinition';
  this.line = line;
};

SassijsTreeNodeMixinDefinition.inherits( SassijsTreeNode );

// In this special case, we want the node to return its children
// rendered as CSS.  This node will be removed from the tree
// root in pre-processing.
SassijsTreeNodeMixinDefinition.method( 'getCss', function(){
  var css = '';
  var children = this.getChildren();
  for( i = 0; i < children.length; i++ ){
    css += children[ i ].getCss();
  }
  return css;
});

SassijsTreeNodeMixinInclude = function( ){
  this.species = 'mixinInclude';
};

SassijsTreeNodeMixinInclude.inherits( SassijsTreeNode );

SassijsTreeNodeRule = function( line ){
  this.children = [];
  this.species = 'rule';
  this.line = line;
};

SassijsTreeNodeRule.inherits( SassijsTreeNode );
/*

SassijsTreeNodeRule.method( 'getCss', function(){
  var css = '';
  var attributes = [];
  for( var i = 0; i < this.getChildren().length; i++ ){
    switch( this.getChildren()[ i ].getSpecies() ){
      case 'rule':
        css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
      case 'attribute':
        attributes.push( this.getChildren()[ i ].getCss() );
    }
  }
  if( attributes.length > 0 ){
    return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";
  } else {
    return css;
  }
});

SassijsTreeNodeRule.method( 'getCss', function(){
  var css = '';
  var attributes = [];
  for( var i = 0; i < this.getChildren().length; i++ ){
    switch( this.getChildren()[ i ].getSpecies() ){
      case 'rule':
        css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
      case 'attribute':
        attributes.push( this.getChildren()[ i ].getCss() );
    }
  }
  if( attributes.length > 0 ){
    return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";
  } else {
    return css;
  }
});

*/
SassijsTreeNodeVariable = function( line ){
  this.children = [];
  this.species = 'variable';
  this.line = line;
  // The regex that matches and extracts data from attributes
  // of the form <tt>!name = value</tt>.
  this.regex = /^!([^\s=:]+)\s*(=?)(?:\s+|$)(.*)/;
};

SassijsTreeNodeVariable.inherits( SassijsTreeNode );

SassijsTreeNodeVariable.method( 'getParts', function(){
  if( this.parts != null ){ 
    return this.parts; 
  }
  this.parts = this.getLine().getSyntax().match( this.regex );
  // If we don't get a match, try the alternate syntax, which is much closer
  // to original CSS.  This is one of the only places where we may differ 
  // from the Ruby SASS implementation, because they don't appear to allow
  // mixing of the normal and alternate sytax.
  if( this.parts == null ){
    this.parts = this.getLine().getSyntax().match( this.regexAlternate );
  }
  return this.parts;
});

SassijsTreeNodeVariable.method( 'getKey', function(){
  // Lazy load the key.
  if( this.key == null ){
    this.key = this.getParts()[1];
  }
  return this.key;
});

// Should this method discriminate between text and expressions, or
// just pass it to an expression and let that evaluate it as text?
SassijsTreeNodeVariable.method( 'getValue', function(){
  // The character that designates that an attribute should be assigned 
  // to a SassScript expression.
  var expressionChar = '!';

  // Lazy load the value.
  if( this.value != null ){
    return this.value;
  }
  if( this.getParts()[2] == expressionChar ){
// Expressions are for evaluation.
  } else {
    this.value = this.getParts()[3];
  }
  return this.value;
});

// BIG TODO: Figure out wtf to do with em and ens.
SassijsExpressionUnit = function( string ){
  this.unit = string;
  this.acceptableChars = [ '\\*', '\\/', '\\%', '\\+', '\\-', '\\(', '\\)', '\\=\\=', '\\!\\=', '\\>\\=', '\\<\\=', '\\>', '\\<' ];
  this.sizeSpecies = [ 'in', 'cm', 'pc', 'mm', 'pt', 'px' ];
  this.sizeConversion_table = [
    [ 1,                2.54,         6,            25.4,        72,          72         ], // in
    [ null,             1,            2.36220473,   10,          28.3464567,  28.3464567 ], // cm
    [ null,             null,         1,            4.23333333,  12,          12         ], // pc
    [ null,             null,         null,         1,           2.83464567,  2.83464567 ], // mm
    [ null,             null,         null,         null,        1,           1          ], // pt
    [ null,             null,         null,         null,        null,        1          ]  // px
  ];
  this.sizeConversionToPoints = { in: 72, cm: 28.3464567, pc: 12, mm: 2.83464567, px: 1, pt: 1 };
  this.termRegex = /([\d\.]+)([\w]*)/;
  this.termsAndOperatorsRegex = /[\d\.\w]+|\+|\*|\/|%|-|\(|\)|==|!=|>=|<=|>|</g;
};

SassijsExpressionUnit.method( 'getSpecies', function( sample ){
  // Break the sample into terms, and look at the first one to 
  // determine what unit type.  The default is text.
  terms = sample.match( /(\w|\d|\.)+/g );
  firstTerm = terms[0];
  sizeRegex = new RegExp( '(\d|\.)+' + this.sizeSpecies.join( '|' ) );
  if( sizeRegex.exec( firstTerm ) ){
    return 'size';
  }
});

// This function assumes that we have already identified the unit as a size.
SassijsExpressionUnit.method( 'getSizeInPoints', function( term ){
  var unitAndSpecies = term.match( this.termRegex );
  if( !unitAndSpecies ){
    return false;
  }
  var unit = unitAndSpecies[1];
  var species = unitAndSpecies[2];
  return( unit * this.sizeConversionToPoints[ species ] );
});


// You kids mess around too much.  Just convert everything into points.
// This function assumes that we have already identified the expression in size.
SassijsExpressionUnit.method( 'getNormalizedSize', function( sample ){
  // Break the sample into terms, and look at the first one to 
  // determine what unit type.  The default is text.
//  var termsAndOperatorsRegex = new RegExp( '(\\d|\\.|\\w)+|' + this.acceptableChars.join( '|' ) );
//var termsAndOperatorsRegex = /(\d|\.|\w)+|\*|\/|%|\+|-|\(|\)|==|!=|>=|<=|>|</g;
  var termsAndOperators = sample.match( this.termsAndOperatorsRegex );
  var normalized = [];
  for( var i = 0; i < termsAndOperators.length; i++ ){
    var unitInPoints = this.getSizeInPoints( termsAndOperators[ i ] );
    if( unitInPoints ){
      normalized.push( unitInPoints );
    } else {
      normalized.push( termsAndOperators[ i ] );
    }
  }
  return normalized.join( ' ' ); // Just for readability in development.
});


// This class evaluates variables and expressions, but not 
// mixins, which are appended to trees at an earlier stage
// in pre-processing.
SassijsExpression = function( string, variables ){
  this.string = string;
  // variables are the databank prepolated with SassijsVariables.
  this.variables = variables;
  this.parts = [];
  this.identifiedParts = [];
  this.acceptableChars = [ '*', '/', '%', '+', '-', '(', ')', '==', '!=', '>=', '<=', '>', '<' ];
  this.operators = [ '*', '/', '%', '+', '-' ];
  this.first_operators = [ '*', '/', '%' ];
  this.second_operators = [ '+', '-' ];
};

SassijsExpression.method( 'getExpression', function(){
  return this.string.slice( 1, ( this.string.length ) );
});

SassijsExpression.method( 'getValue', function(){
  var sassijsExpressionUnit = new SassijsExpressionUnit();
  var species = sassijsExpressionUnit.getSpecies( this.getExpression() );
  switch( species ){
    case 'size':
      var expressionString = sassijsExpressionUnit.getNormalizedSize( this.getExpression() );
      var points = eval( expressionString );
      return points + 'pt';
    default:
      return this.getExpression();
  }
  
});



// This class wraps Sassijs and simply loads a file into the template variable.  We
// assume the presence of a loading DOM element 'document'.
SassijsFile = function( url, async ){
  this.loaded = false;
  this.template = null;
  this.fetch( url, async );
}

SassijsFile.inherits( Sassijs );

SassijsFile.method( 'isLoaded', function(){
  return this.loaded;
});

SassijsFile.method( 'fetch', function( url, async ){
  // Asynchronous transfer is the default.
  if( async != false ){
    async = true;
  }
  if( window.XMLHttpRequest ){
    req = new XMLHttpRequest();
  } else if( window.ActiveXObject ){
    req = new ActiveXObject("Microsoft.XMLHTTP");
  }
  // This is an asynch call that won't freeze up the browser.
  if( ( async ) && ( req != undefined ) ){
    var that = this;
    req.onreadystatechange = function(){ 
      if( req.readyState == 4 ) { // only if req is "loaded"
        if( ( req.status == 200 ) || ( req.status == 0 ) ) { // only if "OK" (0 is status for locally served files)
          this.loaded = true;
          this.template = req.responseText;
        } else {
          this.loaded = false;
        }
      }
    };
    req.open( "GET", url );
    req.send( "" );
  // This is a synchronous call that holds up the browser, which is 
  // necessary for qUnit to work properly, for example.
  } else {
    var that = this;
    req.open( "GET", url, false );
    req.send( "" );
    if( req.readyState == 4 ) { // only if req is "loaded"
      if( ( req.status == 200 ) || ( req.status == 0 ) ) { // only if "OK" (0 is status for locally served files)
        this.loaded = true;
        this.template = req.responseText;
      } else {
        this.loaded = false;
      }
    }
  }
});

// This class populates an object that establishes the properties
// of one syntax element, and hints at some of its context in the tree.
SassijsLine = function( string, tab, lineNumber ){
  this.lineNumber = lineNumber;
  this.syntax = '';
  this.tabCount = 0;
  // A reference to our context.
//  this.sassijs = sassijs;
  
  // Count the tabs.
  findTab = new RegExp( "^" + tab );
  var potentialSyntax = string;
  while( result = findTab.exec( potentialSyntax ) ){
    potentialSyntax = RegExp.rightContext;
    this.tabCount++;
  }

  // Strip extra whitespace from the rule.
  this.syntax = string.replace( /[\s|\t]+/g, ' ' ).replace( /^\s+|\s+$/g, '' );
}

SassijsLine.method( 'getSyntax', function(){
  return this.syntax;
});

//SassijsLine.method( 'getSassijs', function(){
//  return this.sassijs;
//});

SassijsLine.method( 'getTabCount', function(){
  return this.tabCount;
});

SassijsLine.method( 'getLineNumber', function(){
  return this.lineNumber;
});

// This method implements the Factory Pattern that determines
// what kind of Node we are dealing with.
SassijsLine.method( 'determineNode', function(){
    // The character that begins a CSS attribute.
  var attributeChar        = ':';     
    // The character that designates that an node SassijsExpression.
  var variableChar           = '!';
    // The character that designates the beginning of a comment, either Sass or CSS.
  var commentChar          = '/';
    // The character that follows the general COMMENT_CHAR and designates a Sass comment,
    // which is not output as a CSS comment.
  var sassCommentChar     = '/';
    // The character that follows the general COMMENT_CHAR and designates a CSS comment,
    // which is embedded in the CSS document.
  var cssCommentChar      = '*';
    // The character used to denote a compiler directive.
  var directiveChar        = '@';
    // Designates a non-parsed rule.
  var escapeChar           = '\\';
    // Designates block as mixin definition rather than CSS rules to output
  var mixinDefinitionChar = '=';
    // Includes named mixin declared using MIXIN_DEFINITION_CHAR
  var mixinIncludeChar    = '+';
    // The regex that matches attributes of the form <tt>name: attr</tt>.
  var attributeAlternateMatcher = /^[^\s:]+\s*[=:](\s|$)/;
  
  switch( this.getSyntax()[0] ){
    case attributeChar:
      // CSS3 pseudo-elements begin with '::'
      if( this.getSyntax()[1] != ':' ){
        return new SassijsTreeNodeAttribute( this );
      } else {
        return new SassijsTreeNodeRule( this );
      }
//    case expressionChar:
//      return new SassijsScript( this );
    case variableChar:
      return new SassijsTreeNodeVariable( this );
    case commentChar:
      // Two kinds of comments: SASS comments and CSS comments.
      if( this.getSyntax()[ 1 ] == sassCommentChar ){
        return null;
      } else if( this.getSyntax()[ 1 ] == cssCommentChar ){
        return new SassijsTreeNodeComment( this );
      } else {
        return new SassijsTreeNodeRule( this );
      }
    case directiveChar:
      return new SassijsTreeNodeDirective( this );
    case escapeChar:
//???       return new SassijsTreeNodeEscape();
//      when ESCAPE_CHAR
//        Tree::RuleNode.new(line.text[1..-1], @options)
    case mixinDefinitionChar:
      return new SassijsTreeNodeMixinDefinition( this );
    case mixinIncludeChar:
//????
//        if line.text[1].nil?
//          Tree::RuleNode.new(line.text, @options)
//        else
//          parse_mixin_include(line, root)
//        end
      return new SassijsTreeNodeMixinInclude( this );
    default:
      if( this.getSyntax().match( attributeAlternateMatcher ) ){
        return new SassijsTreeNodeAttribute( this );
//???          parse_attribute(line, ATTRIBUTE_ALTERNATE)
      } else {
        return new SassijsTreeNodeRule( this );
      }
   }
});


// This class takes a sass template and breaks it down into children nodes.
SassijsTree = function( template ){
  this.template = template.replace( /\r|\n|\r\n/g, "\n");
  this.determineTab();
  var rootLine = new SassijsLine( '', this.getTab(), 0 );
  this.root = rootLine.determineNode();
  this.determineLines();
  this.determineNodes();
}

SassijsTree.method( 'getTab', function(){
  return this.tab;
});

SassijsTree.method( 'setTab', function( newValue ){
  this.tab = newValue;
  return this;
});

SassijsTree.method( 'getRoot', function(){
  return this.root;
});

SassijsTree.method( 'getLines', function(){
  return this.lines;
});

SassijsTree.method( 'getTemplate', function(){
  return this.template;
});

SassijsTree.method( 'getTemplateLines', function(){
  return this.getTemplate().split( '\n' );
});

// The tab is the form of indentation, which can be tabs or spaces,
// but not both.
SassijsTree.method( 'determineTab', function(){
  var lines = this.getTemplateLines();
  for( var i = 0; i < lines.length; i++ ){
    var firstTab = lines[ i ].match( /^(\s|\t)+/ );
    if( firstTab ){
      this.setTab( firstTab[0] );
      break;
    }
  }
});

// These are just the individual lines of the syntax template.  The
// lines themselves determine their own Node Species before being 
// converted to Nodes.
SassijsTree.method( 'determineLines', function(){
  this.lines = new Array();
  var lines = this.getTemplateLines();
  for( var i = 0; i < lines.length; i++ ){
    this.lines.push( new SassijsLine( lines[ i ], this.getTab(), ( i + 1 ) ) );
  }
});

// Here we take the previously determined lines and parse them into a 
// tree of nodes based on the tab length and context relative to previous
// nodes.  This function is currently not optimized.  We would have to 
// take a few cases and compare parsing speed in a for-loop, string-
// scanner style parsing, and while-loop, etc.
SassijsTree.method( 'determineNodes', function(){
  var previousTabCount = 0;
  var previousNode = this.getRoot();
  for( var i = 0; i < this.getLines().length; i++ ){
    var line = this.getLines()[ i ];
    // Dump empty rules.
    if( line.getSyntax().length > 0 ){
      var newNode = line.determineNode();
      // Comments don't return anything that we want in our tree.
      if( newNode != null ){
        // This node is a sibling to the previous node.
        if( line.getTabCount() == previousTabCount ){
          // Get the Parent by procedural induction.  The last node at the
          // previous depth is the parent.
          parent = this.getRoot();
          for( var j = 0; j < previousTabCount; j++ ){
            var parent = parent.getLastChild();
          }
          parent.appendChild( newNode );
        }
        // This node is a child to the previous node.
        if( line.getTabCount() > previousTabCount ){
          // Insert error check here that it can't be more than 1 tab count greater.
          previousNode.appendChild( newNode );
        }
        // This node is a child to something above the previous node.
        if( line.getTabCount() < previousTabCount ){
          // Get the Parent by procedural induction.
          parent = this.getRoot();
          for( var j = 0; j < line.getTabCount(); j++ ){
            var parent = parent.getLastChild();
          }
          parent.appendChild( newNode );
        }
        previousTabCount = newNode.getLine().getTabCount();
        previousNode = newNode;
      }
    }
  }
});
/*

SassijsTreeNodeRule.method( 'getCss', function(){
  var cssLines = [];
  for( var i = 0; i < this.root.getChildren().length; i++ ){
    switch( this.getChildren()[ i ].getSpecies() ){
      case 'rule':
        css += this.getLine().getSyntax() + ' ' + this.getChildren()[ i ].getCss();
      case 'attribute':
        attributes.push( this.getChildren()[ i ].getCss() );
    }
  }
  if( attributes.length > 0 ){
    return this.getLine().getSyntax() + " { " + attributes.join( '; ' ) + " }";
  } else {
    return css;
  }
});

*/
SassijsAutoloader = {

  getTemplates: function(){
    var sassLinks = [];
    var potentialSass = document.getElementsByTagName( 'link' );
    for( var i = 0; i < potentialSass.length; i++ ){
      if( potentialSass[ i ].getAttribute( 'type' ) == 'text/sass' ){
        sassLinks.push( potentialSass[ i ].getAttribute( 'href' ) );
      }
    }
    return sassLinks;
  },
  
  run: function(){
    var sassLinks = SassijsAutoloader.getTemplates();
    for( var i = 0; i < sassLinks.length; i++ ){
      sassijs = new SassijsFile( sassLinks[ i ], false );
      sassijs.writeToDocument();
    }
  }

}

window.onload = function(){ SassijsAutoloader.run(); };
