// Copyright (C) 2023 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {exists} from '../../base/utils';
import type {Engine} from '../../trace_processor/engine';
import {STR} from '../../trace_processor/query_result';

export enum CauseProcess {
  UNKNOWN,
  BROWSER = 'Browser',
  RENDERER = 'Renderer',
  GPU = 'GPU',
}

export enum CauseThread {
  UNKNOWN,
  BROWSER_MAIN = 'CrBrowserMain',
  RENDERER_MAIN = 'CrRendererMain',
  COMPOSITOR = 'Compositor',
  CHROME_CHILD_IO_THREAD = 'Chrome_ChildIOThread',
  VIZ_COMPOSITOR = 'VizCompositorThread',
  SURFACE_FLINGER = 'surfaceflinger',
}

export interface ScrollJankCause {
  description: string;
  process: CauseProcess;
  thread: CauseThread;
}

export interface EventLatencyStageDetails {
  description: string;
  jankCauses: ScrollJankCause[];
}

export interface ScrollJankCauseMapInternal {
  // Key corresponds with the EventLatency stage.
  [key: string]: EventLatencyStageDetails;
}

function getScrollJankProcess(process: string): CauseProcess {
  switch (process) {
    case CauseProcess.BROWSER:
      return CauseProcess.BROWSER;
    case CauseProcess.RENDERER:
      return CauseProcess.RENDERER;
    case CauseProcess.GPU:
      return CauseProcess.GPU;
    default:
      return CauseProcess.UNKNOWN;
  }
}

function getScrollJankThread(thread: string): CauseThread {
  switch (thread) {
    case CauseThread.BROWSER_MAIN:
      return CauseThread.BROWSER_MAIN;
    case CauseThread.RENDERER_MAIN:
      return CauseThread.RENDERER_MAIN;
    case CauseThread.CHROME_CHILD_IO_THREAD:
      return CauseThread.CHROME_CHILD_IO_THREAD;
    case CauseThread.COMPOSITOR:
      return CauseThread.COMPOSITOR;
    case CauseThread.VIZ_COMPOSITOR:
      return CauseThread.VIZ_COMPOSITOR;
    case CauseThread.SURFACE_FLINGER:
      return CauseThread.SURFACE_FLINGER;
    default:
      return CauseThread.UNKNOWN;
  }
}

export class ScrollJankCauseMap {
  private static instance: ScrollJankCauseMap;
  private causes: ScrollJankCauseMapInternal;

  private constructor() {
    this.causes = {};
  }

  private async initializeCauseMap(engine: Engine) {
    const queryResult = await engine.query(`
      INCLUDE PERFETTO MODULE chrome.scroll_jank.scroll_jank_cause_map;

      SELECT
        IFNULL(name, '') AS name,
        IFNULL(description, '') AS description,
        IFNULL(cause_process, '') AS causeProcess,
        IFNULL(cause_thread, '') AS causeThread,
        IFNULL(cause_description, '') AS causeDescription
      FROM chrome_scroll_jank_causes_with_event_latencies;
    `);

    const iter = queryResult.iter({
      name: STR,
      description: STR,
      causeProcess: STR,
      causeThread: STR,
      causeDescription: STR,
    });

    for (; iter.valid(); iter.next()) {
      const eventLatencyStage = iter.name;
      if (!(eventLatencyStage in this.causes)) {
        this.causes[eventLatencyStage] = {
          description: iter.description,
          jankCauses: [] as ScrollJankCause[],
        };
      }

      const causeProcess = getScrollJankProcess(iter.causeProcess);
      const causeThread = getScrollJankThread(iter.causeThread);

      this.causes[eventLatencyStage].jankCauses.push({
        description: iter.causeDescription,
        process: causeProcess,
        thread: causeThread,
      });
    }
  }

  // Must be called before this item is accessed, as the object is populated
  // from SQL data.
  public static async initialize(engine: Engine) {
    if (!exists(ScrollJankCauseMap.instance)) {
      ScrollJankCauseMap.instance = new ScrollJankCauseMap();
      await ScrollJankCauseMap.instance.initializeCauseMap(engine);
    }
  }

  public static getEventLatencyDetails(
    eventLatency: string | undefined,
  ): EventLatencyStageDetails | undefined {
    if (eventLatency && eventLatency in ScrollJankCauseMap.instance.causes) {
      return ScrollJankCauseMap.instance.causes[eventLatency];
    }
    return undefined;
  }
}
