// 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 {Time, duration, time} from '../../base/time';
import {Engine} from '../../trace_processor/engine';
import {Trace} from '../../public/trace';
import {
  LONG,
  LONG_NULL,
  NUM,
  NUM_NULL,
} from '../../trace_processor/query_result';
import {TrackEventDetailsPanel} from '../../public/details_panel';
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 {TrackEventSelection} from '../../public/selection';
import {hasArgs, renderArguments} from '../../components/details/args';
import {asArgSetId} from '../../components/sql_utils/core_types';
import {ArgsDict, getArgs} from '../../components/sql_utils/args';

interface CounterDetails {
  // The "left" timestamp of the counter sample T(N)
  ts: time;

  // The delta between this sample and the next one's timestamps T(N+1) - T(N)
  duration: duration;

  // The value of the counter sample F(N)
  value: number;

  // The delta between this sample's value and the previous one F(N) - F(N-1)
  delta: number;

  args?: ArgsDict;
}

export class CounterDetailsPanel implements TrackEventDetailsPanel {
  private readonly trace: Trace;
  private readonly engine: Engine;
  private readonly trackId: number;
  private readonly rootTable: string;
  private readonly trackName: string;
  private counterDetails?: CounterDetails;

  constructor(
    trace: Trace,
    trackId: number,
    trackName: string,
    rootTable = 'counter',
  ) {
    this.trace = trace;
    this.engine = trace.engine;
    this.trackId = trackId;
    this.trackName = trackName;
    this.rootTable = rootTable;
  }

  async load({eventId}: TrackEventSelection) {
    this.counterDetails = await loadCounterDetails(
      this.engine,
      this.trackId,
      eventId,
      this.rootTable,
    );
  }

  render() {
    const counterInfo = this.counterDetails;
    if (counterInfo) {
      const args =
        hasArgs(counterInfo.args) &&
        m(
          Section,
          {title: 'Arguments'},
          m(Tree, renderArguments(this.trace, counterInfo.args)),
        );

      return m(
        DetailsShell,
        {title: 'Counter', description: `${this.trackName}`},
        m(
          GridLayout,
          m(
            Section,
            {title: 'Properties'},
            m(
              Tree,
              m(TreeNode, {left: 'Name', right: `${this.trackName}`}),
              m(TreeNode, {
                left: 'Start time',
                right: m(Timestamp, {trace: this.trace, ts: counterInfo.ts}),
              }),
              m(TreeNode, {
                left: 'Value',
                right: `${counterInfo.value.toLocaleString()}`,
              }),
              m(TreeNode, {
                left: 'Delta',
                right: `${counterInfo.delta.toLocaleString()}`,
              }),
              m(TreeNode, {
                left: 'Duration',
                right: m(DurationWidget, {
                  trace: this.trace,
                  dur: counterInfo.duration,
                }),
              }),
            ),
          ),
          args,
        ),
      );
    } else {
      return m(DetailsShell, {title: 'Counter', description: 'Loading...'});
    }
  }

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

async function loadCounterDetails(
  engine: Engine,
  trackId: number,
  id: number,
  rootTable: string,
): Promise<CounterDetails> {
  const query = `
    WITH CURRENT AS (
      SELECT
        id,
        ts,
        value,
        arg_set_id
      FROM ${rootTable}
      WHERE track_id = ${trackId} and id = ${id}
    ),
    PREV as (
      SELECT
        value
      FROM ${rootTable}
      WHERE track_id = ${trackId} AND ts < (select ts from CURRENT)
      ORDER BY ts DESC
      LIMIT 1
    ),
    NEXT as (
      SELECT
        ts
      FROM ${rootTable}
      WHERE track_id = ${trackId} AND ts > (select ts from CURRENT)
      ORDER BY ts ASC
      LIMIT 1
    )
    SELECT
      id,
      ts as leftTs,
      value,
      arg_set_id as argSetId,
      (SELECT value FROM PREV) as prevValue,
      (SELECT ts FROM NEXT) as rightTs
    FROM CURRENT
  `;

  const counter = await engine.query(query);
  const row = counter.iter({
    value: NUM,
    prevValue: NUM_NULL,
    leftTs: LONG,
    rightTs: LONG_NULL,
    argSetId: NUM_NULL,
  });
  const value = row.value;
  const leftTs = Time.fromRaw(row.leftTs);
  const rightTs = row.rightTs !== null ? Time.fromRaw(row.rightTs) : leftTs;
  const prevValue = row.prevValue !== null ? row.prevValue : value;

  const delta = value - prevValue;
  const duration = rightTs - leftTs;
  const argSetId = row.argSetId;
  const args =
    argSetId == null ? undefined : await getArgs(engine, asArgSetId(argSetId));
  return {ts: leftTs, value, delta, duration, args};
}
