// Copyright (C) 2024 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 {Duration, type duration, Time, type time} from '../../base/time';
import {LONG, NUM, STR_NULL} from '../../trace_processor/query_result';
import m from 'mithril';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout} from '../../widgets/grid_layout';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
import {Timestamp} from '../../components/widgets/timestamp';
import {DurationWidget} from '../../components/widgets/duration';
import {Anchor} from '../../widgets/anchor';
import type {Engine} from '../../trace_processor/engine';
import type {TrackEventDetailsPanel} from '../../public/details_panel';
import type {TrackEventSelection} from '../../public/selection';
import type {Trace} from '../../public/trace';
import type {ThreadMap} from '../dev.perfetto.Thread/threads';

interface SuspendResumeEventDetails {
  ts: time;
  dur: duration;
  utid: number;
  cpu: number;
  event_type: string;
  device_name: string;
  driver_name: string;
  callback_phase: string;
  thread_state_id: number;
}

export class SuspendResumeDetailsPanel implements TrackEventDetailsPanel {
  private suspendResumeEventDetails?: SuspendResumeEventDetails;

  constructor(
    private readonly trace: Trace,
    private readonly threads: ThreadMap,
  ) {}

  async load({eventId}: TrackEventSelection) {
    this.suspendResumeEventDetails = await loadSuspendResumeEventDetails(
      this.trace.engine,
      eventId,
    );
  }

  render() {
    const eventDetails = this.suspendResumeEventDetails;
    if (eventDetails) {
      const threadInfo = this.threads.get(eventDetails.utid);
      if (!threadInfo) {
        return null;
      }
      return m(
        DetailsShell,
        {title: 'Suspend / Resume Event'},
        m(
          GridLayout,
          m(
            Section,
            {title: 'Properties'},
            m(
              Tree,
              m(TreeNode, {
                left: 'Device Name',
                right: eventDetails.device_name,
              }),
              m(TreeNode, {
                left: 'Start time',
                right: m(Timestamp, {trace: this.trace, ts: eventDetails.ts}),
              }),
              m(TreeNode, {
                left: 'Duration',
                right: m(DurationWidget, {
                  trace: this.trace,
                  dur: eventDetails.dur,
                }),
              }),
              m(TreeNode, {
                left: 'Driver Name',
                right: eventDetails.driver_name,
              }),
              m(TreeNode, {
                left: 'Callback Phase',
                right: eventDetails.callback_phase,
              }),
              m(TreeNode, {
                left: 'Thread',
                right: m(
                  Anchor,
                  {
                    icon: 'call_made',
                    onclick: () => {
                      this.goToThread(eventDetails.thread_state_id);
                    },
                  },
                  `${threadInfo.threadName} [${threadInfo.tid}]`,
                ),
              }),
              m(TreeNode, {left: 'CPU', right: eventDetails.cpu}),
              m(TreeNode, {left: 'Event Type', right: eventDetails.event_type}),
            ),
          ),
        ),
      );
    } else {
      return m(DetailsShell, {
        title: 'Suspend / Resume Event',
        description: 'Loading...',
      });
    }
  }

  isLoading(): boolean {
    return this.suspendResumeEventDetails === undefined;
  }

  goToThread(threadStateId: number) {
    this.trace.selection.selectSqlEvent('thread_state', threadStateId, {
      scrollToSelection: true,
    });
  }
}

async function loadSuspendResumeEventDetails(
  engine: Engine,
  id: number,
): Promise<SuspendResumeEventDetails> {
  const suspendResumeDetailsQuery = `
    SELECT
      ts,
      dur,
      EXTRACT_ARG(arg_set_id, 'utid') as utid,
      EXTRACT_ARG(arg_set_id, 'cpu') as cpu,
      EXTRACT_ARG(arg_set_id, 'event_type') as event_type,
      EXTRACT_ARG(arg_set_id, 'device_name') as device_name,
      EXTRACT_ARG(arg_set_id, 'driver_name') as driver_name,
      EXTRACT_ARG(arg_set_id, 'callback_phase') as callback_phase
    FROM slice
    WHERE slice_id = ${id};
  `;

  const suspendResumeDetailsResult = await engine.query(
    suspendResumeDetailsQuery,
  );
  const suspendResumeEventRow = suspendResumeDetailsResult.iter({
    ts: LONG,
    dur: LONG,
    utid: NUM,
    cpu: NUM,
    event_type: STR_NULL,
    device_name: STR_NULL,
    driver_name: STR_NULL,
    callback_phase: STR_NULL,
  });
  if (!suspendResumeEventRow.valid()) {
    return {
      ts: Time.fromRaw(0n),
      dur: Duration.fromRaw(0n),
      utid: 0,
      cpu: 0,
      event_type: 'Error',
      device_name: 'Error',
      driver_name: 'Error',
      callback_phase: 'Error',
      thread_state_id: 0,
    };
  }

  const threadStateQuery = `
    SELECT t.id as threadStateId
    FROM thread_state t
    WHERE
      t.utid = ${suspendResumeEventRow.utid}
      AND t.ts <= ${suspendResumeEventRow.ts}
      AND t.ts + t.dur > ${suspendResumeEventRow.ts};
  `;
  const threadStateResult = await engine.query(threadStateQuery);
  let threadStateId = 0;
  if (threadStateResult.numRows() > 0) {
    const threadStateRow = threadStateResult.firstRow({
      threadStateId: NUM,
    });
    threadStateId = threadStateRow.threadStateId;
  }

  return {
    ts: Time.fromRaw(suspendResumeEventRow.ts),
    dur: Duration.fromRaw(suspendResumeEventRow.dur),
    utid: suspendResumeEventRow.utid,
    cpu: suspendResumeEventRow.cpu,
    event_type:
      suspendResumeEventRow.event_type !== null
        ? suspendResumeEventRow.event_type
        : 'N/A',
    device_name:
      suspendResumeEventRow.device_name !== null
        ? suspendResumeEventRow.device_name
        : 'N/A',
    driver_name:
      suspendResumeEventRow.driver_name !== null
        ? suspendResumeEventRow.driver_name
        : 'N/A',
    callback_phase:
      suspendResumeEventRow.callback_phase !== null
        ? suspendResumeEventRow.callback_phase
        : 'N/A',
    thread_state_id: threadStateId,
  };
}
