define(
  ["../exception","exports"],
  function(__dependency1__, __exports__) {
    "use strict";
    var Exception = __dependency1__["default"];

    function LocationInfo(locInfo){
      locInfo = locInfo || {};
      this.firstLine   = locInfo.first_line;
      this.firstColumn = locInfo.first_column;
      this.lastColumn  = locInfo.last_column;
      this.lastLine    = locInfo.last_line;
    }

    var AST = {
      ProgramNode: function(statements, inverseStrip, inverse, locInfo) {
        var inverseLocationInfo, firstInverseNode;
        if (arguments.length === 3) {
          locInfo = inverse;
          inverse = null;
        } else if (arguments.length === 2) {
          locInfo = inverseStrip;
          inverseStrip = null;
        }

        LocationInfo.call(this, locInfo);
        this.type = "program";
        this.statements = statements;
        this.strip = {};

        if(inverse) {
          firstInverseNode = inverse[0];
          if (firstInverseNode) {
            inverseLocationInfo = {
              first_line: firstInverseNode.firstLine,
              last_line: firstInverseNode.lastLine,
              last_column: firstInverseNode.lastColumn,
              first_column: firstInverseNode.firstColumn
            };
            this.inverse = new AST.ProgramNode(inverse, inverseStrip, inverseLocationInfo);
          } else {
            this.inverse = new AST.ProgramNode(inverse, inverseStrip);
          }
          this.strip.right = inverseStrip.left;
        } else if (inverseStrip) {
          this.strip.left = inverseStrip.right;
        }
      },

      MustacheNode: function(rawParams, hash, open, strip, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "mustache";
        this.strip = strip;

        // Open may be a string parsed from the parser or a passed boolean flag
        if (open != null && open.charAt) {
          // Must use charAt to support IE pre-10
          var escapeFlag = open.charAt(3) || open.charAt(2);
          this.escaped = escapeFlag !== '{' && escapeFlag !== '&';
        } else {
          this.escaped = !!open;
        }

        if (rawParams instanceof AST.SexprNode) {
          this.sexpr = rawParams;
        } else {
          // Support old AST API
          this.sexpr = new AST.SexprNode(rawParams, hash);
        }

        this.sexpr.isRoot = true;

        // Support old AST API that stored this info in MustacheNode
        this.id = this.sexpr.id;
        this.params = this.sexpr.params;
        this.hash = this.sexpr.hash;
        this.eligibleHelper = this.sexpr.eligibleHelper;
        this.isHelper = this.sexpr.isHelper;
      },

      SexprNode: function(rawParams, hash, locInfo) {
        LocationInfo.call(this, locInfo);

        this.type = "sexpr";
        this.hash = hash;

        var id = this.id = rawParams[0];
        var params = this.params = rawParams.slice(1);

        // a mustache is definitely a helper if:
        // * it is an eligible helper, and
        // * it has at least one parameter or hash segment
        this.isHelper = !!(params.length || hash);

        // a mustache is an eligible helper if:
        // * its id is simple (a single part, not `this` or `..`)
        this.eligibleHelper = this.isHelper || id.isSimple;

        // if a mustache is an eligible helper but not a definite
        // helper, it is ambiguous, and will be resolved in a later
        // pass or at runtime.
      },

      PartialNode: function(partialName, context, hash, strip, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type         = "partial";
        this.partialName  = partialName;
        this.context      = context;
        this.hash = hash;
        this.strip = strip;
      },

      BlockNode: function(mustache, program, inverse, close, locInfo) {
        LocationInfo.call(this, locInfo);

        if(mustache.sexpr.id.original !== close.path.original) {
          throw new Exception(mustache.sexpr.id.original + " doesn't match " + close.path.original, this);
        }

        this.type = 'block';
        this.mustache = mustache;
        this.program  = program;
        this.inverse  = inverse;

        this.strip = {
          left: mustache.strip.left,
          right: close.strip.right
        };

        (program || inverse).strip.left = mustache.strip.right;
        (inverse || program).strip.right = close.strip.left;

        if (inverse && !program) {
          this.isInverse = true;
        }
      },

      RawBlockNode: function(mustache, content, close, locInfo) {
        LocationInfo.call(this, locInfo);

        if (mustache.sexpr.id.original !== close) {
          throw new Exception(mustache.sexpr.id.original + " doesn't match " + close, this);
        }

        content = new AST.ContentNode(content, locInfo);

        this.type = 'block';
        this.mustache = mustache;
        this.program = new AST.ProgramNode([content], locInfo);
      },

      ContentNode: function(string, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "content";
        this.string = string;
      },

      HashNode: function(pairs, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "hash";
        this.pairs = pairs;
      },

      IdNode: function(parts, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "ID";

        var original = "",
            dig = [],
            depth = 0,
            depthString = '';

        for(var i=0,l=parts.length; i<l; i++) {
          var part = parts[i].part;
          original += (parts[i].separator || '') + part;

          if (part === ".." || part === "." || part === "this") {
            if (dig.length > 0) {
              throw new Exception("Invalid path: " + original, this);
            } else if (part === "..") {
              depth++;
              depthString += '../';
            } else {
              this.isScoped = true;
            }
          } else {
            dig.push(part);
          }
        }

        this.original = original;
        this.parts    = dig;
        this.string   = dig.join('.');
        this.depth    = depth;
        this.idName   = depthString + this.string;

        // an ID is simple if it only has one part, and that part is not
        // `..` or `this`.
        this.isSimple = parts.length === 1 && !this.isScoped && depth === 0;

        this.stringModeValue = this.string;
      },

      PartialNameNode: function(name, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "PARTIAL_NAME";
        this.name = name.original;
      },

      DataNode: function(id, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "DATA";
        this.id = id;
        this.stringModeValue = id.stringModeValue;
        this.idName = '@' + id.stringModeValue;
      },

      StringNode: function(string, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "STRING";
        this.original =
          this.string =
          this.stringModeValue = string;
      },

      NumberNode: function(number, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "NUMBER";
        this.original =
          this.number = number;
        this.stringModeValue = Number(number);
      },

      BooleanNode: function(bool, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "BOOLEAN";
        this.bool = bool;
        this.stringModeValue = bool === "true";
      },

      CommentNode: function(comment, locInfo) {
        LocationInfo.call(this, locInfo);
        this.type = "comment";
        this.comment = comment;
      }
    };

    // Must be exported as an object rather than the root of the module as the jison lexer
    // most modify the object to operate properly.
    __exports__["default"] = AST;
  });