// Copyright (C) 2025 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 type {Trace} from '../../public/trace';
import StandardGroupsPlugin from '../dev.perfetto.StandardGroups';
import type {PerfettoPlugin} from '../../public/plugin';
import {
  STR,
  LONG,
  UNKNOWN,
  LONG_NULL,
} from '../../trace_processor/query_result';
import {SourceDataset} from '../../trace_processor/dataset';
import SupportPlugin from '../com.android.AndroidLongBatterySupport';

const MODEM_RIL_STRENGTH = `
  DROP VIEW IF EXISTS ScreenOn;
  CREATE VIEW ScreenOn AS
  SELECT ts, dur FROM (
      SELECT
          ts, value,
          LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur
      FROM counter, track ON (counter.track_id = track.id)
      WHERE track.name = 'ScreenState'
  ) WHERE value = 2;

  DROP VIEW IF EXISTS RilSignalStrength;
  CREATE VIEW RilSignalStrength AS
  With RilMessages AS (
      SELECT
          ts, slice.name,
          LEAD(ts, 1, TRACE_END()) OVER (ORDER BY ts)-ts AS dur
      FROM slice, track
      ON (slice.track_id = track.id)
      WHERE track.name = 'RIL'
        AND slice.name GLOB 'UNSOL_SIGNAL_STRENGTH*'
  ),
  BandTypes(band_ril, band_name) AS (
      VALUES ("CellSignalStrengthLte:", "LTE"),
              ("CellSignalStrengthNr:", "NR")
  ),
  ValueTypes(value_ril, value_name) AS (
      VALUES ("rsrp=", "rsrp"),
              ("rssi=", "rssi")
  ),
  Extracted AS (
      SELECT ts, dur, band_name, value_name, (
          SELECT CAST(SUBSTR(key_str, start_idx+1, end_idx-start_idx-1) AS INT64) AS value
          FROM (
              SELECT key_str, INSTR(key_str, "=") AS start_idx, INSTR(key_str, " ") AS end_idx
              FROM (
                  SELECT SUBSTR(band_str, INSTR(band_str, value_ril)) AS key_str
                  FROM (SELECT SUBSTR(name, INSTR(name, band_ril)) AS band_str)
              )
          )
      ) AS value
      FROM RilMessages
      JOIN BandTypes
      JOIN ValueTypes
  )
  SELECT
  ts, dur, band_name, value_name, value,
  value_name || "=" || IIF(value = 2147483647, "unknown", ""||value) AS name,
  ROW_NUMBER() OVER (ORDER BY ts) as id,
  DENSE_RANK() OVER (ORDER BY band_name, value_name) AS track_id
  FROM Extracted;

  DROP TABLE IF EXISTS RilScreenOn;
  CREATE VIRTUAL TABLE RilScreenOn
  USING SPAN_JOIN(RilSignalStrength PARTITIONED track_id, ScreenOn)`;

const MODEM_RIL_CHANNELS_PREAMBLE = `
  CREATE OR REPLACE PERFETTO FUNCTION EXTRACT_KEY_VALUE(source STRING, key_name STRING) RETURNS STRING AS
  SELECT SUBSTR(trimmed, INSTR(trimmed, "=")+1, INSTR(trimmed, ",") - INSTR(trimmed, "=") - 1)
  FROM (SELECT SUBSTR($source, INSTR($source, $key_name)) AS trimmed);`;

const MODEM_RIL_CHANNELS_DATASET = new SourceDataset({
  src: `
    With RawChannelConfig AS (
        SELECT ts, slice.name AS raw_config
        FROM slice, track
        ON (slice.track_id = track.id)
        WHERE track.name = 'RIL'
        AND slice.name LIKE 'UNSOL_PHYSICAL_CHANNEL_CONFIG%'
    ),
    Attributes(attribute, attrib_name) AS (
        VALUES ("mCellBandwidthDownlinkKhz", "downlink"),
            ("mCellBandwidthUplinkKhz", "uplink"),
            ("mNetworkType", "network"),
            ("mBand", "band")
    ),
    Slots(idx, slot_name) AS (
        VALUES (0, "primary"),
            (1, "secondary 1"),
            (2, "secondary 2")
    ),
    Stage1 AS (
        SELECT *, IFNULL(EXTRACT_KEY_VALUE(STR_SPLIT(raw_config, "}, {", idx), attribute), "") AS name
        FROM RawChannelConfig
        JOIN Attributes
        JOIN Slots
    ),
    Stage2 AS (
        SELECT *, LAG(name) OVER (PARTITION BY idx, attribute ORDER BY ts) AS last_name
        FROM Stage1
    ),
    Stage3 AS (
        SELECT *, LEAD(ts, 1, TRACE_END()) OVER (PARTITION BY idx, attribute ORDER BY ts) - ts AS dur
        FROM Stage2 WHERE name != last_name
    )
    SELECT ts, dur, slot_name || "-" || attrib_name || "=" || name AS name
    FROM Stage3
  `,
  schema: {
    ts: LONG,
    dur: LONG_NULL,
    name: STR,
  },
});

const MODEM_CELL_RESELECTION_DATASET = new SourceDataset({
  src: `
    with base as (
      select
          ts,
          s.name as raw_ril,
          ifnull(str_split(str_split(s.name, 'CellIdentityLte{', 1), ', operatorNames', 0),
              str_split(str_split(s.name, 'CellIdentityNr{', 1), ', operatorNames', 0)) as cell_id
      from track t join slice s on t.id = s.track_id
      where t.name = 'RIL' and s.name like '%DATA_REGISTRATION_STATE%'
    ),
    base2 as (
      select
          ts,
          raw_ril,
          case
              when cell_id like '%earfcn%' then 'LTE ' || cell_id
              when cell_id like '%nrarfcn%' then 'NR ' || cell_id
              when cell_id is null then 'Unknown'
              else cell_id
          end as cell_id
      from base
    ),
    base3 as (
      select ts, cell_id , lag(cell_id) over (order by ts) as lag_cell_id, raw_ril
      from base2
    )
    select ts, 0 as dur, cell_id as name, raw_ril
    from base3
    where cell_id != lag_cell_id
    order by ts
  `,
  schema: {
    ts: LONG,
    dur: LONG_NULL,
    name: STR,
    raw_ril: UNKNOWN,
  },
});

export default class implements PerfettoPlugin {
  static readonly id = 'com.android.AndroidRil';
  static readonly dependencies = [StandardGroupsPlugin, SupportPlugin];

  private support(ctx: Trace) {
    return ctx.plugins.getPlugin(SupportPlugin);
  }

  async onTraceLoad(ctx: Trace): Promise<void> {
    const e = ctx.engine;
    const support = this.support(ctx);
    const features = await support.features(e);
    if (!features.has('track.ril')) {
      return;
    }

    const groupName = 'Modem Detail';

    const rilStrength = async (band: string, value: string) =>
      await support.addSliceTrack(
        ctx,
        `Modem signal strength ${band} ${value}`,
        new SourceDataset({
          src: `
            SELECT
              ts,
              dur,
              name
            FROM RilScreenOn
            WHERE band_name = '${band}' AND value_name = '${value}'
          `,
          schema: {
            ts: LONG,
            dur: LONG_NULL,
            name: STR,
          },
        }),
        groupName,
      );

    await e.query(MODEM_RIL_STRENGTH);
    await e.query(MODEM_RIL_CHANNELS_PREAMBLE);

    await rilStrength('LTE', 'rsrp');
    await rilStrength('LTE', 'rssi');
    await rilStrength('NR', 'rsrp');
    await rilStrength('NR', 'rssi');

    await support.addSliceTrack(
      ctx,
      'Modem channel config',
      MODEM_RIL_CHANNELS_DATASET,
      groupName,
    );

    await support.addSliceTrack(
      ctx,
      'Modem cell reselection',
      MODEM_CELL_RESELECTION_DATASET,
      groupName,
    );
  }
}
