// 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 {copyToClipboard} from '../../base/clipboard';
import {isString} from '../../base/object_utils';
import {Icons} from '../../base/semantic_icons';
import {sqliteString} from '../../base/string_utils';
import {Duration, Time} from '../../base/time';
import {Row, SqlValue} from '../../trace_processor/query_result';
import {Anchor} from '../../widgets/anchor';
import {renderError} from '../../widgets/error';
import {MenuItem, PopupMenu2} from '../../widgets/menu';
import {SliceRef} from '../sql/slice';
import {asSliceSqlId} from '../sql_types';
import {sqlValueToString} from '../sql_utils';
import {DurationWidget} from '../widgets/duration';
import {Timestamp} from '../widgets/timestamp';

import {Column} from './column';
import {SqlTableState} from './state';
import {SliceIdDisplayConfig} from './table_description';

// This file is responsible for rendering a value in a given sell based on the
// column type.

function filterOptionMenuItem(
    label: string, filter: string, state: SqlTableState): m.Child {
  return m(MenuItem, {
    label,
    onclick: () => {
      state.addFilter(filter);
    },
  });
}

function getStandardFilters(
    c: Column, value: SqlValue, state: SqlTableState): m.Child[] {
  if (value === null) {
    return [
      filterOptionMenuItem('is null', `${c.expression} is null`, state),
      filterOptionMenuItem('is not null', `${c.expression} is not null`, state),
    ];
  }
  if (isString(value)) {
    return [
      filterOptionMenuItem(
          'equals to', `${c.expression} = ${sqliteString(value)}`, state),
      filterOptionMenuItem(
          'not equals to', `${c.expression} != ${sqliteString(value)}`, state),
    ];
  }
  if (typeof value === 'bigint' || typeof value === 'number') {
    return [
      filterOptionMenuItem('equals to', `${c.expression} = ${value}`, state),
      filterOptionMenuItem(
          'not equals to', `${c.expression} != ${value}`, state),
      filterOptionMenuItem('greater than', `${c.expression} > ${value}`, state),
      filterOptionMenuItem(
          'greater or equals than', `${c.expression} >= ${value}`, state),
      filterOptionMenuItem('less than', `${c.expression} < ${value}`, state),
      filterOptionMenuItem(
          'less or equals than', `${c.expression} <= ${value}`, state),
    ];
  }
  return [];
}

function displayValue(value: SqlValue): m.Child {
  if (value === null) {
    return m('i', 'NULL');
  }
  return sqlValueToString(value);
}

function display(column: Column, row: Row): m.Children {
  const value = row[column.alias];
  return displayValue(value);
}

function copyMenuItem(label: string, value: string): m.Child {
  return m(MenuItem, {
    icon: Icons.Copy,
    label,
    onclick: () => {
      copyToClipboard(value);
    },
  });
}

function getContextMenuItems(
    column: Column, row: Row, state: SqlTableState): m.Child[] {
  const result: m.Child[] = [];
  const value = row[column.alias];

  if (isString(value)) {
    result.push(copyMenuItem('Copy', value));
  }

  const filters = getStandardFilters(column, value, state);
  if (filters.length > 0) {
    result.push(
        m(MenuItem, {label: 'Add filter', icon: Icons.Filter}, ...filters));
  }

  return result;
}

function renderStandardColumn(
    column: Column, row: Row, state: SqlTableState): m.Children {
  const displayValue = display(column, row);
  const contextMenuItems: m.Child[] = getContextMenuItems(column, row, state);
  return m(
      PopupMenu2,
      {
        trigger: m(Anchor, displayValue),
      },
      ...contextMenuItems,
  );
}

function renderTimestampColumn(
    column: Column, row: Row, state: SqlTableState): m.Children {
  const value = row[column.alias];
  if (typeof value !== 'bigint') {
    return renderStandardColumn(column, row, state);
  }

  return m(Timestamp, {
    ts: Time.fromRaw(value),
    extraMenuItems: getContextMenuItems(column, row, state),
  });
}

function renderDurationColumn(
    column: Column, row: Row, state: SqlTableState): m.Children {
  const value = row[column.alias];
  if (typeof value !== 'bigint') {
    return renderStandardColumn(column, row, state);
  }

  return m(DurationWidget, {
    dur: Duration.fromRaw(value),
    extraMenuItems: getContextMenuItems(column, row, state),
  });
}

function renderSliceIdColumn(
    column: {alias: string, display: SliceIdDisplayConfig},
    row: Row): m.Children {
  const config = column.display;
  const id = row[column.alias];
  const ts = row[config.ts];
  const dur = row[config.dur] === null ? -1n : row[config.dur];
  const trackId = row[config.trackId];

  const columnNotFoundError = (type: string, name: string) =>
      renderError(`${type} column ${name} not found`);
  const wrongTypeError = (type: string, name: string, value: SqlValue) =>
      renderError(`Wrong type for ${type} column ${name}: bigint expected, ${
          typeof value} found`);

  if (typeof id !== 'bigint') return sqlValueToString(id);
  if (ts === undefined) return columnNotFoundError('Timestamp', config.ts);
  if (typeof ts !== 'bigint') return wrongTypeError('timestamp', config.ts, ts);
  if (dur === undefined) return columnNotFoundError('Duration', config.dur);
  if (typeof dur !== 'bigint') {
    return wrongTypeError('duration', config.dur, ts);
  }
  if (trackId === undefined) return columnNotFoundError('Track id', trackId);
  if (typeof trackId !== 'bigint') {
    return wrongTypeError('track id', config.trackId, trackId);
  }

  return m(SliceRef, {
    id: asSliceSqlId(Number(id)),
    name: `${id}`,
    ts: Time.fromRaw(ts),
    dur: dur,
    sqlTrackId: Number(trackId),
    switchToCurrentSelectionTab: false,
  });
}

export function renderCell(
    column: Column, row: Row, state: SqlTableState): m.Children {
  if (column.display) {
    switch (column.display?.type) {
      case 'slice_id':
        return renderSliceIdColumn(
            {alias: column.alias, display: column.display}, row);
      case 'timestamp':
        return renderTimestampColumn(column, row, state);
      case 'duration':
      case 'thread_duration':
        return renderDurationColumn(column, row, state);
    }
  }
  return renderStandardColumn(column, row, state);
}
