// Copyright (c) 2013, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of csslib.visitor;

// TODO(terry): Enable class for debug only; when conditional imports enabled.

/** Helper function to dump the CSS AST. */
String treeToDebugString(StyleSheet styleSheet, [bool useSpan = false]) {
  var to = new TreeOutput();
  new _TreePrinter(to, useSpan)..visitTree(styleSheet);
  return to.toString();
}

/** Tree dump for debug output of the CSS AST. */
class _TreePrinter extends Visitor {
  final TreeOutput output;
  final bool useSpan;
  _TreePrinter(this.output, this.useSpan) {
    output.printer = this;
  }

  void visitTree(StyleSheet tree) => visitStylesheet(tree);

  void heading(String heading, node) {
    if (useSpan) {
      output.heading(heading, node.span);
    } else {
      output.heading(heading);
    }
  }

  void visitStylesheet(StyleSheet node) {
    heading('Stylesheet', node);
    output.depth++;
    super.visitStyleSheet(node);
    output.depth--;
  }

  void visitTopLevelProduction(TopLevelProduction node) {
    heading('TopLevelProduction', node);
  }

  void visitDirective(Directive node) {
    heading('Directive', node);
  }

  void visitCalcTerm(CalcTerm node) {
    heading('CalcTerm', node);
    output.depth++;
    super.visitCalcTerm(node);
    output.depth--;
  }

  void visitCssComment(CssComment node) {
    heading('Comment', node);
    output.depth++;
    output.writeValue('comment value', node.comment);
    output.depth--;
  }

  void visitCommentDefinition(CommentDefinition node) {
    heading('CommentDefinition (CDO/CDC)', node);
    output.depth++;
    output.writeValue('comment value', node.comment);
    output.depth--;
  }

  void visitMediaExpression(MediaExpression node) {
    heading('MediaExpression', node);
    output.writeValue('feature', node.mediaFeature);
    if (node.andOperator) output.writeValue('AND operator', '');
    visitExpressions(node.exprs);
  }

  void visitMediaQueries(MediaQuery query) {
    output.heading('MediaQueries');
    output.writeValue('unary', query.unary);
    output.writeValue('media type', query.mediaType);
    output.writeNodeList('media expressions', query.expressions);
  }

  void visitMediaDirective(MediaDirective node) {
    heading('MediaDirective', node);
    output.depth++;
    output.writeNodeList('media queries', node.mediaQueries);
    output.writeNodeList('rule sets', node.rules);
    super.visitMediaDirective(node);
    output.depth--;
  }

  void visitDocumentDirective(DocumentDirective node) {
    heading('DocumentDirective', node);
    output.depth++;
    output.writeNodeList('functions', node.functions);
    output.writeNodeList('group rule body', node.groupRuleBody);
    output.depth--;
  }

  void visitSupportsDirective(SupportsDirective node) {
    heading('SupportsDirective', node);
    output.depth++;
    output.writeNode('condition', node.condition);
    output.writeNodeList('group rule body', node.groupRuleBody);
    output.depth--;
  }

  void visitSupportsConditionInParens(SupportsConditionInParens node) {
    heading('SupportsConditionInParens', node);
    output.depth++;
    output.writeNode('condition', node.condition);
    output.depth--;
  }

  void visitSupportsNegation(SupportsNegation node) {
    heading('SupportsNegation', node);
    output.depth++;
    output.writeNode('condition', node.condition);
    output.depth--;
  }

  void visitSupportsConjunction(SupportsConjunction node) {
    heading('SupportsConjunction', node);
    output.depth++;
    output.writeNodeList('conditions', node.conditions);
    output.depth--;
  }

  void visitSupportsDisjunction(SupportsDisjunction node) {
    heading('SupportsDisjunction', node);
    output.depth++;
    output.writeNodeList('conditions', node.conditions);
    output.depth--;
  }

  void visitViewportDirective(ViewportDirective node) {
    heading('ViewportDirective', node);
    output.depth++;
    super.visitViewportDirective(node);
    output.depth--;
  }

  void visitPageDirective(PageDirective node) {
    heading('PageDirective', node);
    output.depth++;
    output.writeValue('pseudo page', node._pseudoPage);
    super.visitPageDirective(node);
    output.depth;
  }

  void visitCharsetDirective(CharsetDirective node) {
    heading('Charset Directive', node);
    output.writeValue('charset encoding', node.charEncoding);
  }

  void visitImportDirective(ImportDirective node) {
    heading('ImportDirective', node);
    output.depth++;
    output.writeValue('import', node.import);
    super.visitImportDirective(node);
    output.writeNodeList('media', node.mediaQueries);
    output.depth--;
  }

  void visitContentDirective(ContentDirective node) {
    print("ContentDirective not implemented");
  }

  void visitKeyFrameDirective(KeyFrameDirective node) {
    heading('KeyFrameDirective', node);
    output.depth++;
    output.writeValue('keyframe', node.keyFrameName);
    output.writeValue('name', node.name);
    output.writeNodeList('blocks', node._blocks);
    output.depth--;
  }

  void visitKeyFrameBlock(KeyFrameBlock node) {
    heading('KeyFrameBlock', node);
    output.depth++;
    super.visitKeyFrameBlock(node);
    output.depth--;
  }

  void visitFontFaceDirective(FontFaceDirective node) {
    // TODO(terry): To Be Implemented
  }

  void visitStyletDirective(StyletDirective node) {
    heading('StyletDirective', node);
    output.writeValue('dartClassName', node.dartClassName);
    output.depth++;
    output.writeNodeList('rulesets', node.rules);
    output.depth--;
  }

  void visitNamespaceDirective(NamespaceDirective node) {
    heading('NamespaceDirective', node);
    output.depth++;
    output.writeValue('prefix', node._prefix);
    output.writeValue('uri', node._uri);
    output.depth--;
  }

  void visitVarDefinitionDirective(VarDefinitionDirective node) {
    heading('Less variable definition', node);
    output.depth++;
    visitVarDefinition(node.def);
    output.depth--;
  }

  void visitMixinRulesetDirective(MixinRulesetDirective node) {
    heading('Mixin top-level ${node.name}', node);
    output.writeNodeList('parameters', node.definedArgs);
    output.depth++;
    _visitNodeList(node.rulesets);
    output.depth--;
  }

  void visitMixinDeclarationDirective(MixinDeclarationDirective node) {
    heading('Mixin declaration ${node.name}', node);
    output.writeNodeList('parameters', node.definedArgs);
    output.depth++;
    visitDeclarationGroup(node.declarations);
    output.depth--;
  }

  /**
   * Added optional newLine for handling @include at top-level vs/ inside of
   * a declaration group.
   */
  void visitIncludeDirective(IncludeDirective node) {
    heading('IncludeDirective ${node.name}', node);
    var flattened = node.args.expand((e) => e).toList();
    output.writeNodeList('parameters', flattened);
  }

  void visitIncludeMixinAtDeclaration(IncludeMixinAtDeclaration node) {
    heading('IncludeMixinAtDeclaration ${node.include.name}', node);
    output.depth++;
    visitIncludeDirective(node.include);
    output.depth--;
  }

  void visitExtendDeclaration(ExtendDeclaration node) {
    heading('ExtendDeclaration', node);
    output.depth++;
    _visitNodeList(node.selectors);
    output.depth--;
  }

  void visitRuleSet(RuleSet node) {
    heading('Ruleset', node);
    output.depth++;
    super.visitRuleSet(node);
    output.depth--;
  }

  void visitDeclarationGroup(DeclarationGroup node) {
    heading('DeclarationGroup', node);
    output.depth++;
    output.writeNodeList('declarations', node.declarations);
    output.depth--;
  }

  void visitMarginGroup(MarginGroup node) {
    heading('MarginGroup', node);
    output.depth++;
    output.writeValue('@directive', node.margin_sym);
    output.writeNodeList('declarations', node.declarations);
    output.depth--;
  }

  void visitDeclaration(Declaration node) {
    heading('Declaration', node);
    output.depth++;
    if (node.isIE7) output.write('IE7 property');
    output.write('property');
    super.visitDeclaration(node);
    output.writeNode('expression', node._expression);
    if (node.important) {
      output.writeValue('!important', 'true');
    }
    output.depth--;
  }

  void visitVarDefinition(VarDefinition node) {
    heading('Var', node);
    output.depth++;
    output.write('defintion');
    super.visitVarDefinition(node);
    output.writeNode('expression', node._expression);
    output.depth--;
  }

  void visitSelectorGroup(SelectorGroup node) {
    heading('Selector Group', node);
    output.depth++;
    output.writeNodeList('selectors', node.selectors);
    output.depth--;
  }

  void visitSelector(Selector node) {
    heading('Selector', node);
    output.depth++;
    output.writeNodeList(
        'simpleSelectorsSequences', node.simpleSelectorSequences);
    output.depth--;
  }

  void visitSimpleSelectorSequence(SimpleSelectorSequence node) {
    heading('SimpleSelectorSequence', node);
    output.depth++;
    if (node.isCombinatorNone) {
      output.writeValue('combinator', "NONE");
    } else if (node.isCombinatorDescendant) {
      output.writeValue('combinator', "descendant");
    } else if (node.isCombinatorPlus) {
      output.writeValue('combinator', "+");
    } else if (node.isCombinatorGreater) {
      output.writeValue('combinator', ">");
    } else if (node.isCombinatorTilde) {
      output.writeValue('combinator', "~");
    } else if (node.isCombinatorShadowPiercingDescendant) {
      output.writeValue('combinator', '>>>');
    } else if (node.isCombinatorDeep) {
      output.writeValue('combinator', '/deep/');
    } else {
      output.writeValue('combinator', "ERROR UNKNOWN");
    }

    super.visitSimpleSelectorSequence(node);

    output.depth--;
  }

  void visitNamespaceSelector(NamespaceSelector node) {
    heading('Namespace Selector', node);
    output.depth++;

    super.visitNamespaceSelector(node);

    visitSimpleSelector(node.nameAsSimpleSelector);
    output.depth--;
  }

  void visitElementSelector(ElementSelector node) {
    heading('Element Selector', node);
    output.depth++;
    super.visitElementSelector(node);
    output.depth--;
  }

  void visitAttributeSelector(AttributeSelector node) {
    heading('AttributeSelector', node);
    output.depth++;
    super.visitAttributeSelector(node);
    String tokenStr = node.matchOperatorAsTokenString();
    output.writeValue('operator', '${node.matchOperator()} (${tokenStr})');
    output.writeValue('value', node.valueToString());
    output.depth--;
  }

  void visitIdSelector(IdSelector node) {
    heading('Id Selector', node);
    output.depth++;
    super.visitIdSelector(node);
    output.depth--;
  }

  void visitClassSelector(ClassSelector node) {
    heading('Class Selector', node);
    output.depth++;
    super.visitClassSelector(node);
    output.depth--;
  }

  void visitPseudoClassSelector(PseudoClassSelector node) {
    heading('Pseudo Class Selector', node);
    output.depth++;
    super.visitPseudoClassSelector(node);
    output.depth--;
  }

  void visitPseudoElementSelector(PseudoElementSelector node) {
    heading('Pseudo Element Selector', node);
    output.depth++;
    super.visitPseudoElementSelector(node);
    output.depth--;
  }

  void visitPseudoClassFunctionSelector(PseudoClassFunctionSelector node) {
    heading('Pseudo Class Function Selector', node);
    output.depth++;
    node.argument.visit(this);
    super.visitPseudoClassFunctionSelector(node);
    output.depth--;
  }

  void visitPseudoElementFunctionSelector(PseudoElementFunctionSelector node) {
    heading('Pseudo Element Function Selector', node);
    output.depth++;
    visitSelectorExpression(node.expression);
    super.visitPseudoElementFunctionSelector(node);
    output.depth--;
  }

  void visitSelectorExpression(SelectorExpression node) {
    heading('Selector Expression', node);
    output.depth++;
    output.writeNodeList('expressions', node.expressions);
    output.depth--;
  }

  void visitNegationSelector(NegationSelector node) {
    super.visitNegationSelector(node);
    output.depth++;
    heading('Negation Selector', node);
    output.writeNode('Negation arg', node.negationArg);
    output.depth--;
  }

  void visitUnicodeRangeTerm(UnicodeRangeTerm node) {
    heading('UnicodeRangeTerm', node);
    output.depth++;
    output.writeValue('1st value', node.first);
    output.writeValue('2nd value', node.second);
    output.depth--;
  }

  void visitLiteralTerm(LiteralTerm node) {
    heading('LiteralTerm', node);
    output.depth++;
    output.writeValue('value', node.text);
    output.depth--;
  }

  void visitHexColorTerm(HexColorTerm node) {
    heading('HexColorTerm', node);
    output.depth++;
    output.writeValue('hex value', node.text);
    output.writeValue('decimal value', node.value);
    output.depth--;
  }

  void visitNumberTerm(NumberTerm node) {
    heading('NumberTerm', node);
    output.depth++;
    output.writeValue('value', node.text);
    output.depth--;
  }

  void visitUnitTerm(UnitTerm node) {
    output.depth++;
    output.writeValue('value', node.text);
    output.writeValue('unit', node.unitToString());
    output.depth--;
  }

  void visitLengthTerm(LengthTerm node) {
    heading('LengthTerm', node);
    super.visitLengthTerm(node);
  }

  void visitPercentageTerm(PercentageTerm node) {
    heading('PercentageTerm', node);
    output.depth++;
    super.visitPercentageTerm(node);
    output.depth--;
  }

  void visitEmTerm(EmTerm node) {
    heading('EmTerm', node);
    output.depth++;
    super.visitEmTerm(node);
    output.depth--;
  }

  void visitExTerm(ExTerm node) {
    heading('ExTerm', node);
    output.depth++;
    super.visitExTerm(node);
    output.depth--;
  }

  void visitAngleTerm(AngleTerm node) {
    heading('AngleTerm', node);
    super.visitAngleTerm(node);
  }

  void visitTimeTerm(TimeTerm node) {
    heading('TimeTerm', node);
    super.visitTimeTerm(node);
  }

  void visitFreqTerm(FreqTerm node) {
    heading('FreqTerm', node);
    super.visitFreqTerm(node);
  }

  void visitFractionTerm(FractionTerm node) {
    heading('FractionTerm', node);
    output.depth++;
    super.visitFractionTerm(node);
    output.depth--;
  }

  void visitUriTerm(UriTerm node) {
    heading('UriTerm', node);
    output.depth++;
    super.visitUriTerm(node);
    output.depth--;
  }

  void visitFunctionTerm(FunctionTerm node) {
    heading('FunctionTerm', node);
    output.depth++;
    super.visitFunctionTerm(node);
    output.depth--;
  }

  void visitGroupTerm(GroupTerm node) {
    heading('GroupTerm', node);
    output.depth++;
    output.writeNodeList('grouped terms', node._terms);
    output.depth--;
  }

  void visitItemTerm(ItemTerm node) {
    heading('ItemTerm', node);
    super.visitItemTerm(node);
  }

  void visitIE8Term(IE8Term node) {
    heading('IE8Term', node);
    visitLiteralTerm(node);
  }

  void visitOperatorSlash(OperatorSlash node) {
    heading('OperatorSlash', node);
  }

  void visitOperatorComma(OperatorComma node) {
    heading('OperatorComma', node);
  }

  void visitOperatorPlus(OperatorPlus node) {
    heading('OperatorPlus', node);
  }

  void visitOperatorMinus(OperatorMinus node) {
    heading('OperatorMinus', node);
  }

  void visitVarUsage(VarUsage node) {
    heading('Var', node);
    output.depth++;
    output.write('usage ${node.name}');
    output.writeNodeList('default values', node.defaultValues);
    output.depth--;
  }

  void visitExpressions(Expressions node) {
    heading('Expressions', node);
    output.depth++;
    output.writeNodeList('expressions', node.expressions);
    output.depth--;
  }

  void visitBinaryExpression(BinaryExpression node) {
    heading('BinaryExpression', node);
    // TODO(terry): TBD
  }

  void visitUnaryExpression(UnaryExpression node) {
    heading('UnaryExpression', node);
    // TODO(terry): TBD
  }

  void visitIdentifier(Identifier node) {
    heading('Identifier(${output.toValue(node.name)})', node);
  }

  void visitWildcard(Wildcard node) {
    heading('Wildcard(*)', node);
  }

  void visitDartStyleExpression(DartStyleExpression node) {
    heading('DartStyleExpression', node);
  }

  void visitFontExpression(FontExpression node) {
    heading('Dart Style FontExpression', node);
  }

  void visitBoxExpression(BoxExpression node) {
    heading('Dart Style BoxExpression', node);
  }

  void visitMarginExpression(MarginExpression node) {
    heading('Dart Style MarginExpression', node);
  }

  void visitBorderExpression(BorderExpression node) {
    heading('Dart Style BorderExpression', node);
  }

  void visitHeightExpression(HeightExpression node) {
    heading('Dart Style HeightExpression', node);
  }

  void visitPaddingExpression(PaddingExpression node) {
    heading('Dart Style PaddingExpression', node);
  }

  void visitWidthExpression(WidthExpression node) {
    heading('Dart Style WidthExpression', node);
  }
}
