<template>
  <v-container>
    <v-card>
      <v-card-title>
        协议测试
      </v-card-title>
      <v-card-text>
        <v-row justify="start">
          <v-col cols="6">
            <v-text-field v-model="ws_url"
                          label="服务端连接地址"
                          variant="outlined"
                          clearable
                          :error="error"
                          :error-messages="error_message"
                          :disabled="isconnect || connecting"
                          clear-icon="mdi-cancel"></v-text-field>
          </v-col>
          <v-col cols="3">
            <v-btn flat
                   :loading="connecting"
                   @click="isconnect ? disconnect():connect()"
                   color="secondary">
              {{isconnect ? '断开':'连接'}}
            </v-btn>
          </v-col>
        </v-row>
        <v-divider inset></v-divider>
        <v-row class="mt-5">
          <v-col cols="2">
            <v-select v-model="mainSelect"
                      :items="proto_models"
                      item-title="mainType"
                      item-value="subs"
                      label="mainType"
                      :disabled="!isconnect"
                      @update:modelValue="mainSelectUpdate"
                      single-line
                      return-object></v-select>
          </v-col>
          <v-col cols="2">
            <v-select v-model="subSelect"
                      :items="mainSelect.subs"
                      item-title="subType"
                      item-value="reqproto"
                      label="subType"
                      :disabled="!isconnect || mainSelect.disabled"
                      @update:modelValue="subSelectUpdate"
                      single-line
                      return-object></v-select>
          </v-col>
        </v-row>
        <v-divider inset></v-divider>
        <v-row v-if="reqproto.length > 0"
               class="mt-5">
          <v-col v-for="item in reqproto"
                 :key="item.FieldName"
                 cols="4">

            <v-card>
              <v-card-title>{{item.FieldName}}</v-card-title>
              <v-card-text>
                <template v-if="item.FieldType == 'number' || item.FieldType == 'text'">
                  <v-text-field color="tertiary"
                                v-model="item.FieldValue"
                                :label="item.FieldName"
                                :type="item.FieldType"
                                :hint="item.FieldType"
                                :disabled="!isconnect"
                                outlined
                                clearable></v-text-field>
                </template>
                <template v-else-if="item.FieldType == 'bool'">
                  <v-checkbox v-model="item.FieldValue"
                              :label="item.FieldName"></v-checkbox>
                </template>
                <template v-else-if="item.FieldType == 'enum'">
                  <v-select v-model="item.FieldValue"
                            :items="item.FieldRange"
                            item-title="name"
                            item-value="value"
                            :label="item.FieldName"></v-select>
                </template>
                <template v-else>
                  <Vue3JsonEditor v-model="item.FieldValue"
                                  @json-change="(v)=>{item.FieldValue = v}"
                                  :showBtns="false"
                                  :mode="'code'"
                                  lang="zh"
                                  :expandedOnStart="false"></Vue3JsonEditor>
                </template>

              </v-card-text>
            </v-card>
          </v-col>
        </v-row>
        <v-row class="mt-5">
          <v-col cols="3">
            <v-btn flat
                   class="text-capitalize"
                   :disabled="subSelect == null"
                   @click="reqprotohandle"
                   color="secondary">
              request
            </v-btn>
          </v-col>
        </v-row>
      </v-card-text>
    </v-card>
    <v-card class="mt-12">
      <v-card-title>
        <v-row justify="space-between">
          <v-col>输出</v-col>
          <v-col cols="auto">
            <v-btn flat
                   prepend-icon="mdi-broom"
                   @click="logs=[]"
                   color="secondary">
              Clear
            </v-btn>
          </v-col>
        </v-row>
      </v-card-title>
      <v-divider></v-divider>
      <v-responsive class="overflow-y-auto"
                    height="500">
        <v-lazy :options="{threshold: .5}"
                min-height="200"
                transition="fade-transition">

          <v-list>
            <v-list-item v-for="log in logs"
                         :key="log">
              <v-alert :type="log.type"
                       variant="outlined"
                       :title="log.title">
                <template v-if="log.data"
                          v-slot:append>
                  <v-list-item-avatar end>
                    <v-btn variant="text"
                           size="x-large"
                           @click="log.isshow = !log.isshow"
                           :icon="log.isshow ? 'mdi-chevron-left':'mdi-chevron-down'"></v-btn>
                  </v-list-item-avatar>
                </template>
                <div v-if="log.data"
                     v-show="log.isshow">
                  <v-divider></v-divider>
                  <pre>{{log.data}}</pre>
                </div>
              </v-alert>
            </v-list-item>
          </v-list>
        </v-lazy>
      </v-responsive>
    </v-card>
    <v-snackbar v-model="error">
      {{ error_message }}
      <template v-slot:actions>
        <v-btn color="pink"
               variant="text"
               @click="error = false">
          Close
        </v-btn>
      </template>
    </v-snackbar>
  </v-container>
</template>

<script>
import { Base64 } from 'js-base64'
import md5 from 'js-md5'
import { reactive } from 'vue'
import proto from '@/pb/proto'
import { socket } from '@/utils/socket'
import { Vue3JsonEditor } from 'vue3-json-editor'
import { defineComponent } from 'vue'

export default defineComponent({
  name: 'Protocol',
  components: { Vue3JsonEditor },
  data() {
    return {
      ws_url: 'ws://localhost:7891/gateway',
      isconnect: false,
      connecting: false,
      error: false,
      error_message: '',
      proto_models: null,
      mainSelect: { protoName: '', mainType: 'User', subs: [] },
      subSelect: null,
      reqproto: [],
      reqloading: false,
      islogin: false,
      account: '',
      serverId: 0,
      logs: [],
      isshow: true,
      showdata: { name: 'liwei1dao', aog: 123 },
      resultInfo: { li: 123 },
      timer: null,
    }
  },
  computed: {},
  setup() {
    const proto_models = reactive([
      { mainType: 'notify', disabled: false, subs: [] },
      { mainType: 'user', subs: [] },
      { mainType: 'items', subs: [] },
      { mainType: 'mail', subs: [] },
      { mainType: 'friend', subs: [] },
      { mainType: 'equipment', subs: [] },
      { mainType: 'hero', subs: [] },
      { mainType: 'shop', subs: [] },
      { mainType: 'stak', subs: [] },
      { mainType: 'mainline', subs: [] },
      { mainType: 'chat', subs: [] },
      { mainType: 'gm', subs: [] },
      { mainType: 'forum', subs: [] },
      { mainType: 'pagoda', subs: [] },
      { mainType: 'martialhall', subs: [] },
      { mainType: 'gourmet', subs: [] },
      { mainType: 'smithy', subs: [] },
      { mainType: 'moonfantasy', subs: [] },
      { mainType: 'viking', subs: [] },
      { mainType: 'hunting', subs: [] },
      { mainType: 'horoscope', subs: [] },
      { mainType: 'pay', subs: [] },
      { mainType: 'arena', subs: [] },
      { mainType: 'reddot', subs: [] },
      { mainType: 'combat', subs: [] },
      { mainType: 'library', subs: [] },
      { mainType: 'practice', subs: [] },
      { mainType: 'parkour', subs: [] },
    ])
    for (const v of Object.keys(proto)) {
      for (const v1 of proto_models) {
        // console.log('protoName', v)
        let protoName = v.toLowerCase()
        //找到模块的消息对象
        let index = protoName.indexOf(v1.mainType)
        if (index == 0) {
          let reqindex = protoName.indexOf('req')
          let respindex = protoName.indexOf('resp')
          let pushindex = protoName.indexOf('push')
          if (reqindex != -1) {
            let sub = protoName.substring(index + v1.mainType.length, reqindex)
            let iskeep = false
            for (const v2 of v1.subs) {
              if (v2.subType == sub) {
                v2.reqproto = proto[v]
                iskeep = true
                break
              }
            }
            if (!iskeep) {
              v1.subs.push({
                reqName: v,
                mainType: v1.mainType,
                subType: sub,
                reqproto: proto[v],
              })
              // console.log(sub + 'Req')
            }
          }
          if (respindex != -1) {
            let sub = protoName.substring(index + v1.mainType.length, respindex)
            let iskeep = false
            for (const v2 of v1.subs) {
              if (v2.subType == sub) {
                v2.respName = v
                v2.respproto = proto[v]
                iskeep = true
                break
              }
            }
            if (!iskeep) {
              v1.subs.push({
                respName: v,
                mainType: v1.mainType,
                subType: sub,
                respproto: proto[v],
              })
              // console.log(sub + 'Resp')
            }
          }
          if (pushindex != -1) {
            let sub = protoName.substring(index + v1.mainType.length, pushindex)
            let iskeep = false
            for (const v2 of v1.subs) {
              if (v2.subType == sub) {
                v2.pushName = v
                v2.pushproto = proto[v]
                iskeep = true
                break
              }
            }
            if (!iskeep) {
              v1.subs.push({
                pushName: v,
                mainType: v1.mainType,
                subType: sub,
                pushproto: proto[v],
              })
              // console.log(sub + 'Resp')
            }
          }
        }
      }
    }
    return { proto_models }
  },
  onBeforeUnmount() {
    // 关闭连接
    socket.close()
  },
  methods: {
    connect() {
      // 发起连接
      this.connecting = true
      socket.receivelisten = this.receivelisten

      socket
        .init(this.ws_url)
        .then((value) => {
          this.error = false
          this.error_message = ''
          this.isconnect = true
          this.connecting = false
          this.pushlog('success', `连接创建成功`)
          socket.closelisten = this.closelisten
          this.timer = setInterval(this.heartbeat, 30000)
        })
        .catch((error) => {
          this.error = true
          this.error_message = error
          this.isconnect = false
          this.connecting = false
          this.subSelect = null
          this.reqproto = []
          this.pushlog('error', `连接创建失败:${this.ws_url}`)
        })
    },
    disconnect() {
      socket.close()
      this.isconnect = false
      this.subSelect = null
      this.reqproto = []
      clearInterval(this.timer)
    },
    heartbeat() {
      let protoobj = proto.lookup('GatewayHeartbeatReq')
      let message = proto.lookup('UserMessage')
      let any = proto.lookup('google.protobuf.Any')
      message.MainType = 'gateway'
      message.SubType = 'heartbeat'
      message.data = any.create({
        type_url: `type.googleapis.com/GatewayHeartbeatReq`,
        value: proto.lookup('GatewayHeartbeatReq').encode(protoobj).finish(), // Or as packed buffer
      })
      // if (this.subSelect.reqName == 'UserLoginReq') {
      //   //登录协议特殊处理
      //   message.sec = this.buildsecStr(protoobj.account, protoobj.sid)
      // } else {
      //   message.sec = this.buildsecStr()
      // }
      let buffer = proto.lookup('UserMessage').encode(message).finish()
      socket.send(buffer)
    },
    mainSelectUpdate(v) {
      console.log(v)
      this.subSelect = null
      this.reqproto = []
    },
    subSelectUpdate(v) {
      this.reqproto = []
      let keys = Object.keys(this.subSelect.reqproto.fields)
      for (const v of keys) {
        let rule = this.subSelect.reqproto.fields[v].rule
        let keyType = this.subSelect.reqproto.fields[v].keyType
        let ftype = this.subSelect.reqproto.fields[v].type
        let range = []
        let fvalue = null

        if (!rule && !keyType) {
          switch (ftype) {
            case 'int32':
            case 'int64':
            case 'uint32':
            case 'uint64':
            case 'float32':
            case 'float64':
              ftype = 'number'
              fvalue = 0
              break
            case 'string':
              ftype = 'text'
              fvalue = ''
              break
            case 'bool':
              ftype = 'bool'
              fvalue = false
              break
            default:
              let data = proto.lookup(ftype)
              if (data == null) {
                ftype = 'object'
                fvalue = {}
              } else {
                if (data.values) {
                  //是枚举
                  ftype = 'enum'
                  for (let key of Object.keys(data.values)) {
                    range.push({ name: key, value: data.values[key] })
                  }
                  fvalue = range[0]
                } else {
                  ftype = 'object'
                  fvalue = {}
                }
              }
              break
          }
        } else {
          ftype = 'object'
          fvalue = {}
        }
        console.log('ftype', ftype)
        this.reqproto.push({
          FieldName: v,
          FieldType: ftype,
          FieldRange: range,
          FieldValue: fvalue,
        })
      }
      // console.log(this.reqproto)
    },
    closelisten() {
      this.isconnect = false
      this.subSelect = null
      this.reqproto = []
      console.log('closelisten!')
      this.pushlog('error', `连接断开${this.ws_url}`)
    },
    //接收服务的消息
    receivelisten(msg) {
      // console.log('接收到消息', msg.data.arrayBuffer())
      msg.data
        .arrayBuffer()
        .then((buf) => new Uint8Array(buf))
        .then((bytes) => {
          console.log('bytes', bytes)
          let message = proto.lookup('UserMessage').decode(bytes)
          let mainType = message.MainType
          let subType = message.SubType
          let iskeep = false
          if (mainType == 'gateway' && subType == 'heartbeat') {
            return
          }
          console.log('接收到消息 message', message)
          for (const v of this.proto_models) {
            if (v.mainType == mainType) {
              for (const v1 of v.subs) {
                if (subType == v1.subType) {
                  console.log('接收到消息 v1', v1)
                  //找到消息组
                  iskeep = true
                  let protoobj = null
                  let messageobj = null
                  if (v1.respName) {
                    console.log('接收到消息 respName', v1.respName)
                    protoobj = proto
                      .lookup(v1.respName)
                      .decode(message.data.value)
                    messageobj = proto.lookup(v1.respName).toObject(protoobj)
                    this.pushlog(
                      'success',
                      `收到回应消息: ${mainType}-${subType}-resp`,
                      messageobj
                    )
                  } else {
                    console.log('接收到消息 pushName', v1.pushName)
                    protoobj = proto
                      .lookup(v1.pushName)
                      .decode(message.data.value)
                    messageobj = proto.lookup(v1.pushName).toObject(protoobj)

                    if (v1.pushName && v1.pushName == 'NotifyErrorNotifyPush') {
                      this.pushlog(
                        'error',
                        `错误信息通知: ${mainType}-${subType}-push`,
                        messageobj
                      )
                    } else {
                      this.pushlog(
                        'success',
                        `收到回应消息: ${mainType}-${subType}-push`,
                        messageobj
                      )
                    }
                  }
                }
              }
            }
            if (iskeep) {
              break
            }
          }
          if (!iskeep) {
            this.pushlog(
              'error',
              `接收消息错误 ${mainType}-${subType}`,
              '未找到指定消息对象'
            )
          }
        })
        .catch((error) => {
          console.log('接收到消息异常', error)
          this.pushlog('error', '接收消息异常', error)
        })
    },
    //请求消息
    reqprotohandle() {
      console.log('protoName:', this.subSelect.reqName)
      let protoobj = proto.lookup(this.subSelect.reqName)
      for (const v of this.reqproto) {
        protoobj[v.FieldName] = v.FieldValue
      }
      let message = proto.lookup('UserMessage')
      let any = proto.lookup('google.protobuf.Any')
      message.MainType = this.subSelect.mainType
      message.SubType = this.subSelect.subType
      message.data = any.create({
        type_url: `type.googleapis.com/${this.subSelect.reqName}`,
        value: proto.lookup(this.subSelect.reqName).encode(protoobj).finish(), // Or as packed buffer
      })
      if (this.subSelect.reqName == 'UserLoginReq') {
        //登录协议特殊处理
        message.sec = this.buildsecStr(protoobj.account, protoobj.sid)
      } else {
        message.sec = this.buildsecStr()
      }
      console.log('message:', message)
      let buffer = proto.lookup('UserMessage').encode(message).finish()
      console.log('req buff:', buffer)
      socket.send(buffer)
      this.pushlog(
        'success',
        `发送请求消息: ${message.MainType}-${message.SubType}-req`,
        proto.lookup(this.subSelect.reqName).toObject(protoobj)
      )
    },
    buildsecStr(_account, _serverId) {
      _account = _account ? _account : this.account
      _serverId = _serverId ? _serverId : this.serverId
      let jsonStr = JSON.stringify({
        account: _account,
        serverId: _serverId,
        timestamp: Date.parse(new Date()) / 1000,
      })
      let baseStr = Base64.encode(jsonStr)
      let clientMd5key = md5(baseStr)
      console.log('clientMd5key:', clientMd5key)
      return `CE:${clientMd5key}${baseStr}`
    },
    pushlog(_type, _title, _data) {
      this.logs.push({
        type: _type,
        title: _title,
        data: _data,
        isshow: false,
      })
    },

    onJsonChange(value) {
      console.log('value:', value)
    },
  },
})
</script>
