// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import { TurboshaftGraphNode } from "./phases/turboshaft-graph-phase/turboshaft-graph-node";

export class InliningPosition {
  sourceId: number;
  inliningPosition: SourcePosition;

  constructor(sourceId: number, inliningPosition: SourcePosition) {
    this.sourceId = sourceId;
    this.inliningPosition = inliningPosition;
  }
}

export class SourcePosition {
  scriptOffset: number;
  inliningId: number;

  constructor(scriptOffset: number, inliningId: number) {
    this.scriptOffset = scriptOffset;
    this.inliningId = inliningId;
  }

  public lessOrEquals(other: SourcePosition): number {
    if (this.inliningId == other.inliningId) {
      return this.scriptOffset - other.scriptOffset;
    }
    return this.inliningId - other.inliningId;
  }

  public equals(other: SourcePosition): boolean {
    return this.inliningId == other.inliningId && this.scriptOffset == other.scriptOffset;
  }

  public isValid(): boolean {
    return typeof this.scriptOffset !== undefined && typeof this.inliningId !== undefined;
  }

  public toString(): string {
    return `SP:${this.inliningId}:${this.scriptOffset}`;
  }
}

export class BytecodePosition {
  bytecodePosition: number;
  inliningId: number;

  constructor(bytecodePosition: number, inliningId: number) {
    this.bytecodePosition = bytecodePosition;
    this.inliningId = inliningId;
  }

  public isValid(): boolean {
    return typeof this.bytecodePosition !== undefined;
  }

  public toString(): string {
    return `BCP:${this.inliningId}:${this.bytecodePosition}`;
  }
}

export class PositionsContainer {
  nodeIdToSourcePositionMap: Array<SourcePosition>;
  nodeIdToBytecodePositionMap: Array<BytecodePosition>;
  sourcePositionToNodes: Map<string, Array<string>>;
  bytecodePositionToNodes: Map<string, Array<string>>;

  constructor() {
    this.nodeIdToSourcePositionMap = new Array<SourcePosition>();
    this.nodeIdToBytecodePositionMap = new Array<BytecodePosition>();
    this.sourcePositionToNodes = new Map<string, Array<string>>();
    this.bytecodePositionToNodes = new Map<string, Array<string>>();
  }

  public addSourcePosition(nodeIdentifier: string, sourcePosition: SourcePosition): void {
    this.nodeIdToSourcePositionMap[nodeIdentifier] = sourcePosition;
    const key = sourcePosition.toString();
    if (!this.sourcePositionToNodes.has(key)) {
      this.sourcePositionToNodes.set(key, new Array<string>());
    }
    const nodes = this.sourcePositionToNodes.get(key);
    if (!nodes.includes(nodeIdentifier)) nodes.push(nodeIdentifier);
  }

  public addBytecodePosition(nodeIdentifier: string, bytecodePosition: BytecodePosition): void {
    this.nodeIdToBytecodePositionMap[nodeIdentifier] = bytecodePosition;
    const key = bytecodePosition.toString();
    if (!this.bytecodePositionToNodes.has(key)) {
      this.bytecodePositionToNodes.set(key, new Array<string>());
    }
    const nodes = this.bytecodePositionToNodes.get(key);
    if (!nodes.includes(nodeIdentifier)) nodes.push(nodeIdentifier);
  }

  public merge(nodes: Array<TurboshaftGraphNode>, replacements: Map<number, number>): void {
    for (const node of nodes) {
      const sourcePosition = node.sourcePosition;
      const bytecodePosition = node.bytecodePosition;
      const nodeId = replacements.has(node.id) ? replacements.get(node.id) : node.id;
      if (sourcePosition && !this.nodeIdToSourcePositionMap[nodeId]) {
        this.addSourcePosition(String(nodeId), sourcePosition);
      }
      if (bytecodePosition && !this.nodeIdToBytecodePositionMap[nodeId]) {
        this.addBytecodePosition(String(nodeId), bytecodePosition);
      }
    }
  }
}
