// Copyright (C) 2019 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use size 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 * as m from 'mithril';

import {Actions} from '../common/actions';
import {drawDoubleHeadedArrow} from '../common/canvas_utils';
import {translateState} from '../common/thread_state';
import {timeToCode, toNs} from '../common/time';

import {globals, SliceDetails, ThreadDesc} from './globals';
import {PanelSize} from './panel';
import {scrollToTrackAndTs} from './scroll_helper';
import {SlicePanel} from './slice_panel';

export class SliceDetailsPanel extends SlicePanel {
  view() {
    const sliceInfo = globals.sliceDetails;
    if (sliceInfo.utid === undefined) return;
    const threadInfo = globals.threads.get(sliceInfo.utid);

    return m(
        '.details-panel',
        m('.details-panel-heading',
          m('h2.split', `Slice Details`),
          (sliceInfo.wakeupTs && sliceInfo.wakerUtid) ?
              m('h2.split', 'Scheduling Latency') :
              ''),
        this.getDetails(sliceInfo, threadInfo));
  }

  getDetails(sliceInfo: SliceDetails, threadInfo: ThreadDesc|undefined) {
    if (!threadInfo || sliceInfo.ts === undefined ||
        sliceInfo.dur === undefined) {
      return null;
    } else {
      const tableRows = [
        m('tr',
          m('th', `Process`),
          m('td', `${threadInfo.procName} [${threadInfo.pid}]`)),
        m('tr',
          m('th', `Thread`),
          m('td',
            `${threadInfo.threadName} [${threadInfo.tid}]`,
            m('i.material-icons.grey',
              {onclick: () => this.goToThread(), title: 'Go to thread'},
              'call_made'))),
        m('tr', m('th', `Cmdline`), m('td', threadInfo.cmdline)),
        m('tr', m('th', `Start time`), m('td', `${timeToCode(sliceInfo.ts)}`)),
        m('tr',
          m('th', `Duration`),
          m('td', this.computeDuration(sliceInfo.ts, sliceInfo.dur))),
        (sliceInfo.threadDur === undefined ||
         sliceInfo.threadTs === undefined) ?
            '' :
            m('tr',
              m('th', 'Thread duration'),
              m('td',
                this.computeDuration(sliceInfo.threadTs, sliceInfo.threadDur))),
        m('tr', m('th', `Prio`), m('td', `${sliceInfo.priority}`)),
        m('tr',
          m('th', `End State`),
          m('td', translateState(sliceInfo.endState))),
        m('tr',
          m('th', `Slice ID`),
          m('td', sliceInfo.id ? sliceInfo.id.toString() : 'Unknown')),
      ];

      for (const [key, value] of this.getProcessThreadDetails(sliceInfo)) {
        if (value !== undefined) {
          tableRows.push(m('tr', m('th', key), m('td', value)));
        }
      }

      return m(
          '.details-table',
          m('table.half-width-panel', tableRows),
      );
    }
  }

  goToThread() {
    const sliceInfo = globals.sliceDetails;
    if (sliceInfo.utid === undefined) return;
    const threadInfo = globals.threads.get(sliceInfo.utid);

    if (sliceInfo.id === undefined || sliceInfo.ts === undefined ||
        sliceInfo.dur === undefined || sliceInfo.cpu === undefined ||
        threadInfo === undefined) {
      return;
    }

    let trackId: string|number|undefined;
    for (const track of Object.values(globals.state.tracks)) {
      if (track.kind === 'ThreadStateTrack' &&
          (track.config as {utid: number}).utid === threadInfo.utid) {
        trackId = track.id;
      }
    }

    if (trackId && sliceInfo.threadStateId) {
      globals.makeSelection(Actions.selectThreadState({
        id: sliceInfo.threadStateId,
        trackId: trackId.toString(),
      }));

      scrollToTrackAndTs(
          trackId, toNs(sliceInfo.ts + globals.state.traceTime.startSec), true);
    }
  }


  renderCanvas(ctx: CanvasRenderingContext2D, size: PanelSize) {
    const details = globals.sliceDetails;
    // Show expanded details on the scheduling of the currently selected slice.
    if (details.wakeupTs && details.wakerUtid !== undefined) {
      const threadInfo = globals.threads.get(details.wakerUtid);
      // Draw diamond and vertical line.
      const startDraw = {x: size.width / 2 + 20, y: 52};
      ctx.beginPath();
      ctx.moveTo(startDraw.x, startDraw.y + 28);
      ctx.fillStyle = 'black';
      ctx.lineTo(startDraw.x + 6, startDraw.y + 20);
      ctx.lineTo(startDraw.x, startDraw.y + 12);
      ctx.lineTo(startDraw.x - 6, startDraw.y + 20);
      ctx.fill();
      ctx.closePath();
      ctx.fillRect(startDraw.x - 1, startDraw.y, 2, 100);

      // Wakeup explanation text.
      ctx.font = '13px Roboto Condensed';
      ctx.fillStyle = '#3c4b5d';
      if (threadInfo) {
        const displayText = `Wakeup @ ${
            timeToCode(
                details.wakeupTs - globals.state.traceTime.startSec)} on CPU ${
            details.wakerCpu} by`;
        const processText = `P: ${threadInfo.procName} [${threadInfo.pid}]`;
        const threadText = `T: ${threadInfo.threadName} [${threadInfo.tid}]`;
        ctx.fillText(displayText, startDraw.x + 20, startDraw.y + 20);
        ctx.fillText(processText, startDraw.x + 20, startDraw.y + 37);
        ctx.fillText(threadText, startDraw.x + 20, startDraw.y + 55);
      }

      // Draw latency arrow and explanation text.
      drawDoubleHeadedArrow(ctx, startDraw.x, startDraw.y + 80, 60, true);
      if (details.ts) {
        const displayLatency = `Scheduling latency: ${
            timeToCode(
                details.ts -
                (details.wakeupTs - globals.state.traceTime.startSec))}`;
        ctx.fillText(displayLatency, startDraw.x + 70, startDraw.y + 86);
        const explain1 =
            'This is the interval from when the task became eligible to run';
        const explain2 =
            '(e.g. because of notifying a wait queue it was suspended on) to';
        const explain3 = 'when it started running.';
        ctx.font = '10px Roboto Condensed';
        ctx.fillText(explain1, startDraw.x + 70, startDraw.y + 86 + 16);
        ctx.fillText(explain2, startDraw.x + 70, startDraw.y + 86 + 16 + 12);
        ctx.fillText(explain3, startDraw.x + 70, startDraw.y + 86 + 16 + 24);
      }
    }
  }
}
