import { CommonToken } from 'antlr4ts';
import { ParseTree, TerminalNode } from 'antlr4ts/tree';
import { ThriftData, ThriftParser, CommentChannel } from 'thrift-parser-ts';
import * as ThriftParserNS from 'thrift-parser-ts/lib/ThriftParser';

type IsKindFunc = (node: ParseTree) => boolean;
type TightFN = (index: number, node: ParseTree) => boolean;
type NodeProcessFunc = (this: PureThriftFormatter, node: ParseTree) => void;
type FieldContext = ThriftParserNS.FieldContext | ThriftParserNS.Enum_fieldContext;

export interface Option {
  indent: number,
  patchRequired: boolean,
  patchSeparator: boolean,
  keepComment: boolean,
  alignByAssign: boolean,
  alignByField: boolean,
}

export const newOption = (opt?: Partial<Option>):Option => {
  const defaultOption: Option = {
    indent: 4,
    patchRequired: true,
    patchSeparator: true,
    keepComment: true,
    alignByAssign: false,
    alignByField: false,
  }
  return {
    ...defaultOption,
    ...opt,
  }
}

export const isToken = (node: ParseTree | undefined, text: string): boolean => {
    return node instanceof TerminalNode && node.symbol.text === text;
}

export const isEOF = (node: ParseTree): boolean => {
  return node instanceof TerminalNode && node.symbol.type === ThriftParser.EOF;
}

const fakeNodeLineNo = -1;

const isFakeNode = (node: TerminalNode): boolean => {
  return node.symbol.line === fakeNodeLineNo;
}

const notSameClass = (a:ParseTree, b: ParseTree): boolean => {
  return a.constructor.name !== b.constructor.name;
}

const isNeedNewLineNode = (node: ParseTree): boolean => {
  return (
    node instanceof ThriftParserNS.Enum_ruleContext ||
    node instanceof ThriftParserNS.Struct_Context ||
    node instanceof ThriftParserNS.Union_Context ||
    node instanceof ThriftParserNS.Exception_Context ||
    node instanceof ThriftParserNS.ServiceContext
  );
}

const isFunctionOrThrowsListNode = (node: ParseTree): boolean => {
  return (
    node instanceof ThriftParserNS.Function_Context ||
    node instanceof ThriftParserNS.Throws_listContext
  );
}

const isFieldOrEnumField = (node: ParseTree|undefined): boolean => {
  return (
    node instanceof ThriftParserNS.FieldContext ||
    node instanceof ThriftParserNS.Enum_fieldContext
  );
}

const splitFieldChildrenByAssign = (node: FieldContext):[ParseTree[], ParseTree[]] => {
  const children: ParseTree[] = node.children || [];

  let i = 0;
  let curLeft = true;
  for (;i < node.childCount; i++) {
    const child = node.getChild(i);
    if (isToken(child, '=') || (child instanceof ThriftParserNS.List_separatorContext)){
      curLeft = false;
      break
    }
  }
  // current child is belong to left.
  if (curLeft) {
    i++;
  }

  const left = children.slice(0, i);
  const right = children.slice(i);
  return [left, right];
}

export const splitFieldByAssign = (node: FieldContext):[ParseTree, ParseTree] => {
  /*
    split field's children to [left, right]
    field: '1: required i32 number_a = 0,'
    left:  '1: required i32 number_a'
    right: '= 0,'
  */
  let left :FieldContext|undefined = undefined;
  let right :FieldContext|undefined = undefined;

  if (node instanceof ThriftParserNS.FieldContext) {
    left = new ThriftParserNS.FieldContext(node.parent, 0);
    right = new ThriftParserNS.FieldContext(node.parent, 0);
  } else {
    left = new ThriftParserNS.Enum_fieldContext(node.parent, 0);
    right = new ThriftParserNS.Enum_fieldContext(node.parent, 0);
  }
  const [leftChildren, rightChildren] = splitFieldChildrenByAssign(node);
  for (const child of leftChildren) {
    left.addAnyChild(child);
  }
  for (const child of rightChildren) {
    right.addAnyChild(child);
  }

  return [left, right];
}

const getSplitFieldsLeftRightSize = (fields: ParseTree[]):[number, number] => {
  let leftMaxSize = 0;
  let rightMaxSize = 0;

  for (const field of fields) {
    const node = <FieldContext> field;
    const [left, right] = splitFieldByAssign(node);
    const leftSize = new PureThriftFormatter().formatNode(left).length;
    const rightSize = new PureThriftFormatter().formatNode(right).length;
    leftMaxSize = leftMaxSize > leftSize?leftMaxSize:leftSize;
    rightMaxSize = rightMaxSize > rightSize?rightMaxSize:rightSize;
  }
  return [leftMaxSize, rightMaxSize];
}

export const getNodeChildren = (node: ParseTree): ParseTree[] => {
  const children = [];
  for (let i = 0; i < node.childCount; i++) {
    children.push(node.getChild(i));
  }
  return children;
}

export const walkNode = (root: ParseTree, callback: (node: ParseTree) => void) => {
  const stack: ParseTree[] = [root];
  while (stack.length > 0) {
    const node = stack.shift();
    if (node === undefined) {
      break;
    }
    callback(node);
    const children = getNodeChildren(node);
    children.forEach(value => stack.push(value))
  }
}

export const splitRepeatNodes = (nodes: ParseTree[], kindFn: IsKindFunc): [ParseTree[], ParseTree[]] => {
  const children = [];
  for (const [index, node] of nodes.entries()) {
    if (!kindFn(node)) {
      return [children, nodes.slice(index)];
    }
    children.push(node);
  }
  return [children, []];
}

const genInlineContext = (join = ' ', tightFn?: TightFN | undefined): NodeProcessFunc => {
  return function (this: PureThriftFormatter, node: ParseTree) {
    for (let i = 0; i < node.childCount; i++) {
      const child = node.getChild(i);
      if (i > 0 && join.length > 0) {
        if (!tightFn || !tightFn(i, child)) {
          this.append(join);
        }
      }
      this.processNode(child);
    }
  };
}

const genSubblocksContext = (start: number, kindFn: IsKindFunc): NodeProcessFunc => {
  return function (this: PureThriftFormatter, node: ParseTree) {
    const children = getNodeChildren(node);
    this.processInlineNodes(children.slice(0, start));
    this.newline();

    const leftChildren = children.slice(start);
    const [subblocks, leftNodes] = splitRepeatNodes(leftChildren, kindFn);

    this.beforeSubblocks(subblocks);
    this.processBlockNodes(subblocks, ' '.repeat(this._option.indent));
    this.afterSubblocks(subblocks);

    this.newline();
    this.processInlineNodes(leftNodes);
  };
}

const tupleTightInline = genInlineContext(
  ' ',
  (i, n) =>
    isToken(n, '(') ||
    isToken(n, ')') ||
    isToken(n.parent?.getChild(i - 1), '(') ||
    n instanceof ThriftParserNS.List_separatorContext
);

const listSeparatorInline = genInlineContext(
  ' ',
  (_, node) => node instanceof ThriftParserNS.List_separatorContext
);

const fieldSubblocks = genSubblocksContext(
  3,
  (n) => n instanceof ThriftParserNS.FieldContext
);

export class PureThriftFormatter {
  protected _option: Option = newOption();
  protected currentIndent = '';
  protected newlineCounter = 0;
  private _out = '';

  formatNode(node: ParseTree): string {
    this._out = '';
    this.newlineCounter = 0;
    this.currentIndent = '';

    this.processNode(node);
    return this._out;
  }

  option(opt: Option) {
    this._option = opt;
  }

  get out(): string {
    return this._out;
  }

  private push(text: string) {
    this._out += text;
  }

  // if this.newlineCounter was set. `append` will first write newlines and then append
  protected append(text: string) {
    if (this.newlineCounter > 0) {
      this.push('\n'.repeat(this.newlineCounter));
      this.newlineCounter = 0;
    }
    this.push(text);
  }

  // appendCurrentLine append to current line, and  ignore this.newlineCounter.
  protected appendCurrentLine(text: string) {
    this.push(text);
  }

  protected newline(repeat = 1) {
    const diff = repeat - this.newlineCounter;
    if (diff > 0) {
      this.newlineCounter += diff;
    }
  }

  protected setCurrentIndent(indent = '') {
    this.currentIndent = indent;
  }

  protected pushCurrentIndent() {
    if (this.currentIndent.length > 0) {
      this.append(this.currentIndent);
    }
  }

  protected beforeBlockNode(_: ParseTree) {} // eslint-disable-line
  protected afterBlockNode(_: ParseTree) {}  // eslint-disable-line
  protected beforeSubblocks(_: ParseTree[]) {} // eslint-disable-line
  protected afterSubblocks(_: ParseTree[]) {}  // eslint-disable-line
  protected beforeProcessNode(_: ParseTree) {} // eslint-disable-line
  protected afterProcessNode(_: ParseTree) {}  // eslint-disable-line

  protected processBlockNodes(nodes: ParseTree[], indent = '') {
    let lastNode: ParseTree | undefined = undefined;
    // eslint-disable-next-line
    for (let [index, node] of nodes.entries()) {
      if (
        node instanceof ThriftParserNS.HeaderContext ||
        node instanceof ThriftParserNS.DefinitionContext
      ) {
        node = node.getChild(0);
      }

      this.beforeBlockNode(node)

      if (index > 0 && lastNode !== undefined) {
        if (notSameClass(lastNode, node)|| isNeedNewLineNode(node)) {
          this.newline(2);
        } else {
          this.newline();
        }
      }

      this.setCurrentIndent(indent);
      this.processNode(node);

      this.afterBlockNode(node);
      lastNode = node;
    }
  }

  protected processInlineNodes(nodes: ParseTree[], join = ' ') {
    // eslint-disable-next-line
    for (const [index, node] of nodes.entries()) {
      if (index > 0) {
        this.append(join);
      }
      this.processNode(node);
    }
  }

  protected processNode(node: ParseTree): void {
    this.beforeProcessNode(node)
    this._processNode(node)
    this.afterProcessNode(node)
  }

  private _processNode(node: ParseTree): void {
    if (node instanceof TerminalNode) {
      this.TerminalNode(node);
    } else if (node instanceof ThriftParserNS.DocumentContext) {
      this.DocumentContext(node);
    } else if (node instanceof ThriftParserNS.HeaderContext) {
      this.HeaderContext(node);
    } else if (node instanceof ThriftParserNS.DefinitionContext) {
      this.DefinitionContext(node);
    } else if (node instanceof ThriftParserNS.Include_Context) {
      this.Include_Context(node);
    } else if (node instanceof ThriftParserNS.Namespace_Context) {
      this.Namespace_Context(node);
    } else if (node instanceof ThriftParserNS.Typedef_Context) {
      this.Typedef_Context(node);
    } else if (node instanceof ThriftParserNS.Base_typeContext) {
      this.Base_typeContext(node);
    } else if (node instanceof ThriftParserNS.Real_base_typeContext) {
      this.Real_base_typeContext(node);
    } else if (node instanceof ThriftParserNS.Const_ruleContext) {
      this.Const_ruleContext(node);
    } else if (node instanceof ThriftParserNS.Const_valueContext) {
      this.Const_valueContext(node);
    } else if (node instanceof ThriftParserNS.IntegerContext) {
      this.IntegerContext(node);
    } else if (node instanceof ThriftParserNS.Container_typeContext) {
      this.Container_typeContext(node);
    } else if (node instanceof ThriftParserNS.Set_typeContext) {
      this.Set_typeContext(node);
    } else if (node instanceof ThriftParserNS.List_typeContext) {
      this.List_typeContext(node);
    } else if (node instanceof ThriftParserNS.Cpp_typeContext) {
      this.Cpp_typeContext(node);
    } else if (node instanceof ThriftParserNS.Const_mapContext) {
      this.Const_mapContext(node);
    } else if (node instanceof ThriftParserNS.Const_map_entryContext) {
      this.Const_map_entryContext(node);
    } else if (node instanceof ThriftParserNS.List_separatorContext) {
      this.List_separatorContext(node);
    } else if (node instanceof ThriftParserNS.Field_idContext) {
      this.Field_idContext(node);
    } else if (node instanceof ThriftParserNS.Field_reqContext) {
      this.Field_reqContext(node);
    } else if (node instanceof ThriftParserNS.Field_typeContext) {
      this.Field_typeContext(node);
    } else if (node instanceof ThriftParserNS.Map_typeContext) {
      this.Map_typeContext(node);
    } else if (node instanceof ThriftParserNS.Const_listContext) {
      this.Const_listContext(node);
    } else if (node instanceof ThriftParserNS.Enum_ruleContext) {
      this.Enum_ruleContext(node);
    } else if (node instanceof ThriftParserNS.Struct_Context) {
      this.Struct_Context(node);
    } else if (node instanceof ThriftParserNS.Union_Context) {
      this.Union_Context(node);
    } else if (node instanceof ThriftParserNS.Exception_Context) {
      this.Exception_Context(node);
    } else if (node instanceof ThriftParserNS.Enum_fieldContext) {
      this.Enum_fieldContext(node);
    } else if (node instanceof ThriftParserNS.FieldContext) {
      this.FieldContext(node);
    } else if (node instanceof ThriftParserNS.Function_Context) {
      this.Function_Context(node);
    } else if (node instanceof ThriftParserNS.OnewayContext) {
      this.OnewayContext(node);
    } else if (node instanceof ThriftParserNS.Function_typeContext) {
      this.Function_typeContext(node);
    } else if (node instanceof ThriftParserNS.Throws_listContext) {
      this.Throws_listContext(node);
    } else if (node instanceof ThriftParserNS.Type_annotationsContext) {
      this.Type_annotationsContext(node);
    } else if (node instanceof ThriftParserNS.Type_annotationContext) {
      this.Type_annotationContext(node);
    } else if (node instanceof ThriftParserNS.Annotation_valueContext) {
      this.Annotation_valueContext(node);
    } else if (node instanceof ThriftParserNS.ServiceContext) {
      this.ServiceContext(node);
    } else if (node instanceof ThriftParserNS.SenumContext) {
      this.SenumContext(node);
    } else {
      // unsupport node
    }
  }

  protected TerminalNode(node: TerminalNode) {
    if (isEOF(node)) {
      return;
    }

    this.pushCurrentIndent();
    this.setCurrentIndent('');

    this.append(node.symbol.text || '');
  }

  protected DocumentContext: NodeProcessFunc = function (this: PureThriftFormatter, node: ParseTree) {
    const children = getNodeChildren(node);
    this.processBlockNodes(children);
  };

  protected HeaderContext: NodeProcessFunc = function (this: PureThriftFormatter,node: ParseTree) {
    this.processNode(node.getChild(0));
  };

  protected DefinitionContext: NodeProcessFunc = function (this: PureThriftFormatter, node: ParseTree) {
    this.processNode(node.getChild(0));
  };

  // TODO: clean this?
  protected Include_Context: NodeProcessFunc = genInlineContext();
  protected Namespace_Context: NodeProcessFunc = genInlineContext();
  protected Typedef_Context: NodeProcessFunc = genInlineContext();
  protected Base_typeContext: NodeProcessFunc = genInlineContext();
  protected Field_typeContext: NodeProcessFunc = genInlineContext();
  protected Real_base_typeContext: NodeProcessFunc = genInlineContext();
  protected Const_ruleContext: NodeProcessFunc = genInlineContext();
  protected Const_valueContext: NodeProcessFunc = genInlineContext();
  protected IntegerContext: NodeProcessFunc = genInlineContext();
  protected Container_typeContext: NodeProcessFunc = genInlineContext('');
  protected Set_typeContext: NodeProcessFunc = genInlineContext('');
  protected List_typeContext: NodeProcessFunc = genInlineContext('');
  protected Cpp_typeContext: NodeProcessFunc = genInlineContext();
  protected Const_mapContext: NodeProcessFunc = genInlineContext();
  protected Const_map_entryContext: NodeProcessFunc = genInlineContext();
  protected List_separatorContext: NodeProcessFunc = genInlineContext();
  protected Field_idContext: NodeProcessFunc = genInlineContext('');
  protected Field_reqContext: NodeProcessFunc = genInlineContext();
  protected Map_typeContext: NodeProcessFunc = genInlineContext(
    ' ',
    (i, n) => !isToken(n.parent?.getChild(i - 1), ',')
  );
  protected Const_listContext: NodeProcessFunc = listSeparatorInline
  protected Enum_ruleContext: NodeProcessFunc = genSubblocksContext(
    3,
    (n) => n instanceof ThriftParserNS.Enum_fieldContext
  );
  protected Enum_fieldContext: NodeProcessFunc = listSeparatorInline
  protected Struct_Context: NodeProcessFunc = fieldSubblocks
  protected Union_Context: NodeProcessFunc = fieldSubblocks
  protected Exception_Context: NodeProcessFunc = fieldSubblocks
  protected FieldContext: NodeProcessFunc = listSeparatorInline
  protected Function_Context: NodeProcessFunc = tupleTightInline
  protected OnewayContext: NodeProcessFunc = genInlineContext();
  protected Function_typeContext: NodeProcessFunc = genInlineContext();
  protected Throws_listContext: NodeProcessFunc = tupleTightInline
  protected Type_annotationsContext: NodeProcessFunc = tupleTightInline
  protected Type_annotationContext: NodeProcessFunc = tupleTightInline
  protected Annotation_valueContext: NodeProcessFunc = genInlineContext();
  protected ServiceContext_Default: NodeProcessFunc = genSubblocksContext(
    3,
    (n:ParseTree) => n instanceof ThriftParserNS.Function_Context
  );
  protected ServiceContext_Extends: NodeProcessFunc = genSubblocksContext(
    5,
    (n:ParseTree) => n instanceof ThriftParserNS.Function_Context
  );
  protected ServiceContext: NodeProcessFunc = function (this: PureThriftFormatter, n: ParseTree) {
    const node = <ThriftParserNS.ServiceContext>n;
    if (isToken(node.getChild(2), 'extends')) {
      this.ServiceContext_Extends(node);
    } else {
      this.ServiceContext_Default(node);
    }
  };
  protected SenumContext: NodeProcessFunc = function (this: PureThriftFormatter, _:ParseTree) {}; // eslint-disable-line
}

const patchFieldRequired = (n: ParseTree): void => {
  if (!(n instanceof ThriftParserNS.FieldContext)) {
    return;
  }

  if (n.parent === undefined || isFunctionOrThrowsListNode(n.parent)) {
    return;
  }

  let i = 0;
  for (; i < n.childCount; i++) {
    const child = n.getChild(i);
    if (child instanceof ThriftParserNS.Field_reqContext) {
      return;
    }
    if (child instanceof ThriftParserNS.Field_typeContext) {
      break;
    }
  }

  const fakeToken = new CommonToken(ThriftParser.T__20, 'required');
  fakeToken.line = fakeNodeLineNo;
  fakeToken.charPositionInLine = fakeNodeLineNo;
  fakeToken.tokenIndex = -1;
  const fakeNode = new TerminalNode(fakeToken);
  const fakeReq = new ThriftParserNS.Field_reqContext(n, 0);

  fakeNode.setParent(fakeReq);
  fakeReq.addChild(fakeNode);
  fakeReq.setParent(n);
  n.children?.splice(i, 0, fakeReq); // addChild
}

const patchFieldListSeparator = (n: ParseTree): void => {
  if (!(n instanceof ThriftParserNS.Enum_fieldContext
    || n instanceof ThriftParserNS.FieldContext
    || n instanceof ThriftParserNS.Function_Context)) {
    return;
  }

  const child = n.getChild(n.childCount - 1);
  if (child instanceof ThriftParserNS.List_separatorContext) {
    const comma = <TerminalNode>child.getChild(0);
    const token = <CommonToken> comma.symbol;
    token.text = ',';
    return;
  }

  const fakeToken = new CommonToken(ThriftParser.COMMA, ',');
  fakeToken.line = fakeNodeLineNo;
  fakeToken.charPositionInLine = fakeNodeLineNo;
  fakeToken.tokenIndex = -1;
  const fakeNode = new TerminalNode(fakeToken);
  const fakeCtx = new ThriftParserNS.List_separatorContext(n, 0);

  fakeNode.setParent(fakeCtx);
  fakeCtx.addChild(fakeNode);

  fakeCtx.setParent(n);
  n.addChild(fakeCtx);
}

const patchRemoveLastListSeparator = (n: ParseTree): void => {
  const isInlineField = n instanceof ThriftParserNS.FieldContext
    && n.parent !== undefined && isFunctionOrThrowsListNode(n.parent);
  const isInlineNode = n instanceof ThriftParserNS.Type_annotationContext;

  if (!(isInlineField || isInlineNode)) {
    return;
  }

  if (n.parent === undefined) {
    return;
  }

  let last = false;
  const brothers = n.parent.children || [];
  const bortherCount = n.parent.childCount;
  for (let i = 0; i < bortherCount; i++) {
    if (brothers[i] === n) {
      if (i === bortherCount - 1 || notSameClass(n, brothers[i + 1])) {
        last = true;
        break;
      }
    }
  }

  if (last) {
    const child = n.getChild(n.childCount - 1);
    if (child instanceof ThriftParserNS.List_separatorContext) {
      n.removeLastChild();
    }
  }
}

const calcSubBlocksCommentPadding = (subblocks: ParseTree[]): number => {
  let padding = 0;
  for (const subblock of subblocks) {
    const nodeLength = (new PureThriftFormatter().formatNode(subblock)).length;
    padding = padding >= nodeLength? padding: nodeLength;
  }

  if (padding > 0) {
    padding = padding + 1;
  }
  return padding;
}

export const calcFieldAlignByAssignPadding = (fields: ParseTree[]): [number, number] => {
  if (fields.length === 0 || !isFieldOrEnumField(fields[0]) ) {
    return  [0, 0];
  }

  const [leftMaxSize, rightMaxSize] = getSplitFieldsLeftRightSize(fields);
  // add extra space "xxx = yyy" -> "xxx" + " " + "= yyy"
  const assignPadding = leftMaxSize + 1;
  let commentPadding = assignPadding + rightMaxSize + 1; // add an extra space for next comment
  /*
      if it is not list sep, need add extra space
      case 1 --> "1: bool a = true," ---> "1: bool a" + " " + "= true,"
      case 2 --> "2: bool b," ---> "2: bool b" + "" + ","
  */
  if (rightMaxSize <= 1) { // case 1
    commentPadding = commentPadding - 1;
  }
  return [assignPadding, commentPadding];
}

const getFieldChildName = (n: ParseTree): string =>  {
  if (isToken(n, '=')) {
    return '=';
  }
  return n.constructor.name;
}

const calcFieldAlignByFieldPaddingMap = (fields: ParseTree[]):[Map<string, number>, number] => {
  const paddingMap: Map<string, number> = new Map();
  if (fields.length == 0 || !isFieldOrEnumField(fields[0])) {
    return [paddingMap, 0];
  }

  const nameLevels: Map<string, number> = new Map();
  for (const field of fields) {
    let i = 0;
    for (;i < field.childCount -1; i++) {
      const nameA = getFieldChildName(field.getChild(i))
      const nameB = getFieldChildName(field.getChild(i+1))
      if (!nameLevels.has(nameA)) {
        nameLevels.set(nameA, 0);
      }
      if (!nameLevels.has(nameB)) {
        nameLevels.set(nameB, 0);
      }
      const levelB = Math.max(nameLevels.get(nameB)!, nameLevels.get(nameA)! +1);  // eslint-disable-line
      nameLevels.set(nameB, levelB);
    }
  }

  // 检查 levles 连续
  if (Math.max(...nameLevels.values()) != (nameLevels.size -1)) {
    return [paddingMap, 0];
  }

  const levelLength: Map<number, number> = new Map();
  for (const field of fields) {
    let i = 0;
    for (;i < field.childCount; i++) {
      const child = field.getChild(i);
      const level = nameLevels.get(getFieldChildName(child))!; // eslint-disable-line
      const length = new PureThriftFormatter().formatNode(child).length;

      levelLength.set(level, Math.max(levelLength.get(level) || 0, length));
    }
  }

  const sep = new ThriftParserNS.List_separatorContext(undefined, 0);
  const levelPadding: Map<number, number> = new Map();
  for (const [level, ] of levelLength) {
    let padding = level;
    if (level === nameLevels.get(getFieldChildName(sep))) {
      padding -= 1;
    }

    let i = 0;
    for (;i < level; i++) {
      padding += levelLength.get(i) || 0;
    }

    levelPadding.set(level, padding);
  }

  for (const [name, level] of nameLevels) {
    paddingMap.set(name, levelPadding.get(level)!); // eslint-disable-line
  }

  let commentPadding = levelLength.size;
  for (const [, length] of levelLength) {
    commentPadding += length;
  }
  if (paddingMap.has(getFieldChildName(sep))) {
    commentPadding -= 1;
  }

  return [paddingMap, commentPadding];
}

export class ThriftFormatter extends PureThriftFormatter {
  private data: ThriftData;
  private document: ThriftParserNS.DocumentContext;

  private fieldCommentPadding = 0;
  private fieldAlignByAssignPadding = 0;
  private fieldAlignByFieldPaddingMap: Map<string, number> = new Map();
  private lastTokenIndex = -1;

  constructor(data: ThriftData) {
    super();

    this.data = data;
    this.document = data.document;
  }

  format(): string {
    this.patch();
    return this.formatNode(this.document);
  }

  public patch() {
    if (this._option.patchRequired) {
      walkNode(this.document, patchFieldRequired);
    }
    if (this._option.patchSeparator) {
      walkNode(this.document, patchFieldListSeparator);
      walkNode(this.document, patchRemoveLastListSeparator);
    }
  }

  protected beforeSubblocks(subblocks: ParseTree[]): void {
    if (this._option.alignByField) {
      const [paddingMap, commentPadding] = calcFieldAlignByFieldPaddingMap(subblocks);
      paddingMap.forEach((value, key, m) => {m.set(key, this.calcAddIndentPadding(value))});

      this.fieldAlignByFieldPaddingMap = paddingMap;
      this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding);
    } else if (this._option.alignByAssign) {
      const [alignPadding, commentPadding] = calcFieldAlignByAssignPadding(subblocks);

      this.fieldAlignByAssignPadding = this.calcAddIndentPadding(alignPadding);
      this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding);
    }

    if (this._option.keepComment && this.fieldCommentPadding === 0) {
      const commentPadding = calcSubBlocksCommentPadding(subblocks);

      this.fieldCommentPadding = this.calcAddIndentPadding(commentPadding);
    }
  }

  protected afterSubblocks(_: ParseTree[]): void {
    this.fieldAlignByAssignPadding = 0;
    this.fieldAlignByFieldPaddingMap = new Map();
    this.fieldCommentPadding = 0;
  }

  protected afterBlockNode(_: ParseTree): void {
    this.addTailComment();
  }

  protected beforeProcessNode(n: ParseTree): void {
    this.addAlignPadding(n)
  }

  protected get currentLine() :string {
    if (this.newlineCounter > 0) {
      return '';
    }

    const parts = this.out.split('\n');
    const cur = parts[parts.length -1];
    return cur;
  }

  private calcAddIndentPadding(padding:number) : number {
    if (padding > 0) {
      padding += this._option.indent;
    }
    return padding;
  }

  private addAlignPadding(n: ParseTree): void {
    if (!isFieldOrEnumField(n.parent)) {
      return;
    }

    if (this._option.alignByField && this.fieldAlignByFieldPaddingMap.size > 0) {
      const name = getFieldChildName(n);
      const padding = this.fieldAlignByFieldPaddingMap.get(name);
      if (padding && padding > 0) {
        this.padding(padding);
      }
      return;
    }

    if (this._option.alignByAssign && isToken(n, '=') ) {
      this.padding(this.fieldAlignByAssignPadding);
      return;
    }

    return;
  }

  private padding(padding: number, pad = ' ') {
    if (padding > 0) {
      padding = padding - this.currentLine.length;
      if (padding > 0) {
        this.appendCurrentLine(pad.repeat(padding));
      }
    }
  }

  private addInlineComments(node: TerminalNode) {
    if (!this._option.keepComment) {
      return;
    }

    if (isFakeNode(node)) {
      return;
    }

    const tokenIndex = node.symbol.tokenIndex;
    const comments = [];
    const tokens = this.data.tokens.getTokens();
    for (const token of tokens.slice(this.lastTokenIndex + 1)) {
      if (token.channel != CommentChannel) {
        continue;
      }
      if (token.tokenIndex < tokenIndex) {
        comments.push(token);
      }
    }

    for (const token of comments) {
      if (token.tokenIndex > 0 && token.type == ThriftParser.ML_COMMENT) {
        this.newline(2);
      }
      if (token.text === undefined) {
        return;
      }

      // TODO: 确认是否需要 clean indent;
      this.pushCurrentIndent();

      const text = token.text;
      this.append(text.trim());

      const lastLine = token.line + text.split('\n').length - 1;
      const lineDiff = node.symbol.line - lastLine;
      const isTight =
        token.type == ThriftParser.SL_COMMENT ||
        isEOF(node) ||
        (0 < lineDiff && lineDiff <= 1);

      if (isTight) {
        this.newline();
      } else {
        this.newline(2);
      }
    }

    this.lastTokenIndex = tokenIndex;
  }

  private addTailComment() {
    if (!this._option.keepComment) {
      return;
    }
    if (this.lastTokenIndex === -1) {
      return;
    }

    const tokens = this.data.tokens.getTokens();
    const lastToken = tokens[this.lastTokenIndex];
    const comments = [];
    for (const token of tokens.slice(this.lastTokenIndex + 1)) {
      if (token.line != lastToken.line) {
        break;
      }
      if (token.channel != CommentChannel) {
        continue;
      }
      comments.push(token);
    }

    if (comments.length > 0) {
      const comment = comments[0];
      if (comment.text === undefined) {
        return;
      }

      // align comment
      if (this.fieldCommentPadding > 0) {
        this.padding(this.fieldCommentPadding, ' ');
      } else {
        this.appendCurrentLine(' ');
      }

      this.appendCurrentLine(comment.text.trim());
      this.append('');
      this.lastTokenIndex = comment.tokenIndex;
    }
  }

  protected TerminalNode(n: TerminalNode) {
    if (this.newlineCounter > 0) {
      this.addTailComment();
    }

    this.addInlineComments(n);

    super.TerminalNode(n);
  }
}
