import { Logger } from "@perf-profiler/logger";
import { POLLING_INTERVAL } from "@perf-profiler/types";

export const parseLine = (
  line: string
): {
  timestamp: number;
  ending: boolean;
  methodName: string | undefined;
} => {
  let regexMatching = line.match(/ (\d+\.\d+): tracing_mark_write: ([A-Z])(.*)/);

  if (!regexMatching) {
    regexMatching = line.match(/ (\d+\.\d+): (.*)/);

    if (!regexMatching) {
      throw new Error(`Could not parse ATrace line "${line}"`);
    }
  }

  const [, timestamp, beginOrEnd, methodName] = regexMatching;

  return {
    timestamp: parseFloat(timestamp) * 1000,
    ending: beginOrEnd === "E",
    methodName,
  };
};

// At some point we might want to change this to adapt to 90fps or 120fps devices
const TARGET_FRAME_RATE = 60;
const TARGET_FRAME_TIME = 1000 / TARGET_FRAME_RATE;

export class FrameTimeParser {
  private methodStartedCount = 0;
  private doFrameStartedTimeStamp: number | null = null;

  getFrameTimes(
    output: string,
    pid: string
  ): {
    frameTimes: number[];
    interval: number;
  } {
    const lines = output.split(/\r\n|\n|\r/).filter(Boolean);

    if (lines.length === 0)
      return {
        frameTimes: [],
        interval: POLLING_INTERVAL,
      };

    const frameTimes: number[] = [];

    lines.forEach((line) => {
      try {
        if (!line.includes("-" + pid + " ")) return;

        const { timestamp, ending, methodName } = parseLine(line);

        if (ending) {
          this.methodStartedCount--;
          if (this.methodStartedCount <= 0) {
            if (this.doFrameStartedTimeStamp) {
              frameTimes.push(timestamp - this.doFrameStartedTimeStamp);
              this.doFrameStartedTimeStamp = null;
            }

            this.methodStartedCount = 0;
          }
        } else {
          if (methodName) {
            if (methodName.includes("Choreographer#doFrame")) {
              this.methodStartedCount = 1;
              this.doFrameStartedTimeStamp = timestamp;
            } else {
              this.methodStartedCount++;
            }
          }
        }
      } catch (error) {
        Logger.error(`Failed to parse Atrace line:
${line}

Error:
${error instanceof Error ? error.message : error}`);
      }
    });

    return {
      frameTimes,
      interval: parseLine(lines[lines.length - 1]).timestamp - parseLine(lines[0]).timestamp,
    };
  }

  static getFps(frameTimes: number[], timeInterval: number, uiCpuUsage: number) {
    const frameCount = frameTimes.length;

    const totalFrameTime = frameTimes.reduce(
      (sum, time) => sum + Math.max(TARGET_FRAME_TIME, time),
      0
    );

    /**
     * This is an approximation of idle time. When the user is not doing anything
     * and the app is not drawing any frames, we aim to set the value to 60FPS
     *
     * In RN apps, Choreographer#doFrame is always called, but for other apps
     * it might not be called, either because UI thread is too busy or because
     * the app is just not doing anything
     */
    const idleTime = (timeInterval - totalFrameTime) * (1 - uiCpuUsage / 100);
    // We add frame count in idle time as if we were running at 60fps still
    const idleTimeFrameCount = (idleTime / 1000) * TARGET_FRAME_RATE;

    const fps = ((frameCount + idleTimeFrameCount) / timeInterval) * 1000;

    return Math.max(0, Math.min(TARGET_FRAME_RATE, fps));
  }
}
