// 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 m from 'mithril';
import {LONG, NUM, STR, STR_NULL} from '../../trace_processor/query_result';
import type {Trace} from '../../public/trace';
import {SliceTrack} from '../../components/tracks/slice_track';
import {SourceDataset} from '../../trace_processor/dataset';
import {makeColorScheme} from '../../components/colorizer';
import {HSLColor} from '../../base/color';
import {Section} from '../../widgets/section';
import {Tree, TreeNode} from '../../widgets/tree';
import {Timestamp} from '../../components/widgets/timestamp';
import {Time} from '../../base/time';
import {DetailsShell} from '../../widgets/details_shell';
import {GridLayout, GridLayoutColumn} from '../../widgets/grid_layout';
import {Spinner} from '../../widgets/spinner';
import type {ColorScheme} from '../../base/color_scheme';

const PRIO_TO_COLOR: Record<number, ColorScheme> = {
  2: makeColorScheme(new HSLColor({h: 0, s: 0, l: 60})), // V - grey
  3: makeColorScheme(new HSLColor({h: 217, s: 100, l: 60})), // D - blue
  4: makeColorScheme(new HSLColor({h: 120, s: 62, l: 55})), // I - green
  5: makeColorScheme(new HSLColor({h: 38, s: 100, l: 58})), // W - amber
  6: makeColorScheme(new HSLColor({h: 0, s: 86, l: 60})), // E - red
  7: makeColorScheme(new HSLColor({h: 268, s: 100, l: 65})), // F - magenta
};
const DEFAULT_PRIO_COLOR = makeColorScheme(new HSLColor({h: 0, s: 0, l: 60}));

const EVT_PX = 6; // Width of an event tick in pixels.

const LOGS_SQL = `
  select
    id,
    ts,
    prio,
    utid,
    tag,
    msg,
    CASE
      WHEN prio <= 3 THEN 0
      WHEN prio = 4 THEN 1
      WHEN prio = 5 THEN 2
      WHEN prio = 6 THEN 3
      WHEN prio = 7 THEN 4
      ELSE -1
    END as depth
  from android_logs
  order by ts
`;

const LOGS_SCHEMA = {
  id: NUM,
  ts: LONG,
  prio: NUM,
  utid: NUM,
  depth: NUM,
  tag: STR_NULL,
  msg: STR_NULL,
};

const PRIO_LABELS: Record<number, string> = {
  2: 'Verbose',
  3: 'Debug',
  4: 'Info',
  5: 'Warn',
  6: 'Error',
  7: 'Fatal',
};

function makeDetailsPanel(
  trace: Trace,
  row: {
    id: number;
    ts: bigint;
    prio: number;
    tag: string | null;
    utid: number;
  },
) {
  // The msg is initially undefined, it'll be filled in when it loads
  let msg: string | undefined;
  let threadInfo: string | undefined;

  // Quickly load the log message
  trace.engine
    .query(`select msg from android_logs where id = ${row.id}`)
    .then((result) => {
      msg = result.maybeFirstRow({msg: STR})?.msg;
    });

  trace.engine
    .query(`select tid, name from thread where utid = ${row.utid}`)
    .then((result) => {
      const r = result.maybeFirstRow({tid: NUM, name: STR_NULL});
      threadInfo = r
        ? r.name
          ? `${r.name} [${r.tid}]`
          : `${r.tid}`
        : `utid=${row.utid}`;
    });

  const prioLabel = PRIO_LABELS[row.prio] ?? `${row.prio}`;

  return {
    render() {
      return m(
        DetailsShell,
        {title: `Android Log`},
        m(
          GridLayout,
          m(
            GridLayoutColumn,
            m(
              Section,
              {title: 'Details'},
              m(
                Tree,
                m(TreeNode, {left: 'ID', right: row.id}),
                m(TreeNode, {
                  left: 'Timestamp',
                  right: m(Timestamp, {trace, ts: Time.fromRaw(row.ts)}),
                }),
                m(TreeNode, {left: 'Priority', right: prioLabel}),
                m(TreeNode, {left: 'Tag', right: row.tag}),
                m(TreeNode, {
                  left: 'Thread',
                  right: threadInfo ?? m(Spinner),
                }),
                m(TreeNode, {
                  left: 'Message',
                  right: msg !== undefined ? msg : m(Spinner),
                }),
              ),
            ),
          ),
        ),
      );
    },
  };
}

export function createAndroidLogTrack(trace: Trace, uri: string) {
  return SliceTrack.create({
    trace,
    uri,
    dataset: new SourceDataset({src: LOGS_SQL, schema: LOGS_SCHEMA}),
    initialMaxDepth: 4,
    colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
    tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
    // All log events are instant events, render them as a little box rather
    // than the default chevron.
    instantStyle: {
      width: EVT_PX,
      render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
    },
    // Make rows a little more compact.
    sliceLayout: {
      padding: 2,
      sliceHeight: 7,
    },
    detailsPanel: (row) => makeDetailsPanel(trace, row),
  });
}

export function createPerProcessLogTrack(
  trace: Trace,
  uri: string,
  utids: number[],
) {
  return SliceTrack.create({
    trace,
    uri,
    dataset: new SourceDataset({
      src: LOGS_SQL,
      schema: LOGS_SCHEMA,
      filter: {col: 'utid', in: utids},
    }),
    initialMaxDepth: 4,
    colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
    tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
    instantStyle: {
      width: EVT_PX,
      render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
    },
    sliceLayout: {
      padding: 2,
      sliceHeight: 7,
    },
    detailsPanel: (row) => makeDetailsPanel(trace, row),
  });
}

export function createPerThreadLogTrack(
  trace: Trace,
  uri: string,
  utid: number,
) {
  return SliceTrack.create({
    trace,
    uri,
    rootTableName: 'android_logs',
    dataset: new SourceDataset({
      src: LOGS_SQL,
      schema: LOGS_SCHEMA,
      filter: {col: 'utid', eq: utid},
    }),
    initialMaxDepth: 4,
    colorizer: (row) => PRIO_TO_COLOR[row.prio] ?? DEFAULT_PRIO_COLOR,
    tooltip: (slice) => [m('', m('b', slice.row.tag)), m('', slice.row.msg)],
    instantStyle: {
      width: EVT_PX,
      render: (ctx, r) => ctx.fillRect(r.x, r.y, r.width, r.height),
    },
    sliceLayout: {
      padding: 2,
      sliceHeight: 7,
    },
    detailsPanel: (row) => makeDetailsPanel(trace, row),
  });
}
