// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

export class FormattedContentBuilder {
  private lastOriginalPosition = 0;
  private formattedContent: string[] = [];
  private formattedContentLength = 0;
  private lastFormattedPosition = 0;
  private nestingLevel = 0;
  private newLines = 0;
  private enforceSpaceBetweenWords = true;
  private softSpace = false;
  private hardSpaces = 0;
  private cachedIndents = new Map<number, string>();

  mapping = {original: [0], formatted: [0]};

  constructor(private indentString: string) {
  }

  setEnforceSpaceBetweenWords(value: boolean): boolean {
    const oldValue = this.enforceSpaceBetweenWords;
    this.enforceSpaceBetweenWords = value;
    return oldValue;
  }

  addToken(token: string, offset: number): void {
    const last = this.formattedContent[this.formattedContent.length - 1];
    if (this.enforceSpaceBetweenWords && last && /\w/.test(last[last.length - 1]) && /\w/.test(token)) {
      this.addSoftSpace();
    }

    this.appendFormatting();

    // Insert token.
    this.addMappingIfNeeded(offset);
    this.addText(token);
  }

  addSoftSpace(): void {
    if (!this.hardSpaces) {
      this.softSpace = true;
    }
  }

  addHardSpace(): void {
    this.softSpace = false;
    ++this.hardSpaces;
  }

  addNewLine(noSquash?: boolean): void {
    // Avoid leading newlines.
    if (!this.formattedContentLength) {
      return;
    }
    if (noSquash) {
      ++this.newLines;
    } else {
      this.newLines = this.newLines || 1;
    }
  }

  increaseNestingLevel(): void {
    this.nestingLevel += 1;
  }

  decreaseNestingLevel(): void {
    if (this.nestingLevel > 0) {
      this.nestingLevel -= 1;
    }
  }

  content(): string {
    return this.formattedContent.join('') + (this.newLines ? '\n' : '');
  }

  private appendFormatting(): void {
    if (this.newLines) {
      for (let i = 0; i < this.newLines; ++i) {
        this.addText('\n');
      }
      this.addText(this.indent());
    } else if (this.softSpace) {
      this.addText(' ');
    }
    if (this.hardSpaces) {
      for (let i = 0; i < this.hardSpaces; ++i) {
        this.addText(' ');
      }
    }
    this.newLines = 0;
    this.softSpace = false;
    this.hardSpaces = 0;
  }

  private indent(): string {
    const cachedValue = this.cachedIndents.get(this.nestingLevel);
    if (cachedValue) {
      return cachedValue;
    }

    let fullIndent = '';
    for (let i = 0; i < this.nestingLevel; ++i) {
      fullIndent += this.indentString;
    }

    // Cache a maximum of 20 nesting level indents.
    if (this.nestingLevel <= 20) {
      this.cachedIndents.set(this.nestingLevel, fullIndent);
    }
    return fullIndent;
  }

  private addText(text: string): void {
    this.formattedContent.push(text);
    this.formattedContentLength += text.length;
  }

  private addMappingIfNeeded(originalPosition: number): void {
    if (originalPosition - this.lastOriginalPosition === this.formattedContentLength - this.lastFormattedPosition) {
      return;
    }
    this.mapping.original.push(originalPosition);
    this.lastOriginalPosition = originalPosition;
    this.mapping.formatted.push(this.formattedContentLength);
    this.lastFormattedPosition = this.formattedContentLength;
  }
}
