// 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 '../visitor.dart';

// 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 = TreeOutput();
  _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;
  }

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

  void heading(String heading, TreeNode 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--;
  }

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

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

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

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

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

  @override
  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);
  }

  @override
  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--;
  }

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

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

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

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

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

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

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

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

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

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

  @override
  void visitContentDirective(ContentDirective node) {
    print('ContentDirective not implemented');
  }

  @override
  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--;
  }

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

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

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

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

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

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

  @override
  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.
  @override
  void visitIncludeDirective(IncludeDirective node) {
    heading('IncludeDirective ${node.name}', node);
    var flattened = node.args.expand((e) => e).toList();
    output.writeNodeList('parameters', flattened);
  }

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

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

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

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

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

  @override
  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--;
  }

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

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

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

  @override
  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 {
      output.writeValue('combinator', 'ERROR UNKNOWN');
    }

    super.visitSimpleSelectorSequence(node);

    output.depth--;
  }

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

    super.visitNamespaceSelector(node);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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