<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref } from 'vue';
import { PageContainer } from 'antdv-pro-layout';
import { message, Modal } from 'ant-design-vue/es';
import DissectionTree from '../tshark/components/DissectionTree.vue';
import DissectionDump from '../tshark/components/DissectionDump.vue';
import PacketTable from '../tshark/components/PacketTable.vue';
import {
  RESULT_CODE_ERROR,
  RESULT_CODE_SUCCESS,
} from '@/constants/result-constants';
import { filePullTask } from '@/api/trace/task';
import { OptionsType, WS } from '@/plugins/ws-websocket';
import useI18n from '@/hooks/useI18n';
import saveAs from 'file-saver';
import {
  packetDevices,
  packetStart,
  packetStop,
  packetFilter,
  packetKeep,
} from '@/api/trace/packet';
const ws = new WS();
const { t } = useI18n();

const NO_SELECTION = { id: '', idx: 0, start: 0, length: 0 };

type StateType = {
  /**网卡设备列表 */
  devices: { id: string; label: string; children: any[] }[];
  /**初始化 */
  initialized: boolean;
  /**保活调度器 */
  keepTimer: any;
  /**任务 */
  task: {
    taskNo: string;
    device: string;
    filter: string;
    outputPCAP: boolean;
  };
  /**字段 */
  columns: string[];

  /**过滤条件 */
  filter: string;
  /**过滤条件错误信息 */
  filterError: string | null;

  /**当前选中的帧编号 */
  selectedFrame: number;
  /**当前选中的帧数据 */
  packetFrame: { tree: any[]; data_sources: any[] };
  /**pcap包帧数据 */
  packetFrameTreeMap: Map<string, any> | null;
  /**当前选中的帧数据 */
  selectedTree: typeof NO_SELECTION;
  /**选择帧的Dump数据标签 */
  selectedDataSourceIndex: number;

  /**包总数 */
  totalPackets: number;
  /**包数据 */
  packetList: any[];
};

const state = reactive<StateType>({
  devices: [],
  initialized: false,
  keepTimer: null,
  task: {
    taskNo: 'laYlTbq',
    device: '192.168.5.58',
    filter: 'tcp and (port 33030 or 8080)',
    outputPCAP: false,
  },
  columns: [
    'No.',
    'Time',
    'Source',
    'Destination',
    'Protocol',
    'Length',
    'Info',
  ],
  filter: 'tcp and (port 33030 or 8080)',
  filterError: null,
  selectedFrame: 1,
  /**当前选中的帧数据 */
  packetFrame: { tree: [], data_sources: [] },
  packetFrameTreeMap: null, // 注意：Map 需要额外处理
  selectedTree: NO_SELECTION, // NO_SELECTION 需要定义
  /**选择帧的Dump数据标签 */
  selectedDataSourceIndex: 0,
  // 包数据
  totalPackets: 0,
  packetList: [],
});

/**清除帧数据和报文信息状态 */
function fnReset() {
  state.initialized = false;
  // 选择帧的数据
  state.selectedFrame = 0;
  state.packetFrame = { tree: [], data_sources: [] };
  state.packetFrameTreeMap = null;
  state.selectedTree = NO_SELECTION;
  state.selectedDataSourceIndex = 0;
  // 过滤条件
  state.filter = 'tcp and (port 33030 or 8080)';
  state.filterError = null;
  // 包数据
  state.totalPackets = 0;
  state.packetList = [];
}

/**解析帧数据为简单结构 */
function parseFrameTree(id: string, node: Record<string, any>) {
  let map = new Map();

  if (node.tree && node.tree.length > 0) {
    for (let i = 0; i < node.tree.length; i++) {
      const subMap = parseFrameTree(`${id}-${i}`, node.tree[i]);
      subMap.forEach((value, key) => {
        map.set(key, value);
      });
    }
  } else if (node.length > 0) {
    map.set(id, {
      id: id,
      idx: node.data_source_idx,
      start: node.start,
      length: node.length,
    });
  }

  return map;
}

/**帧数据点击选中 */
function handleSelectedTreeEntry(e: any) {
  console.log('fnSelectedTreeEntry', e);
  state.selectedTree = e;
}
/**报文数据点击选中 */
function handleSelectedFindSelection(src_idx: number, pos: number) {
  console.log('fnSelectedFindSelection', pos);
  if (state.packetFrameTreeMap == null) return;
  // find the smallest one
  let current = null;
  for (let [k, pp] of state.packetFrameTreeMap) {
    if (pp.idx !== src_idx) continue;

    if (pos >= pp.start && pos <= pp.start + pp.length) {
      if (
        current != null &&
        state.packetFrameTreeMap.get(current).length > pp.length
      ) {
        current = k;
      } else {
        current = k;
      }
    }
  }
  if (current != null) {
    state.selectedTree = state.packetFrameTreeMap.get(current);
  }
}

/**包数据表点击选中 */
function handleSelectedFrame(num: number) {
  console.log('fnSelectedFrame', num, state.totalPackets);
  const packet = state.packetList.find((v: any) => v.number === num);
  if (!packet) return;
  const packetFrame = packet.frame;
  state.selectedFrame = packet.number;
  state.packetFrame = packetFrame;
  state.packetFrameTreeMap = parseFrameTree('root', packetFrame);
  state.selectedTree = NO_SELECTION;
  state.selectedDataSourceIndex = 0;
}
/**包数据表滚动底部加载 */
function handleScrollBottom(index: any) {
  console.log('handleScrollBottom', index);
}

/**开始跟踪 */
function fnStart() {
  // state.task.taskNo = 'laYlTbq';
  state.task.taskNo = Number(Date.now()).toString(16);
  state.task.outputPCAP = false;
  packetStart(state.task).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      fnReset();
      fnWS();
    } else {
      message.error(t('common.operateErr'), 3);
    }
  });
}

/**停止跟踪 */
function fnStop() {
  packetStop(state.task.taskNo).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      ws.close();
      state.initialized = false;
      state.filter = '';
      state.filterError = null;
    } else {
      message.warning(res.msg, 3);
    }
  });
}

/**跟踪数据表过滤 */
function handleFilterFrames() {
  packetFilter(state.task.taskNo, state.filter).then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      state.task.filter = state.filter;
    } else {
      state.filterError = res.msg;
    }
  });
}

/**开始跟踪 */
function fnDevice(v: string) {
  state.task.device = v;
}

/**下载触发等待 */
let downLoading = ref<boolean>(false);

/**信息文件下载 */
function fnDownloadPCAP() {
  if (downLoading.value) return;
  const fileName = `trace_packet_${state.task.taskNo}.pcap`;
  Modal.confirm({
    title: t('common.tipTitle'),
    content: t('views.logManage.neFile.downTip', { fileName }),
    onOk() {
      downLoading.value = true;
      const hide = message.loading(t('common.loading'), 0);
      filePullTask(state.task.taskNo)
        .then(res => {
          if (res.code === RESULT_CODE_SUCCESS) {
            message.success({
              content: t('common.msgSuccess', {
                msg: t('common.downloadText'),
              }),
              duration: 2,
            });
            saveAs(res.data, `${fileName}`);
          } else {
            message.error({
              content: t('views.logManage.neFile.downTipErr'),
              duration: 2,
            });
          }
        })
        .finally(() => {
          hide();
          downLoading.value = false;
        });
    },
  });
}

/**接收数据后回调 */
function wsMessage(res: Record<string, any>) {
  const { code, requestId, data } = res;
  if (code === RESULT_CODE_ERROR) {
    console.warn(res.msg);
    return;
  }

  // 建联时发送请求
  if (!requestId && data.clientId) {
    state.initialized = true;
    state.keepTimer = setInterval(() => {
      packetKeep(state.task.taskNo, 120);
    }, 90 * 1000);
    return;
  }

  // 订阅组信息
  if (!data?.groupId) {
    return;
  }
  if (data.groupId === `4_${state.task.taskNo}`) {
    const packetData = data.data;
    state.totalPackets = packetData.number;
    state.packetList.push(packetData);
  }
}

/**建立WS连接 */
function fnWS() {
  const options: OptionsType = {
    url: '/ws',
    params: {
      /**订阅通道组
       *
       * 信令跟踪Packet (GroupID:4_taskNo)
       */
      subGroupID: `4_${state.task.taskNo}`,
    },
    onmessage: wsMessage,
    onerror: (ev: any) => {
      // 接收数据后回调
      console.error(ev);
    },
  };
  //建立连接
  ws.connect(options);
}

onMounted(() => {
  packetDevices().then(res => {
    if (res.code === RESULT_CODE_SUCCESS) {
      state.devices = res.data;
      if (res.data.length === 0) return;
      state.task.device = res.data[0].id;
    }
  });
});

onBeforeUnmount(() => {
  clearInterval(state.keepTimer);
  state.keepTimer = null;
  if (ws.state() === WebSocket.OPEN) ws.close();
});
</script>

<template>
  <PageContainer>
    <a-card :bordered="false" :body-style="{ padding: '12px' }">
      <div class="toolbar">
        <a-space :size="8" class="toolbar-oper">
          <a-dropdown-button
            type="primary"
            :disabled="state.initialized"
            @click="fnStart"
          >
            <PlayCircleOutlined />
            Start Trace
            <template #overlay>
              <a-menu
                @click="({ key }:any) => fnDevice(key)"
                :selectedKeys="[state.task.device]"
              >
                <a-menu-item v-for="v in state.devices" :key="v.id">
                  <a-popover placement="rightTop" trigger="hover">
                    <template #content>
                      <div v-for="c in v.children">{{ c.id }}</div>
                    </template>
                    <template #title>
                      <span>IP Address</span>
                    </template>
                    <div>{{ v.label }}</div>
                  </a-popover>
                </a-menu-item>
              </a-menu>
            </template>
            <template #icon><DownOutlined /></template>
          </a-dropdown-button>

          <a-button danger @click.prevent="fnStop()" v-if="state.initialized">
            <template #icon><CloseCircleOutlined /></template>
            Stop Trace
          </a-button>
          <a-button
            type="primary"
            :loading="downLoading"
            @click.prevent="fnDownloadPCAP()"
            v-if="state.task.outputPCAP"
          >
            <template #icon><DownloadOutlined /></template>
            {{ t('common.downloadText') }}
          </a-button>
          <a-tag
            color="green"
            v-show="!!state.task.filter && state.initialized"
          >
            {{ state.task.filter }}
          </a-tag>
        </a-space>

        <a-space :size="8" class="toolbar-info" v-show="state.initialized">
          <span>
            {{ t('views.traceManage.task.traceId') }}:&nbsp;
            <strong>{{ state.task.taskNo }}</strong>
          </span>
          <span> Packets: {{ state.totalPackets }} </span>
        </a-space>
      </div>

      <!-- 包数据表过滤 -->
      <a-input-group compact v-show="state.initialized">
        <a-input
          v-model:value="state.filter"
          placeholder="display filter, example: tcp"
          :allow-clear="true"
          style="width: calc(100% - 100px)"
          @pressEnter="handleFilterFrames"
        >
          <template #prefix>
            <FilterOutlined />
          </template>
        </a-input>
        <a-button
          type="primary"
          html-type="submit"
          style="width: 100px"
          @click="handleFilterFrames"
        >
          Filter
        </a-button>
      </a-input-group>
      <a-alert
        :message="state.filterError"
        type="error"
        v-if="state.filterError != null"
      />

      <!-- 包数据表 -->
      <PacketTable
        :columns="state.columns"
        :data="state.packetList"
        :selectedFrame="state.selectedFrame"
        :onSelectedFrame="handleSelectedFrame"
        :onScrollBottom="handleScrollBottom"
      ></PacketTable>

      <a-row>
        <a-col :lg="12" :md="12" :xs="24" class="tree">
          <!-- 帧数据 -->
          <DissectionTree
            id="root"
            :select="handleSelectedTreeEntry"
            :selected="state.selectedTree"
            :tree="state.packetFrame.tree"
          />
        </a-col>
        <a-col :lg="12" :md="12" :xs="24" class="dump">
          <!-- 报文数据 -->
          <a-tabs
            v-model:activeKey="state.selectedDataSourceIndex"
            :tab-bar-gutter="16"
            :tab-bar-style="{ marginBottom: '8px' }"
          >
            <a-tab-pane
              :key="idx"
              :tab="v.name"
              v-for="(v, idx) in state.packetFrame.data_sources"
              style="overflow: auto"
            >
              <DissectionDump
                :base64="v.data"
                :select="(pos:number)=>handleSelectedFindSelection(idx, pos)"
                :selected="
                  idx === state.selectedTree.idx
                    ? state.selectedTree
                    : NO_SELECTION
                "
              />
            </a-tab-pane>
          </a-tabs>
        </a-col>
      </a-row>
    </a-card>
  </PageContainer>
</template>

<style scoped>
.toolbar {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
}
.toolbar-oper {
  flex: 1;
}
.toolbar-info {
  text-align: right;
  padding-right: 8px;
}

.summary {
  display: flex;
  flex-direction: column;
}
.summary-item > span:first-child {
  font-weight: 600;
  margin-right: 6px;
}

.tree {
  font-size: 0.8125rem;
  line-height: 1.5rem;
  padding-bottom: 0.75rem;
  padding-top: 0.75rem;
  white-space: nowrap;
  overflow-y: auto;
  user-select: none;
  height: 100%;
}
.tree > ul.tree {
  min-height: 15rem;
}

.dump {
  padding-bottom: 0.75rem;
  padding-top: 0.75rem;
  overflow-y: auto;
  height: 100%;
}
.dump .ant-tabs-tabpane {
  min-height: calc(15rem - 56px);
}
</style>
