/*
 * Copyright (C) 2022 Huawei Device Co., Ltd.
 * 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 { CpuStruct, WakeupBean } from './cpu/ProcedureWorkerCPU';
import { TraceRow } from '../../component/trace/base/TraceRow';
import { TimerShaftElement } from '../../component/trace/TimerShaftElement';
import { Flag } from '../../component/trace/timer-shaft/Flag';
import { drawVSync } from '../../component/chart/VSync';
import { draw } from '../../bean/FrameChartStruct';

export abstract class Render {
  abstract renderMainThread(req: any, row: TraceRow<any>): void;
}

export abstract class PerfRender {
  abstract render(req: RequestMessage, list: Array<any>, filter: Array<any>, dataList2: Array<any>): void;
}

export class RequestMessage {
  type: string | undefined | null;
  lazyRefresh: boolean | undefined;
  intervalPerf: any;
  canvas: any;
  context: any;
  params: any;
  online: any;
  buf: any;
  isRangeSelect: any;
  isHover: any;
  xs: any;
  frame: any;
  flagMoveInfo: any;
  flagSelectedInfo: any;
  hoverX: any;
  hoverY: any;
  startNS: any;
  endNS: any;
  totalNS: any;
  slicesTime:
    | {
        startTime: number | null;
        endTime: number | null;
        color: string | null;
      }
    | undefined;
  range: any;
  scale: any;
  chartColor: any;
  canvasWidth: any;
  canvasHeight: any;
  useCache: any;
  lineColor: any;
  wakeupBean: WakeupBean | undefined | null;
  id: any;
  postMessage:
    | {
        (message: any, targetOrigin: string, transfer?: Transferable[]): void;
        (message: any, options?: WindowPostMessageOptions): void;
      }
    | undefined;
}

export function ns2s(ns: number): string {
  let second1 = 1_000_000_000; // 1 second
  let millisecond = 1_000_000; // 1 millisecond
  let microsecond = 1_000; // 1 microsecond
  let res;
  if (ns >= second1) {
    res = `${(ns / 1000 / 1000 / 1000).toFixed(1)} s`;
  } else if (ns >= millisecond) {
    res = `${(ns / 1000 / 1000).toFixed(1)} ms`;
  } else if (ns >= microsecond) {
    res = `${(ns / 1000).toFixed(1)} μs`;
  } else if (ns > 0) {
    res = `${ns.toFixed(1)} ns`;
  } else {
    res = `${ns.toFixed(0)}`;
  }
  return res;
}

export function ns2Timestamp(ns: number): string {
  let hour = Math.floor(ns / 3600000000000);
  let minute = Math.floor((ns % 3600000000000) / 60000000000);
  let second = Math.floor((ns % 60000000000) / 1000000000);
  let millisecond = Math.floor((ns % 1000000000) / 1000000);
  let microsecond = Math.floor((ns % 1000000) / 1000);
  let nanosecond = ns % 1000;
  return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second
    .toString()
    .padStart(2, '0')}:${millisecond.toString().padStart(3, '0')}:${microsecond
    .toString()
    .padStart(3, '0')}:${nanosecond.toString().padStart(3, '0')}`;
}

const offsetX = 5;

export function isFrameContainPoint(
  frame: Rect,
  x: number,
  y: number,
  strict: boolean = true,
  offset: boolean = false
): boolean {
  if (strict) {
    if (offset) {
      return (
        x >= frame.x - offsetX && x <= frame.x + frame.width + offsetX && y >= frame.y && y <= frame.y + frame.height
      );
    } else {
      return x >= frame.x && x <= frame.x + frame.width && y >= frame.y && y <= frame.y + frame.height;
    }
  } else {
    if (offset) {
      return x >= frame.x - offsetX && x <= frame.x + frame.width + offsetX;
    } else {
      return x >= frame.x && x <= frame.x + frame.width;
    }
  }
}

export const isSurroundingPoint = function (pointX: number, currentRect: Rect, unitPointXRange: number): boolean {
  return pointX >= currentRect?.x - unitPointXRange && pointX <= currentRect?.x + unitPointXRange;
};

export const computeUnitWidth = function (
  preTs: number,
  currentTs: number,
  frameWidth: number,
  selectUnitWidth: number
): number {
  let max = 150;
  let unitWidth = ((currentTs - preTs) * frameWidth) / (TraceRow.range!.endNS - TraceRow.range!.startNS);
  if (unitWidth < selectUnitWidth) {
    return unitWidth > max || unitWidth === 0 ? max : unitWidth;
  }
  return selectUnitWidth > max || selectUnitWidth === 0 ? max : selectUnitWidth;
};

class FilterConfig {
  startNS: number = 0;
  endNS: number = 0;
  totalNS: number = 0;
  frame: any = null;
  useCache: boolean = false;
  startKey: string = 'startNS';
  durKey: string = 'dur';
  paddingTop: number = 0;
}

export function fillCacheData(filterList: Array<any>, condition: FilterConfig): boolean {
  if (condition.useCache && filterList.length > 0) {
    let pns = (condition.endNS - condition.startNS) / condition.frame.width;
    let y = condition.frame.y + condition.paddingTop;
    let height = condition.frame.height - condition.paddingTop * 2;
    for (let i = 0, len = filterList.length; i < len; i++) {
      let it = filterList[i];
      if (
        (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS &&
        (it[condition.startKey] || 0) < condition.endNS
      ) {
        if (!filterList[i].frame) {
          filterList[i].frame = {};
          filterList[i].frame.y = y;
          filterList[i].frame.height = height;
        }
        setNodeFrame(
          filterList[i],
          pns,
          condition.startNS,
          condition.endNS,
          condition.frame,
          condition.startKey,
          condition.durKey
        );
      } else {
        filterList[i].frame = null;
      }
    }
    return true;
  }
  return false;
}

export function fillCacheDataIdx(filterData: Array<any>, slice: number[], condition: FilterConfig): boolean {
  if (condition.useCache && filterData.length > 0) {
    let pns = (condition.endNS - condition.startNS) / condition.frame.width;
    let y = condition.frame.y + condition.paddingTop;
    let height = condition.frame.height - condition.paddingTop * 2;
    for (let i = slice[0]; i <= slice[1]; i++) {
      let it = filterData[i];
      if (!it) continue;
      if (
        (it[condition.startKey] || 0) + (it[condition.durKey] || 0) > condition.startNS &&
        (it[condition.startKey] || 0) < condition.endNS
      ) {
        if (!filterData[i].frame) {
          filterData[i].frame = {};
          filterData[i].frame.y = y;
          filterData[i].frame.height = height;
        }
        setNodeFrame(
          filterData[i],
          pns,
          condition.startNS,
          condition.endNS,
          condition.frame,
          condition.startKey,
          condition.durKey
        );
      } else {
        filterData[i].frame = null;
      }
    }
    return true;
  }
  return false;
}

export function bsearch(haystack: ArrayLike<any>, needle: any): number {
  return searchImpl(haystack, needle, 0, haystack.length);
}

function searchImpl(stack: ArrayLike<any>, cfg: FilterConfig, i: number, j: number): number {
  if (i === j) return -1;
  if (i + 1 === j) {
    return cfg.endNS >= stack[i][cfg.startKey] ? i : -1;
  }
  const middle = Math.floor((j - i) / 2) + i;
  const middleValue = stack[middle][cfg.startKey];
  if (cfg.endNS < middleValue) {
    return searchImpl(stack, cfg, i, middle);
  } else {
    return searchImpl(stack, cfg, middle, j);
  }
}

export function findRangeIdx(fullData: Array<any>, condition: FilterConfig): number[] {
  let a = fullData.findIndex((it) => it[condition.startKey] + it[condition.durKey] >= condition.startNS);
  let b = bsearch(fullData, condition);
  return [a, b + 1];
}

export function findRange(fullData: Array<any>, condition: FilterConfig): Array<any> {
  let left = 0,
    right = 0;
  for (let i = 0, j = fullData.length - 1, ib = true, jb = true; i < fullData.length, j >= 0; i++, j--) {
    if (fullData[j][condition.startKey] <= condition.endNS && jb) {
      right = j;
      jb = false;
    }
    if (fullData[i][condition.startKey] + fullData[i][condition.durKey] >= condition.startNS && ib) {
      left = i;
      ib = false;
    }
    if (!ib && !jb) {
      break;
    }
  }
  return fullData.slice(left, right + 1);
}

export const dataFilterHandler = (fullData: Array<any>, filterData: Array<any>, condition: FilterConfig): void => {
  if (fillCacheData(filterData, condition)) {
    return;
  }
  if (fullData) {
    filterData.length = 0;
    let pns = (condition.endNS - condition.startNS) / condition.frame.width; //每个像素多少ns
    let y = condition.frame.y + condition.paddingTop;
    let height = condition.frame.height - condition.paddingTop * 2;
    let slice = findRange(fullData, condition);
    for (let i = 0; i < slice.length; i++) {
      if (!slice[i].frame) {
        slice[i].frame = {};
        slice[i].frame.y = y;
        slice[i].frame.height = height;
      }
      if (slice[i][condition.durKey] === undefined || slice[i][condition.durKey] === null) {
        if (i === slice.length - 1) {
          slice[i][condition.durKey] = (condition.endNS || 0) - (slice[i][condition.startKey] || 0);
        } else {
          slice[i][condition.durKey] = (slice[i + 1][condition.startKey] || 0) - (slice[i][condition.startKey] || 0);
        }
      }
      setSliceFrame(slice, condition, pns, i);
    }
    filterData.push(...slice.filter((it) => it.v));
  }
};
function setSliceFrame(slice: Array<any>, condition: FilterConfig, pns: number, i: number) {
  let sum = 0;
  if (slice[i][condition.durKey] >= pns || slice.length < 100) {
    slice[i].v = true;
    setNodeFrame(
      slice[i],
      pns,
      condition.startNS,
      condition.endNS,
      condition.frame,
      condition.startKey,
      condition.durKey
    );
  } else {
    if (i > 0) {
      let c = slice[i][condition.startKey] - slice[i - 1][condition.startKey] - slice[i - 1][condition.durKey];
      if (c < pns && sum < pns) {
        sum += c + slice[i - 1][condition.durKey];
        slice[i].v = false;
      } else {
        slice[i].v = true;
        setNodeFrame(
          slice[i],
          pns,
          condition.startNS,
          condition.endNS,
          condition.frame,
          condition.startKey,
          condition.durKey
        );
        sum = 0;
      }
    }
  }
}
function setNodeFrame(
  node: any,
  pns: number,
  startNS: number,
  endNS: number,
  frame: any,
  startKey: string,
  durKey: string
) {
  if ((node[startKey] || 0) < startNS) {
    node.frame.x = 0;
  } else {
    node.frame.x = Math.floor(((node[startKey] || 0) - startNS) / pns);
  }
  if ((node[startKey] || 0) + (node[durKey] || 0) > endNS) {
    node.frame.width = frame.width - node.frame.x;
  } else {
    node.frame.width = Math.ceil(((node[startKey] || 0) + (node[durKey] || 0) - startNS) / pns - node.frame.x);
  }
  if (node.frame.width < 1) {
    node.frame.width = 1;
  }
}

export function ns2x(ns: number, startNS: number, endNS: number, duration: number, rect: any) {
  if (endNS === 0) {
    endNS = duration;
  }
  let xSize: number = ((ns - startNS) * rect.width) / (endNS - startNS);
  if (xSize < 0) {
    xSize = 0;
  } else if (xSize > rect.width) {
    xSize = rect.width;
  }
  return xSize;
}

export function nsx(ns: number, width: number) {
  let startNS = TraceRow.range?.startNS || 0;
  let endNS = TraceRow.range?.endNS || 0;
  let duration = TraceRow.range?.totalNS || 0;
  if (endNS === 0) {
    endNS = duration;
  }
  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
  if (xSize < 0) {
    xSize = 0;
  } else if (xSize > width) {
    xSize = width;
  }
  return xSize;
}

export function ns2xByTimeShaft(ns: number, tse: TimerShaftElement) {
  let startNS = tse.getRange()!.startNS;
  let endNS = tse.getRange()!.endNS;
  let duration = tse.getRange()!.totalNS;
  if (endNS === 0) {
    endNS = duration;
  }
  let width = tse.getBoundingClientRect().width - 258;
  let xSize: number = ((ns - startNS) * width) / (endNS - startNS);
  if (xSize < 0) {
    xSize = 0;
  } else if (xSize > width) {
    xSize = width;
  }
  return xSize;
}

export class Rect {
  x: number = 0;
  y: number = 0;
  width: number = 0;
  height: number = 0;
  constructor(x: number, y: number, width: number, height: number) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
  }

  static intersect(r1: Rect, rect: Rect): boolean {
    let minX = r1.x <= rect.x ? r1.x : rect.x;
    let minY = r1.y <= rect.y ? r1.y : rect.y;
    let maxX = r1.x + r1.width >= rect.x + rect.width ? r1.x + r1.width : rect.x + rect.width;
    let maxY = r1.y + r1.height >= rect.y + rect.height ? r1.y + r1.height : rect.y + rect.height;
    return maxX - minX <= rect.width + r1.width && maxY - minY <= r1.height + rect.height;
  }

  static contains(rect: Rect, x: number, y: number): boolean {
    return rect.x <= x && x <= rect.x + rect.width && rect.y <= y && y <= rect.y + rect.height;
  }

  static containsWithMargin(rect: Rect, x: number, y: number, t: number, r: number, b: number, l: number): boolean {
    return rect.x - l <= x && x <= rect.x + rect.width + r && rect.y - t <= y && y <= rect.y + rect.height + b;
  }

  static containsWithPadding(
    rect: Rect,
    x: number,
    y: number,
    paddingLeftRight: number,
    paddingTopBottom: number
  ): boolean {
    return (
      rect.x + paddingLeftRight <= x &&
      rect.y + paddingTopBottom <= y &&
      x <= rect.x + rect.width - paddingLeftRight &&
      y <= rect.y + rect.height - paddingTopBottom
    );
  }

  /**
   * 判断是否相交
   * @param rect
   */
  intersect(rect: Rect): boolean {
    let minX = this.x <= rect.x ? this.x : rect.x;
    let minY = this.y <= rect.y ? this.y : rect.y;
    let maxX = this.x + this.width >= rect.x + rect.width ? this.x + this.width : rect.x + rect.width;
    let maxY = this.y + this.height >= rect.y + rect.height ? this.y + this.height : rect.y + rect.height;
    return maxX - minX <= rect.width + this.width && maxY - minY <= this.height + rect.height;
  }

  contains(x: number, y: number): boolean {
    return this.x <= x && x <= this.x + this.width && this.y <= y && y <= this.y + this.height;
  }

  containsWithMargin(x: number, y: number, t: number, r: number, b: number, l: number): boolean {
    return this.x - l <= x && x <= this.x + this.width + r && this.y - t <= y && y <= this.y + this.height + b;
  }

  containsWithPadding(x: number, y: number, paddingLeftRight: number, paddingTopBottom: number): boolean {
    return (
      this.x + paddingLeftRight <= x &&
      x <= this.x + this.width - paddingLeftRight &&
      this.y + paddingTopBottom <= y &&
      y <= this.y + this.height - paddingTopBottom
    );
  }
}

export class Point {
  x: number = 0;
  y: number = 0;
  isRight: boolean = true;

  constructor(x: number, y: number, isRight: boolean = true) {
    this.x = x;
    this.y = y;
    this.isRight = isRight;
  }
}

export enum LineType {
  brokenLine,
  bezierCurve,
  straightLine,
}

export class PairPoint {
  x: number = 0;
  ns: number = 0;
  y: number = 0;
  offsetY: number = 0;
  rowEL: TraceRow<any>;
  isRight: boolean = true;
  lineType?: LineType;
  business: string = '';
  hidden?: boolean = false;
  backrowEL?: TraceRow<any>;
  constructor(
    rowEL: TraceRow<any>,
    x: number,
    y: number,
    ns: number,
    offsetY: number,
    isRight: boolean,
    business: string
  ) {
    this.rowEL = rowEL;
    this.x = x;
    this.y = y;
    this.ns = ns;
    this.offsetY = offsetY;
    this.isRight = isRight;
    this.business = business;
  }
}

export class BaseStruct {
  translateY: number | undefined;
  frame: Rect | undefined;
  isHover: boolean = false;
}

export function drawLines(ctx: CanvasRenderingContext2D, xs: Array<any>, height: number, lineColor: string) {
  if (ctx) {
    ctx.beginPath();
    ctx.lineWidth = 1;
    ctx.strokeStyle = lineColor || '#dadada';
    xs?.forEach((it) => {
      ctx.moveTo(Math.floor(it), 0);
      ctx.lineTo(Math.floor(it), height);
    });
    ctx.stroke();
    ctx.closePath();
  }
}

export function drawFlagLine(
  commonCtx: any,
  hoverFlag: any,
  selectFlag: any,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: any,
  slicesTime:
    | {
        startTime: number | null | undefined;
        endTime: number | null | undefined;
        color: string | null | undefined;
      }
    | undefined
) {
  if (commonCtx) {
    if (hoverFlag) {
      setHoverFlag(hoverFlag, commonCtx, frame);
    }
    if (selectFlag) {
      commonCtx.beginPath();
      commonCtx.lineWidth = 2;
      commonCtx.strokeStyle = selectFlag?.color || '#dadada';
      selectFlag.x = ns2x(selectFlag.time, startNS, endNS, totalNS, frame);
      commonCtx.moveTo(Math.floor(selectFlag.x), 0);
      commonCtx.lineTo(Math.floor(selectFlag.x), frame.height);
      commonCtx.stroke();
      commonCtx.closePath();
    }
    if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
      commonCtx.beginPath();
      commonCtx.lineWidth = 1;
      commonCtx.strokeStyle = slicesTime.color || '#dadada';
      let x1 = ns2x(slicesTime.startTime, startNS, endNS, totalNS, frame);
      let x2 = ns2x(slicesTime.endTime, startNS, endNS, totalNS, frame);
      commonCtx.moveTo(Math.floor(x1), 0);
      commonCtx.lineTo(Math.floor(x1), frame.height);
      commonCtx.moveTo(Math.floor(x2), 0);
      commonCtx.lineTo(Math.floor(x2), frame.height);
      commonCtx.stroke();
      commonCtx.closePath();
    }
  }
}

export function drawFlagLineSegment(
  ctx: CanvasRenderingContext2D | null | undefined,
  hoverFlag: Flag | null | undefined,
  selectFlag: Flag | null | undefined,
  frame: any,
  tse: TimerShaftElement
): void {
  if (ctx) {
    setHoverFlag(hoverFlag, ctx, frame);
    setSelectFlag(selectFlag, ctx, frame);
    tse.sportRuler!.slicesTimeList.forEach((slicesTime) => {
      if (slicesTime && slicesTime.startTime && slicesTime.endTime) {
        ctx.beginPath();
        ctx.lineWidth = 1;
        ctx.strokeStyle = slicesTime.color || '#dadada';
        let x1 = ns2x(
          slicesTime.startTime,
          TraceRow.range!.startNS,
          TraceRow.range!.endNS,
          TraceRow.range!.totalNS,
          frame
        );
        let x2 = ns2x(
          slicesTime.endTime,
          TraceRow.range!.startNS,
          TraceRow.range!.endNS,
          TraceRow.range!.totalNS,
          frame
        );
        // 划线逻辑
        ctx.moveTo(Math.floor(x1), 0);
        ctx.lineTo(Math.floor(x1), frame.height); //左边的线
        ctx.moveTo(Math.floor(x2), 0);
        ctx.lineTo(Math.floor(x2), frame.height); // 右边的线
        ctx.stroke();
        ctx.closePath();
      }
    });
  }
}

function setHoverFlag(hoverFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: any): void {
  if (hoverFlag) {
    ctx.beginPath();
    ctx.lineWidth = 2;
    ctx.strokeStyle = hoverFlag?.color || '#dadada';
    ctx.moveTo(Math.floor(hoverFlag.x), 0);
    ctx.lineTo(Math.floor(hoverFlag.x), frame.height);
    ctx.stroke();
    ctx.closePath();
  }
}

function setSelectFlag(selectFlag: Flag | null | undefined, ctx: CanvasRenderingContext2D, frame: any): void {
  if (selectFlag) {
    ctx.beginPath();
    ctx.lineWidth = 2;
    ctx.strokeStyle = selectFlag?.color || '#dadada';
    selectFlag.x = ns2x(
      selectFlag.time,
      TraceRow.range!.startNS,
      TraceRow.range!.endNS,
      TraceRow.range!.totalNS,
      frame
    );
    ctx.moveTo(Math.floor(selectFlag.x), 0);
    ctx.lineTo(Math.floor(selectFlag.x), frame.height);
    ctx.stroke();
    ctx.closePath();
  }
}

export function drawLogsLineSegment(
  ctx: CanvasRenderingContext2D | undefined | null,
  systemLogFlag: Flag | undefined | null,
  frame: {
    x: number;
    y: number;
    width: number | undefined;
    height: number | undefined;
  },
  timerShaftEl: TimerShaftElement
): void {
  timerShaftEl.sportRuler?.draw();
  if (systemLogFlag) {
    if (ctx) {
      ctx.beginPath();
      ctx.lineWidth = 2;
      ctx.strokeStyle = systemLogFlag?.color || '#dadada';
      ctx.moveTo(Math.floor(systemLogFlag.x), 0);
      ctx.lineTo(Math.floor(systemLogFlag.x), frame.height || 0);
      ctx.stroke();
      ctx.closePath();
    }
    if (timerShaftEl.ctx) {
      let timeText = `| ${ns2Timestamp(systemLogFlag.time)}`;
      let textPointX = systemLogFlag.x;
      let textMetrics = timerShaftEl.ctx.measureText(timeText);
      if (timerShaftEl.ctx.canvas.width - systemLogFlag.x <= textMetrics.width) {
        textPointX = systemLogFlag.x - textMetrics.width;
        timeText = `${ns2Timestamp(systemLogFlag.time)} |`;
      }
      let locationY = 120;
      timerShaftEl.ctx.beginPath();
      timerShaftEl.ctx.lineWidth = 0;
      timerShaftEl.ctx.fillStyle = '#FFFFFF';
      let textHeight = textMetrics.actualBoundingBoxAscent + textMetrics.actualBoundingBoxDescent;
      timerShaftEl.ctx.fillRect(textPointX, locationY - textHeight, textMetrics.width, textHeight);
      timerShaftEl.ctx.lineWidth = 2;
      timerShaftEl.ctx.fillStyle = systemLogFlag?.color || '#dadada';
      timerShaftEl.ctx.fillText(timeText, textPointX, locationY);
      timerShaftEl.ctx.stroke();
      timerShaftEl.ctx.closePath();
    }
  }
}

export function drawSelection(ctx: any, params: any) {
  if (params.isRangeSelect && params.rangeSelectObject) {
    params.rangeSelectObject!.startX = Math.floor(
      ns2x(params.rangeSelectObject!.startNS!, params.startNS, params.endNS, params.totalNS, params.frame)
    );
    params.rangeSelectObject!.endX = Math.floor(
      ns2x(params.rangeSelectObject!.endNS!, params.startNS, params.endNS, params.totalNS, params.frame)
    );
    if (ctx) {
      ctx.globalAlpha = 0.5;
      ctx.fillStyle = '#666666';
      ctx.fillRect(
        params.rangeSelectObject!.startX!,
        params.frame.y,
        params.rangeSelectObject!.endX! - params.rangeSelectObject!.startX!,
        params.frame.height
      );
      ctx.globalAlpha = 1;
    }
  }
}

// draw range select
export function drawSelectionRange(context: any, params: TraceRow<any>) {
  if (params.rangeSelect && TraceRow.rangeSelectObject) {
    setStartXEndX(params);
    if (context) {
      context.globalAlpha = 0.5;
      context.fillStyle = '#666666';
      context.fillRect(
        TraceRow.rangeSelectObject!.startX!,
        params.frame.y,
        TraceRow.rangeSelectObject!.endX! - TraceRow.rangeSelectObject!.startX!,
        params.frame.height
      );
      context.globalAlpha = 1;
    }
    // 绘制方法H:RSMainThread::DoComposition平均帧率的箭头指示线条
    if (params._docompositionList?.length) {
      const rateList: Array<number> = [...new Set(params.docompositionList)];
      if (rateList.length >= 2) {
        changeFrameRatePoint(rateList, context, params);
      }
    }
  }
}

function setStartXEndX(params: TraceRow<any>) {
  TraceRow.rangeSelectObject!.startX = Math.floor(
    ns2x(
      TraceRow.rangeSelectObject!.startNS!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      params.frame
    )
  );
  TraceRow.rangeSelectObject!.endX = Math.floor(
    ns2x(
      TraceRow.rangeSelectObject!.endNS!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      params.frame
    )
  );
}

function setAvgRateStartXEndX(rateList: number[], params: TraceRow<any>) {
  let avgRateStartX = Math.floor(
    ns2x(
      rateList[0]!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      params.frame
    )
  );
  let avgRateEndX = Math.floor(
    ns2x(
      rateList[rateList.length - 1]!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      params.frame
    )
  );
  return [avgRateStartX, avgRateEndX];
}

function setTextXY(rateList: number[], params: TraceRow<any>, textWidth: any) {
  let textX =
    Math.floor(
      ns2x(
        (rateList[0]! + rateList[rateList.length - 1]!) / 2,
        TraceRow.range?.startNS ?? 0,
        TraceRow.range?.endNS ?? 0,
        TraceRow.range?.totalNS ?? 0,
        params.frame
      )
    ) -
    textWidth / 2;
  let textY = params.frame.y + 25;
  return [textX, textY];
}

function drawSelectionRangeContext(rateList: number[], context: any, params: TraceRow<any>) {
  let cutres: number = rateList[rateList.length - 1]! - rateList[0]!;
  let avgFrameRate: string = (((rateList.length - 1) / cutres) * 1000000000).toFixed(1) + 'fps';
  let avgRateStartX = setAvgRateStartXEndX(rateList, params)[0];
  let avgRateEndX = setAvgRateStartXEndX(rateList, params)[1];
  const textWidth = context.measureText(avgFrameRate).width;
  const textHeight = 25;
  const padding = 5;
  let textX = setTextXY(rateList, params, textWidth)[0];
  const textY = setTextXY(rateList, params, textWidth)[1];
  avgRateStartX = avgRateStartX <= 0 ? -100 : avgRateStartX;
  avgRateEndX = avgRateEndX <= 0 ? -100 : avgRateEndX;
  textX = textX <= 0 ? -100 : textX;
  textX = textX + textWidth / 2 >= params.frame.width ? params.frame.width + 100 : textX;
  avgRateStartX = avgRateStartX >= params.frame.width ? params.frame.width + 100 : avgRateStartX;
  avgRateEndX = avgRateEndX >= params.frame.width ? params.frame.width + 100 : avgRateEndX;
  context.fillStyle = 'red';
  context.fillRect(textX - padding, textY - textHeight + padding, textWidth + padding * 2, textHeight - padding * 2);
  context.lineWidth = 2;
  context.strokeStyle = 'yellow';
  context.beginPath();
  context.moveTo(avgRateStartX, textY);
  context.lineTo(avgRateEndX, textY);
  context.stroke();
  const arrowSize = 5.5;
  const arrowHead = (x: number, y: number, direction: 'left' | 'right') => {
    context.beginPath();
    const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
    const headY = y - arrowSize / 2;
    context.moveTo(x, y);
    context.lineTo(headX, headY);
    context.lineTo(headX, y + arrowSize);
    context.closePath();
    context.fillStyle = 'yellow';
    context.fill();
  };
  arrowHead(avgRateStartX, textY - 1, 'left');
  arrowHead(avgRateEndX, textY - 1, 'right');
  context.fillStyle = 'white';
  context.fillText(avgFrameRate, textX, textY - 8);
}

// 转换起始点坐标
function changeFrameRatePoint(rateList: Array<number>, ctx: any, selectParams: TraceRow<any>): void {
  let avgRateStartX = Math.floor(
    ns2x(
      rateList[0]!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      selectParams.frame
    )
  );
  let avgRateEndX = Math.floor(
    ns2x(
      rateList[rateList.length - 1]!,
      TraceRow.range?.startNS ?? 0,
      TraceRow.range?.endNS ?? 0,
      TraceRow.range?.totalNS ?? 0,
      selectParams.frame
    )
  );
  drawAvgFrameRate(rateList, ctx, selectParams, avgRateStartX, avgRateEndX);
}

// 计算平均帧率
function calculateAvgRate(arr: Array<number>) {
  const CONVERT_SECONDS = 1000000000;
  let cutres: number = arr[arr.length - 1]! - arr[0]!;
  let avgRate: string = (((arr.length - 1) / cutres) * CONVERT_SECONDS).toFixed(1);
  return avgRate;
}

// 绘制平均帧率箭头指示线条
function drawAvgFrameRate(
  arrList: Array<number>,
  ctx: any,
  selectParams: TraceRow<any>,
  startX: number,
  endX: number
): void {
  let avgFrameRate: string = calculateAvgRate(arrList) + 'fps';
  const textWidth = ctx.measureText(avgFrameRate).width;
  const TEXT_WIDTH_HALF = 2;
  let textX =
    Math.floor(
      ns2x(
        (arrList[0]! + arrList[arrList.length - 1]!) / 2,
        TraceRow.range?.startNS ?? 0,
        TraceRow.range?.endNS ?? 0,
        TraceRow.range?.totalNS ?? 0,
        selectParams.frame
      )
    ) -
    textWidth / TEXT_WIDTH_HALF;
  const textY = selectParams.frame.y + 25;
  if (startX <= 0) {
    startX = -100;
  }
  if (endX <= 0) {
    endX = -100;
  }
  if (textX <= 0) {
    textX = -100;
  }
  const ADD_DISTANCE = 100;
  if (textX + textWidth / 2 >= selectParams.frame.width) {
    textX = selectParams.frame.width + ADD_DISTANCE;
  }
  if (startX >= selectParams.frame.width) {
    startX = selectParams.frame.width + ADD_DISTANCE;
  }
  if (endX >= selectParams.frame.width) {
    endX = selectParams.frame.width + ADD_DISTANCE;
  }
  drawAvgFrameRateArrow(ctx, textX, textY, textWidth, startX, endX, avgFrameRate);
}
function drawAvgFrameRateArrow(
  ctx: any,
  textX: number,
  textY: number,
  textWidth: number,
  startX: number,
  endX: number,
  avgFrameRate: string
) {
  const textHeight = 25;
  const padding = 5;
  const TEXT_RECT_PADDING = 2;
  ctx.fillStyle = 'red';
  ctx.fillRect(
    textX - padding,
    textY - textHeight + padding,
    textWidth + padding * TEXT_RECT_PADDING,
    textHeight - padding * TEXT_RECT_PADDING
  );
  ctx.lineWidth = 2;
  ctx.strokeStyle = 'yellow';
  ctx.beginPath();
  ctx.moveTo(startX, textY);
  ctx.lineTo(endX, textY);
  ctx.stroke();
  arrowHead(ctx, startX, textY - 1, 'left');
  arrowHead(ctx, endX, textY - 1, 'right');
  ctx.fillStyle = 'white';
  ctx.fillText(avgFrameRate, textX, textY - 8);
}
const arrowSize = 5.5;
const arrowHead = (ctx: any, x: number, y: number, direction: 'left' | 'right') => {
  ctx.beginPath();
  const headX = x + (direction === 'left' ? arrowSize : -arrowSize);
  const headY = y - arrowSize / 2;
  ctx.moveTo(x, y);
  ctx.lineTo(headX, headY);
  ctx.lineTo(headX, y + arrowSize);
  ctx.closePath();
  ctx.fillStyle = 'yellow';
  ctx.fill();
};

export function drawWakeUp(
  wakeUpContext: CanvasRenderingContext2D | any,
  wake: WakeupBean | undefined | null,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: Rect,
  selectCpuStruct: CpuStruct | undefined = undefined,
  wakeUpCurrentCpu: number | undefined = undefined,
  noVerticalLine = false
) {
  if (wake) {
    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
    wakeUpContext.beginPath();
    wakeUpContext.lineWidth = 2;
    wakeUpContext.fillStyle = '#000000';
    if (x1 > 0 && x1 < frame.x + frame.width) {
      if (!noVerticalLine) {
        wakeUpContext.moveTo(x1, frame.y);
        wakeUpContext.lineTo(x1, frame.y + frame.height);
      }
      if (wakeUpCurrentCpu === wake.cpu) {
        let centerY = Math.floor(frame.y + frame.height / 2);
        wakeUpContext.moveTo(x1, centerY - 6);
        wakeUpContext.lineTo(x1 + 4, centerY);
        wakeUpContext.lineTo(x1, centerY + 6);
        wakeUpContext.lineTo(x1 - 4, centerY);
        wakeUpContext.lineTo(x1, centerY - 6);
        wakeUpContext.fill();
      }
    }
    if (selectCpuStruct) {
      drawWakeUpIfSelect(selectCpuStruct, startNS, endNS, totalNS, frame, wakeUpContext, wake, x1);
    }
    wakeUpContext.strokeStyle = '#000000';
    wakeUpContext.stroke();
    wakeUpContext.closePath();
  }
}

function drawWakeUpIfSelect(
  selectCpuStruct: CpuStruct,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: Rect,
  wakeUpContext: any,
  wake: any,
  x1: number
) {
  let x2 = Math.floor(ns2x(selectCpuStruct.startTime || 0, startNS, endNS, totalNS, frame));
  let y = frame.y + frame.height - 10;
  wakeUpContext.moveTo(x1, y);
  wakeUpContext.lineTo(x2, y);
  let s = ns2s((selectCpuStruct.startTime || 0) - (wake.wakeupTime || 0));
  let distance = x2 - x1;
  if (distance > 12) {
    wakeUpContext.moveTo(x1, y);
    wakeUpContext.lineTo(x1 + 6, y - 3);
    wakeUpContext.moveTo(x1, y);
    wakeUpContext.lineTo(x1 + 6, y + 3);
    wakeUpContext.moveTo(x2, y);
    wakeUpContext.lineTo(x2 - 6, y - 3);
    wakeUpContext.moveTo(x2, y);
    wakeUpContext.lineTo(x2 - 6, y + 3);
    let measure = wakeUpContext.measureText(s);
    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
    let xStart = x1 + Math.floor(distance / 2 - measure.width / 2);
    if (distance > measure.width + 4) {
      wakeUpContext.fillStyle = '#ffffff';
      wakeUpContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
      wakeUpContext.font = '10px solid';
      wakeUpContext.fillStyle = '#000000';
      wakeUpContext.textBaseline = 'bottom';
      wakeUpContext.fillText(s, xStart, y - 2);
    }
  }
}

const wid = 5;
const linkLineColor = '#ff0000';
export function drawLinkLines(
  context: CanvasRenderingContext2D,
  nodes: PairPoint[][],
  tm: TimerShaftElement,
  isFavorite: boolean,
  favoriteHeight: number
) {
  let percentage =
    (tm.getRange()!.totalNS - Math.abs(tm.getRange()!.endNS - tm.getRange()!.startNS)) / tm.getRange()!.totalNS;
  let maxWidth = tm.getBoundingClientRect().width - 268;
  setLinkLinesNodes(nodes, isFavorite, favoriteHeight, maxWidth, context, percentage);
}

function setLinkLinesNodes(nodes: any, isFav: any, favH: number, max: number, context: any, perc: number): void {
  for (let i = 0; i < nodes.length; i++) {
    let it = nodes[i];
    let newFirstNode = new PairPoint(
      it[0].rowEL,
      it[0].x,
      it[0].y,
      it[0].ns,
      it[0].offsetY,
      it[0].isRight,
      it[0].business
    );
    let newSecondNode = new PairPoint(
      it[1].rowEL,
      it[1].x,
      it[1].y,
      it[1].ns,
      it[1].offsetY,
      it[1].isRight,
      it[1].business
    );
    if (it[0].hidden) {
      continue;
    }
    if (isFav) {
      if (it[0].rowEL.collect && it[1].rowEL.collect) {
      } else if (!it[0].rowEL.collect && !it[1].rowEL.collect) {
        continue;
      } else {
        it[0].rowEL.collect
          ? (newSecondNode.y = Math.max(it[1].y + favH, favH))
          : (newFirstNode.y = Math.max(it[0].y + favH, favH));
      }
    } else {
      if (it[0].rowEL.collect && it[1].rowEL.collect) {
        continue;
      } else if (!it[0].rowEL.collect && !it[1].rowEL.collect) {
      } else {
        it[0].rowEL.collect ? (newFirstNode.y = it[0].y - favH) : (newSecondNode.y = it[1].y - favH);
      }
    }
    drawLinesByType(it[0].lineType, newFirstNode, newSecondNode, max, context, perc);
  }
}

function drawLinesByType(
  lineType: LineType | undefined,
  newFirstNode: PairPoint,
  newSecondNode: PairPoint,
  maxWidth: number,
  context: CanvasRenderingContext2D,
  percentage: number
) {
  switch (lineType) {
    case LineType.brokenLine:
      drawBrokenLine([newFirstNode, newSecondNode], maxWidth, context);
      break;
    case LineType.bezierCurve:
      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
      break;
    case LineType.straightLine:
      drawStraightLine([newFirstNode, newSecondNode], maxWidth, context);
      break;
    default:
      drawBezierCurve([newFirstNode, newSecondNode], maxWidth, context, percentage);
  }
}

function drawBezierCurve(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D, percentage: number) {
  let bezierCurveStart = it[0].x > it[1].x ? it[1] : it[0];
  let bezierCurveEnd = it[0].x > it[1].x ? it[0] : it[1];
  if (bezierCurveStart && bezierCurveEnd) {
    //左移到边界,不画线
    if (bezierCurveStart.x <= 0) {
      bezierCurveStart.x = -100;
    }
    if (bezierCurveEnd.x <= 0) {
      bezierCurveEnd.x = -100;
    }
    //右移到边界,不画线
    if (bezierCurveStart.x >= maxWidth) {
      bezierCurveStart.x = maxWidth + 100;
    }
    if (bezierCurveEnd.x >= maxWidth) {
      bezierCurveEnd.x = maxWidth + 100;
    }
    drawBezierCurveContext(context, bezierCurveStart, bezierCurveEnd, percentage);
  }
}

function drawBezierCurveContext(
  context: CanvasRenderingContext2D,
  bezierCurveStart: PairPoint,
  bezierCurveEnd: PairPoint,
  percentage: number
) {
  context.beginPath();
  context.lineWidth = 2;
  context.fillStyle = linkLineColor;
  context.strokeStyle = linkLineColor;
  let x0, y0, x1, x2, y1, y2, x3, y3;
  x0 = bezierCurveStart.x ?? 0;
  y0 = bezierCurveStart.y ?? 0;
  x3 = bezierCurveEnd.x ?? 0;
  y3 = bezierCurveEnd.y ?? 0;
  x2 = bezierCurveEnd.isRight ? x3 - 100 * percentage : x3 + 100 * percentage;
  y2 = y3 - 40 * percentage;
  x1 = bezierCurveStart.isRight ? x0 - 100 * percentage : x0 + 100 * percentage;
  y1 = y0 + 40 * percentage;
  if (!bezierCurveStart.isRight) {
    x0 -= 5;
  }
  context.moveTo(x0, y0);
  if (bezierCurveStart.isRight) {
    context.lineTo(x0 - wid, y0 + wid);
    context.moveTo(x0, y0);
    context.lineTo(x0 - wid, y0 - wid);
  } else {
    context.lineTo(x0 + wid, y0 + wid);
    context.moveTo(x0, y0);
    context.lineTo(x0 + wid, y0 - wid);
  }
  context.moveTo(x0, y0);
  context.bezierCurveTo(x1, y1, x2, y2, x3, y3);
  context.moveTo(x3, y3);
  if (bezierCurveEnd.isRight) {
    context.lineTo(x3 - wid, y3 + wid);
    context.moveTo(x3, y3);
    context.lineTo(x3 - wid, y3 - wid);
  } else {
    context.lineTo(x3 + wid, y3 + wid);
    context.moveTo(x3, y3);
    context.lineTo(x3 + wid, y3 - wid);
  }
  context.moveTo(x3, y3);
  context.stroke();
  context.closePath();
}

function drawStraightLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D) {
  let startPoint = it[0].x > it[1].x ? it[1] : it[0];
  let endPoint = it[0].x > it[1].x ? it[0] : it[1];
  let arrowSize = 8;
  if (startPoint && endPoint) {
    //左移到边界,不画线
    if (startPoint.x <= 0) {
      startPoint.x = -100;
    }
    if (endPoint.x <= 0) {
      endPoint.x = -100;
    }
    //右移到边界,不画线
    if (startPoint.x >= maxWidth) {
      startPoint.x = maxWidth + 100;
    }
    if (endPoint.x >= maxWidth) {
      endPoint.x = maxWidth + 100;
    }
    drawArrow(context, startPoint, endPoint, arrowSize);
  }
}
function drawArrow(context: CanvasRenderingContext2D, startPoint: PairPoint, endPoint: PairPoint, arrowSize: number) {
  context.beginPath();
  context.lineWidth = 2;
  context.strokeStyle = '#0000FF';
  context.moveTo(startPoint.x, startPoint.y);
  context.lineTo(endPoint.x, endPoint.y);
  // 绘制箭头
  let arrow = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
  context.moveTo(endPoint.x, endPoint.y);
  context.lineTo(
    endPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
    endPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
  );
  context.moveTo(endPoint.x, endPoint.y);
  context.lineTo(
    endPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
    endPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
  );
  // 绘制另一端箭头
  arrow = Math.atan2(startPoint.y - endPoint.y, startPoint.x - endPoint.x);
  context.moveTo(startPoint.x, startPoint.y);
  context.lineTo(
    startPoint.x - arrowSize * Math.cos(arrow - Math.PI / 6),
    startPoint.y - arrowSize * Math.sin(arrow - Math.PI / 6)
  );
  context.moveTo(startPoint.x, startPoint.y);
  context.lineTo(
    startPoint.x - arrowSize * Math.cos(arrow + Math.PI / 6),
    startPoint.y - arrowSize * Math.sin(arrow + Math.PI / 6)
  );
  context.stroke();
  context.closePath();
}
function drawBrokenLine(it: PairPoint[], maxWidth: number, context: CanvasRenderingContext2D): void {
  let brokenLineStart = it[0].x > it[1].x ? it[1] : it[0];
  let brokenLineEnd = it[0].x > it[1].x ? it[0] : it[1];
  if (brokenLineStart && brokenLineEnd) {
    if (brokenLineStart.x <= 0) {
      brokenLineStart.x = -100;
    }
    if (brokenLineEnd.x <= 0) {
      brokenLineEnd.x = -100;
    }
    if (brokenLineStart.x >= maxWidth) {
      brokenLineStart.x = maxWidth + 100;
    }
    if (brokenLineEnd.x >= maxWidth) {
      brokenLineEnd.x = maxWidth + 100;
    }
    drawBrokenLineContext(context, brokenLineStart, brokenLineEnd);
  }
}

function drawBrokenLineContext(
  context: CanvasRenderingContext2D,
  brokenLineStart: PairPoint,
  brokenLineEnd: PairPoint
) {
  context.beginPath();
  context.lineWidth = 2;
  context.fillStyle = '#46B1E3';
  context.strokeStyle = '#46B1E3';
  let x0, y0, x1, y1, x2, y2;
  x0 = brokenLineStart.x ?? 0;
  y0 = brokenLineStart.y ?? 0;
  y2 = brokenLineEnd.y ?? 0;
  x2 = brokenLineEnd.x ?? 0;
  let leftEndpointX, leftEndpointY, rightEndpointX, rightEndpointY;
  if (brokenLineStart.y < brokenLineEnd.y) {
    x1 = brokenLineStart.x ?? 0;
    y1 = brokenLineEnd.y ?? 0;
    leftEndpointX = x2 - wid;
    leftEndpointY = y2 - wid;
    rightEndpointX = x2 - wid;
    rightEndpointY = y2 + wid;
  } else {
    x2 = brokenLineEnd.x - wid ?? 0;
    x1 = brokenLineEnd.x - wid ?? 0;
    y1 = brokenLineStart.y ?? 0;
    leftEndpointX = x2 - wid;
    leftEndpointY = y2 + wid;
    rightEndpointX = x2 + wid;
    rightEndpointY = y2 + wid;
  }
  context.moveTo(x0, y0);
  context.lineTo(x1, y1);
  context.lineTo(x2, y2);
  context.stroke();
  context.closePath();
  context.beginPath();
  context.lineWidth = 2;
  context.fillStyle = '#46B1E3';
  context.strokeStyle = '#46B1E3';
  context.moveTo(x2, y2);
  context.lineTo(leftEndpointX, leftEndpointY);
  context.lineTo(rightEndpointX, rightEndpointY);
  context.lineTo(x2, y2);
  context.fill();
  context.closePath();
}

export function drawLoading(
  ctx: CanvasRenderingContext2D,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: any,
  left: number,
  right: number
) {}

let loadingText = 'Loading...';
let loadingTextWidth = 0;
let loadingBackground = '#f1f1f1';
let loadingFont = 'bold 11pt Arial';
let loadingFontColor = '#696969';
export function drawLoadingFrame(
  ctx: CanvasRenderingContext2D,
  list: Array<any>,
  row: TraceRow<any>,
  sort: boolean = false
) {
  ctx.beginPath();
  ctx.clearRect(0, 0, row.frame.width, row.frame.height);
  drawLines(ctx, TraceRow.range?.xs || [], row.frame.height, '#dadada');
  drawVSync(ctx, row.frame.width, row.frame.height);
  if (row.loadingFrame) {
    if (loadingTextWidth === 0) {
      loadingTextWidth = ctx.measureText(loadingText).width;
    }
    let firstPx = nsx(row.loadingPin1, row.frame.width);
    let lastPx = nsx(row.loadingPin2, row.frame.width);
    ctx.fillStyle = loadingBackground;
    ctx.fillRect(0, 1, firstPx, row.frame.height - 2);
    ctx.fillRect(lastPx, 1, row.frame.width - lastPx, row.frame.height - 2);
    ctx.fillStyle = loadingFontColor;
    if (firstPx > loadingTextWidth) {
      ctx.fillText(loadingText, (firstPx - loadingTextWidth) / 2, row.frame.height / 2);
    }
    if (row.frame.width - lastPx > loadingTextWidth) {
      ctx.fillText(loadingText, lastPx + (row.frame.width - lastPx) / 2 - loadingTextWidth / 2, row.frame.height / 2);
    }
  }
  ctx.closePath();
}

export function drawString(ctx: CanvasRenderingContext2D, str: string, textPadding: number, frame: Rect, data: any) {
  if (data.textMetricsWidth === undefined) {
    data.textMetricsWidth = ctx.measureText(str).width;
  }
  let charWidth = Math.round(data.textMetricsWidth / str.length);
  let fillTextWidth = frame.width - textPadding * 2;
  if (data.textMetricsWidth < fillTextWidth) {
    let x2 = Math.floor(frame.width / 2 - data.textMetricsWidth / 2 + frame.x + textPadding);
    ctx.fillText(str, x2, Math.floor(frame.y + frame.height / 2), fillTextWidth);
  } else {
    if (fillTextWidth >= charWidth) {
      let chatNum = fillTextWidth / charWidth;
      let x1 = frame.x + textPadding;
      if (chatNum < 2) {
        ctx.fillText(str.substring(0, 1), x1, Math.floor(frame.y + frame.height / 2), fillTextWidth);
      } else {
        ctx.fillText(str.substring(0, chatNum - 1) + '...', x1, Math.floor(frame.y + frame.height / 2), fillTextWidth);
      }
    }
  }
}

export function drawString2Line(
  ctx: CanvasRenderingContext2D,
  str1: string,
  str2: string,
  textPadding: number,
  frame: Rect,
  data: any
) {
  if (frame.height < 30) return;
  if (data.textMetrics1Width === undefined) {
    data.textMetrics1Width = ctx.measureText(str1).width;
    data.textMetrics2Width = ctx.measureText(str2).width;
  }
  let charWidth = Math.round(data.textMetrics1Width / str1.length);
  let fillTextWidth = frame.width - textPadding * 2;
  let y1 = frame.y + 12;
  let y2 = y1 + 14;
  if (data.textMetrics1Width < fillTextWidth) {
    let x = Math.floor(frame.width / 2 - data.textMetrics1Width / 2 + frame.x + textPadding - 1);
    ctx.fillText(str1, x, y1, fillTextWidth);
  } else {
    if (fillTextWidth >= charWidth) {
      let chatNum = fillTextWidth / charWidth;
      let x = frame.x + textPadding - 1;
      if (chatNum < 2) {
        ctx.fillText(str1.substring(0, 1), x, y1, fillTextWidth);
      } else {
        ctx.fillText(str1.substring(0, chatNum - 1) + '...', x, y1, fillTextWidth);
      }
    }
  }
  if (data.textMetrics2Width < fillTextWidth) {
    let x = Math.floor(frame.width / 2 - data.textMetrics2Width / 2 + frame.x + textPadding - 1);
    ctx.fillText(str2, x, y2, fillTextWidth);
  } else {
    if (fillTextWidth >= charWidth) {
      let chatNum = fillTextWidth / charWidth;
      let x = frame.x + textPadding - 1;
      if (chatNum < 2) {
        ctx.fillText(str2.substring(0, 1), x, y2, fillTextWidth);
      } else {
        ctx.fillText(str2.substring(0, chatNum - 1) + '...', x, y2, fillTextWidth);
      }
    }
  }
}

export function hiPerf(
  arr: Array<any>,
  arr2: Array<any>,
  res: Array<any>,
  startNS: number,
  endNS: number,
  frame: any,
  groupBy10MS: boolean,
  use: boolean
): void {
  if (use && res.length > 0) {
    setFrameByRes(res, startNS, endNS, frame);
    return;
  }
  res.length = 0;
  if (arr) {
    setFrameByArr(arr, arr2, res, startNS, endNS, frame, groupBy10MS);
  }
}

function setFrameByRes(res: Array<any>, startNS: number, endNS: number, frame: any) {
  let pns = (endNS - startNS) / frame.width;
  let y = frame.y;
  for (let i = 0; i < res.length; i++) {
    let item = res[i];
    if ((item.startNS || 0) + (item.dur || 0) > startNS && (item.startNS || 0) < endNS) {
      if (!item.frame) {
        item.frame = {};
        item.frame.y = y;
      }
      item.frame.height = item.height;
      HiPerfStruct.setFrame(item, pns, startNS, endNS, frame);
    } else {
      item.frame = null;
    }
  }
}

function setFrameByArr(
  arr: Array<any>,
  arr2: Array<any>,
  res: Array<any>,
  startNS: number,
  endNS: number,
  frame: any,
  groupBy10MS: boolean
) {
  let list = groupBy10MS ? arr2 : arr;
  let pns = (endNS - startNS) / frame.width;
  let y = frame.y;
  for (let i = 0, len = list.length; i < len; i++) {
    let it = list[i];
    if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
      if (!list[i].frame) {
        list[i].frame = {};
        list[i].frame.y = y;
      }
      list[i].frame.height = it.height;
      HiPerfStruct.setFrame(list[i], pns, startNS, endNS, frame);
      setResultArr(groupBy10MS, list, i, res);
    }
  }
}

function setResultArr(groupBy10MS: boolean, list: Array<any>, i: number, res: Array<any>) {
  if (groupBy10MS) {
    let flag: boolean =
      i > 0 &&
      (list[i - 1].frame.x || 0) === (list[i].frame.x || 0) &&
      (list[i - 1].frame.width || 0) === (list[i].frame.width || 0) &&
      (list[i - 1].frame.height || 0) === (list[i].frame.height || 0);
    if (!flag) {
      res.push(list[i]);
    }
  } else {
    if (!(i > 0 && Math.abs((list[i - 1].frame.x || 0) - (list[i].frame.x || 0)) < 4)) {
      res.push(list[i]);
    }
  }
}

export function hiPerf2(filter: Array<any>, startNS: number, endNS: number, frame: any): void {
  if (filter.length > 0) {
    let pns = (endNS - startNS) / frame.width;
    let y = frame.y;
    for (let i = 0; i < filter.length; i++) {
      let it = filter[i];
      if ((it.startNS || 0) + (it.dur || 0) > startNS && (it.startNS || 0) < endNS) {
        if (!it.frame) {
          it.frame = {};
          it.frame.y = y;
        }
        it.frame.height = it.height;
        HiPerfStruct.setFrame(it, pns, startNS, endNS, frame);
      } else {
        it.frame = null;
      }
    }
    return;
  }
}

export class HiPerfStruct extends BaseStruct {
  static hoverStruct: HiPerfStruct | undefined;
  static selectStruct: HiPerfStruct | undefined;
  id: number | undefined;
  callchain_id: number | undefined;
  timestamp: number | undefined;
  thread_id: number | undefined;
  event_count: number | undefined;
  event_type_id: number | undefined;
  cpu_id: number | undefined;
  thread_state: string | undefined;
  startNS: number | undefined;
  endNS: number | undefined;
  dur: number | undefined;
  height: number | undefined;
  eventCount: number | undefined;
  sampleCount: number | undefined;

  static drawRoundRectPath(cxt: Path2D, x: number, y: number, width: number, height: number, radius: number) {
    cxt.arc(x + width - radius, y + height - radius, radius, 0, Math.PI / 2);
    cxt.lineTo(x + radius, y + height);
    cxt.arc(x + radius, y + height - radius, radius, Math.PI / 2, Math.PI);
    cxt.lineTo(x, y + radius);
    cxt.arc(x + radius, y + radius, radius, Math.PI, (Math.PI * 3) / 2);
    cxt.lineTo(x + width - radius, y);
    cxt.arc(x + width - radius, y + radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
    cxt.lineTo(x + width, y + height - radius);
    cxt.moveTo(x + width / 3, y + height / 5);
    cxt.lineTo(x + width / 3, y + (height / 5) * 4);
    cxt.moveTo(x + width / 3, y + height / 5);
    cxt.bezierCurveTo(
      x + width / 3 + 7,
      y + height / 5 - 2,
      x + width / 3 + 7,
      y + height / 5 + 6,
      x + width / 3,
      y + height / 5 + 4
    );
  }

  static draw(
    ctx: CanvasRenderingContext2D,
    normalPath: Path2D,
    specPath: Path2D,
    data: any,
    groupBy10MS: boolean,
    textMetrics?: TextMetrics
  ) {
    if (data.frame) {
      if (groupBy10MS) {
        let width = data.frame.width;
        normalPath.rect(data.frame.x, 40 - (data.height || 0), width, data.height || 0);
      } else {
        data.frame.width > 4 ? (data.frame.width = 4) : (data.frame.width = data.frame.width);
        let path = data.callchain_id === -1 ? specPath : normalPath;
        path.moveTo(data.frame.x + 7, 20);
        if (textMetrics) {
          ctx.fillText('🄿', data.frame.x - textMetrics!.width / 2, 26); //℗©®℗®🄿
        } else {
          HiPerfStruct.drawRoundRectPath(path, data.frame.x - 7, 20 - 7, 14, 14, 3);
        }
        path.moveTo(data.frame.x, 27);
        path.lineTo(data.frame.x, 33);
      }
    }
  }

  static drawSpecialPath(ctx: CanvasRenderingContext2D, specPath: Path2D) {
    ctx.strokeStyle = '#9fafc4';
    ctx.globalAlpha = 0.5;
    ctx.stroke(specPath);
    ctx.globalAlpha = 1;
  }

  static setFrame(node: any, pns: number, startNS: number, endNS: number, frame: any) {
    if ((node.startNS || 0) < startNS) {
      node.frame.x = 0;
    } else {
      node.frame.x = Math.floor(((node.startNS || 0) - startNS) / pns);
    }
    if ((node.startNS || 0) + (node.dur || 0) > endNS) {
      node.frame.width = frame.width - node.frame.x;
    } else {
      node.frame.width = Math.ceil(((node.startNS || 0) + (node.dur || 0) - startNS) / pns - node.frame.x);
    }
    if (node.frame.width < 1) {
      node.frame.width = 1;
    }
  }

  static groupBy10MS(
    groupArray: Array<any>,
    intervalPerf: number,
    maxCpu?: number | undefined,
    usage?: boolean,
    event?: number
  ): Array<any> {
    let maxEventCount = 0;
    let obj = filterGroupArray(groupArray, maxEventCount, usage, event);
    let arr = [];
    for (let aKey in obj) {
      let ns = parseInt(aKey);
      let height: number = 0;
      if (usage) {
        if (maxCpu != undefined) {
          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf) / maxCpu) * 40);
        } else {
          height = Math.floor((obj[aKey].sampleCount / (10 / intervalPerf)) * 40);
        }
      } else {
        height = Math.floor((obj[aKey].eventCount / maxEventCount) * 40);
      }
      arr.push({
        startNS: ns,
        dur: 10_000_000,
        eventCount: obj[aKey].eventCount,
        sampleCount: obj[aKey].sampleCount,
        height: height,
      });
    }
    return arr;
  }
}
function filterGroupArray(groupArray: Array<any>, maxEventCount: number, usage?: boolean, event?: number) {
  return groupArray
    .map((it) => {
      it.timestamp_group = Math.trunc(it.startNS / 10_000_000) * 10_000_000;
      return it;
    })
    .reduce((pre: any, current) => {
      if (usage || current.event_type_id === event || event === -1) {
        if (pre[current['timestamp_group']]) {
          pre[current['timestamp_group']].sampleCount += 1;
          pre[current['timestamp_group']].eventCount += current.event_count;
        } else {
          pre[current['timestamp_group']] = {
            sampleCount: 1,
            eventCount: current.event_count,
          };
        }
        maxEventCount = Math.max(pre[current['timestamp_group']].eventCount, maxEventCount);
      }
      return pre;
    }, {});
}

function setMemFrame(node: any, padding: number, startNS: number, endNS: number, totalNS: number, frame: any) {
  let x1: number;
  let x2: number;
  if ((node.startTime || 0) <= startNS) {
    x1 = 0;
  } else {
    x1 = ns2x(node.startTime || 0, startNS, endNS, totalNS, frame);
  }
  if ((node.startTime || 0) + (node.duration || 0) >= endNS) {
    x2 = frame.width;
  } else {
    x2 = ns2x((node.startTime || 0) + (node.duration || 0), startNS, endNS, totalNS, frame);
  }
  let getV: number = x2 - x1 <= 1 ? 1 : x2 - x1;
  if (!node.frame) {
    node.frame = {};
  }
  node.frame.x = Math.floor(x1);
  node.frame.y = Math.floor(frame.y + padding);
  node.frame.width = Math.ceil(getV);
  node.frame.height = Math.floor(frame.height - padding * 2);
}

export function mem(
  list: Array<any>,
  memFilter: Array<any>,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: any,
  use: boolean
) {
  if (use && memFilter.length > 0) {
    for (let i = 0, len = memFilter.length; i < len; i++) {
      if (
        (memFilter[i].startTime || 0) + (memFilter[i].duration || 0) > startNS &&
        (memFilter[i].startTime || 0) < endNS
      ) {
        setMemFrame(memFilter[i], 5, startNS, endNS, totalNS, frame);
      } else {
        memFilter[i].frame = null;
      }
    }
    return;
  }
  memFilter.length = 0;
  setMemFilter(list, memFilter, startNS, endNS, totalNS, frame);
}
function setMemFilter(
  list: Array<any>,
  memFilter: Array<any>,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: any
) {
  if (list) {
    for (let i = 0, len = list.length; i < len; i++) {
      let it = list[i];
      if ((it.startTime || 0) + (it.duration || 0) > startNS && (it.startTime || 0) < endNS) {
        setMemFrame(list[i], 5, startNS, endNS, totalNS, frame);
        if (
          i > 0 &&
          (list[i - 1].frame?.x || 0) === (list[i].frame?.x || 0) &&
          (list[i - 1].frame?.width || 0) === (list[i].frame?.width || 0)
        ) {
        } else {
          memFilter.push(list[i]);
        }
      }
    }
  }
}

export function drawWakeUpList(
  wakeUpListContext: CanvasRenderingContext2D | any,
  wake: WakeupBean | undefined | null,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: Rect,
  wakeup: WakeupBean | undefined = undefined,
  currentCpu: number | undefined | null = undefined,
  noVerticalLine = false
) {
  if (wake) {
    let x1 = Math.floor(ns2x(wake.wakeupTime || 0, startNS, endNS, totalNS, frame));
    wakeUpListContext.beginPath();
    wakeUpListContext.lineWidth = 2;
    wakeUpListContext.fillStyle = '#000000';
    if (x1 > 0 && x1 < frame.x + frame.width) {
      if (!noVerticalLine) {
        wakeUpListContext.moveTo(x1, frame.y);
        wakeUpListContext.lineTo(x1, frame.y + frame.height);
      }
      if (currentCpu === wake.cpu) {
        let centerY = Math.floor(frame.y + frame.height / 2);
        wakeUpListContext.moveTo(x1, centerY - 6);
        wakeUpListContext.lineTo(x1 + 4, centerY);
        wakeUpListContext.lineTo(x1, centerY + 6);
        wakeUpListContext.lineTo(x1 - 4, centerY);
        wakeUpListContext.lineTo(x1, centerY - 6);
        wakeUpListContext.fill();
      }
    }
    if (wakeup) {
      drawWakeUpListIfWakeUp(wakeUpListContext, wake, startNS, endNS, totalNS, frame, wakeup, x1);
    }
    wakeUpListContext.strokeStyle = '#000000';
    wakeUpListContext.stroke();
    wakeUpListContext.closePath();
  }
}

function drawWakeUpListIfWakeUp(
  wakeUpListContext: CanvasRenderingContext2D | any,
  wake: any,
  startNS: number,
  endNS: number,
  totalNS: number,
  frame: Rect,
  wakeup: any,
  x1: number
) {
  let x2 = Math.floor(ns2x(wakeup.ts || 0, startNS, endNS, totalNS, frame));
  let y = frame.y + frame.height - 10;
  wakeUpListContext.moveTo(x1, y);
  wakeUpListContext.lineTo(x2, y);
  wakeUpListContext.moveTo(x2, y - 25);
  wakeUpListContext.lineTo(x2, y + 5);
  let s = ns2s((wakeup.ts || 0) - (wake.wakeupTime || 0));
  let wakeUpListDistance = x2 - x1;
  if (wakeUpListDistance > 12) {
    wakeUpListContext.moveTo(x1, y);
    wakeUpListContext.lineTo(x1 + 6, y - 3);
    wakeUpListContext.moveTo(x1, y);
    wakeUpListContext.lineTo(x1 + 6, y + 3);
    wakeUpListContext.moveTo(x2, y);
    wakeUpListContext.lineTo(x2 - 6, y - 3);
    wakeUpListContext.moveTo(x2, y);
    wakeUpListContext.lineTo(x2 - 6, y + 3);
    let measure = wakeUpListContext.measureText(s);
    let tHeight = measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent;
    let xStart = x1 + Math.floor(wakeUpListDistance / 2 - measure.width / 2);
    if (wakeUpListDistance > measure.width + 4) {
      wakeUpListContext.fillStyle = '#ffffff';
      wakeUpListContext.fillRect(xStart - 2, y - 4 - tHeight, measure.width + 4, tHeight + 4);
      wakeUpListContext.font = '10px solid';
      wakeUpListContext.fillStyle = '#000000';
      wakeUpListContext.textBaseline = 'bottom';
      wakeUpListContext.fillText(s, xStart, y - 2);
    }
  }
}

export function findSearchNode(data: any[], search: string, parentSearch: boolean): void {
  search = search.toLocaleLowerCase();
  data.forEach((node) => {
    if ((node.symbolName && node.symbolName.toLocaleLowerCase().includes(search) && search !== '') || parentSearch) {
      node.searchShow = true;
      node.isSearch = node.symbolName != undefined && node.symbolName.toLocaleLowerCase().includes(search);
      let parentNode = node.parent;
      while (parentNode && !parentNode.searchShow) {
        parentNode.searchShow = true;
        parentNode = parentNode.parent;
      }
    } else {
      node.searchShow = false;
      node.isSearch = false;
    }
    if (node.children.length > 0) {
      findSearchNode(node.children, search, node.searchShow);
    }
  });
}