// 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 m from 'mithril';

import {BigintMath} from '../base/bigint_math';
import {sqliteString} from '../base/string_utils';
import {duration, time} from '../base/time';
import {exists} from '../base/utils';
import {Anchor} from '../widgets/anchor';
import {MenuItem, PopupMenu2} from '../widgets/menu';
import {Section} from '../widgets/section';
import {SqlRef} from '../widgets/sql_ref';
import {Tree, TreeNode} from '../widgets/tree';

import {addTab} from './bottom_tab';
import {globals} from './globals';
import {SliceDetails} from './sql/slice';
import {
  BreakdownByThreadState,
  BreakdownByThreadStateTreeNode,
} from './sql/thread_state';
import {SqlTableTab} from './sql_table/tab';
import {SqlTables} from './sql_table/well_known_tables';
import {getProcessName, getThreadName} from './thread_and_process_info';
import {DurationWidget} from './widgets/duration';
import {Timestamp} from './widgets/timestamp';

function computeDuration(ts: time, dur: duration): m.Children {
  if (dur === -1n) {
    const minDuration = globals.state.traceTime.end - ts;
    return [m(DurationWidget, {dur: minDuration}), ' (Did not end)'];
  } else {
    return m(DurationWidget, {dur});
  }
}

// Renders a widget storing all of the generic details for a slice from the
// slice table.
export function renderDetails(
    slice: SliceDetails, durationBreakdown?: BreakdownByThreadState) {
  return m(
      Section,
      {title: 'Details'},
      m(
          Tree,
          m(TreeNode, {
            left: 'Name',
            right: m(
                PopupMenu2,
                {
                  trigger: m(Anchor, slice.name),
                },
                m(MenuItem, {
                  label: 'Slices with the same name',
                  onclick: () => {
                    addTab({
                      kind: SqlTableTab.kind,
                      config: {
                        table: SqlTables.slice,
                        displayName: 'slice',
                        filters: [`name = ${sqliteString(slice.name)}`],
                      },
                    });
                  },
                }),
                ),
          }),
          m(TreeNode, {
            left: 'Category',
            right: !slice.category || slice.category === '[NULL]' ?
                'N/A' :
                slice.category,
          }),
          m(TreeNode, {
            left: 'Start time',
            right: m(Timestamp, {ts: slice.ts}),
          }),
          exists(slice.absTime) &&
              m(TreeNode, {left: 'Absolute Time', right: slice.absTime}),
          m(
              TreeNode,
              {
                left: 'Duration',
                right: computeDuration(slice.ts, slice.dur),
              },
              exists(durationBreakdown) && slice.dur > 0 &&
                  m(BreakdownByThreadStateTreeNode, {
                    data: durationBreakdown,
                    dur: slice.dur,
                  }),
              ),
          renderThreadDuration(slice),
          slice.thread && m(TreeNode, {
            left: 'Thread',
            right: getThreadName(slice.thread),
          }),
          slice.process && m(TreeNode, {
            left: 'Process',
            right: getProcessName(slice.process),
          }),
          slice.process && exists(slice.process.uid) && m(TreeNode, {
            left: 'User ID',
            right: slice.process.uid,
          }),
          slice.process && slice.process.packageName && m(TreeNode, {
            left: 'Package name',
            right: slice.process.packageName,
          }),
          slice.process && exists(slice.process.versionCode) && m(TreeNode, {
            left: 'Version code',
            right: slice.process.versionCode,
          }),
          m(TreeNode, {
            left: 'SQL ID',
            right: m(SqlRef, {table: 'slice', id: slice.id}),
          }),
          ));
}

function renderThreadDuration(sliceInfo: SliceDetails) {
  if (exists(sliceInfo.threadTs) && exists(sliceInfo.threadDur)) {
    // If we have valid thread duration, also display a percentage of
    // |threadDur| compared to |dur|.
    const ratio = BigintMath.ratio(sliceInfo.threadDur, sliceInfo.dur);
    const threadDurFractionSuffix =
        sliceInfo.threadDur === -1n ? '' : ` (${(ratio * 100).toFixed(2)}%)`;
    return m(TreeNode, {
      left: 'Thread duration',
      right: [
        computeDuration(sliceInfo.threadTs, sliceInfo.threadDur),
        threadDurFractionSuffix,
      ],
    });
  } else {
    return undefined;
  }
}
