<template>
  <div class="mini-wrap">
    <div class="mini-body" v-show="showChat">
      <div class="box">
        <div class="box-hd">
          <div class="btn-menu" @click.stop="showMenuExtra = !showMenuExtra"></div>
          <div class="btn-close" @click="handleToggleChat(false)"></div>
          <div class="box-title">{{group.groupName}}</div>
          <div class="user-tips">
            <em>{{group.userCounts}}</em>
          </div>

          <ul class="menu-extra" v-show="showMenuExtra">
            <div class="info clearfix" v-if="account">
              <span class="avatar avatar-bg">{{account.name.slice(0,2).toUpperCase()}}</span>
              <span class="account-info">
                <em>{{account.name}}</em>
                <i class="login-out" href="javascript:void(0);" @click="handleLogout">注销</i>
              </span>
            </div>
            <li v-if="group.groupId"><a :href="`/mini.html?groupId=${group.groupId}&show=true`" target="_blank">在新网页中打开聊天<i class="icon-arrow"></i></a></li>
            <li><a href="https://www.mee.chat/" target="_blank">官网：www.mee.chat</a></li>
          </ul>
        </div>

        <chat-pin v-bind="pinMsg" @pinMsgClose="pinMsgClose" @scrollToView="scrollToView"></chat-pin>

        <div class="box-bd" :style="{height:height - (pinMsg.visible ? 92 : 62) + 'px'} " v-bar>
          <div ref="scrollWrap" @scroll="handleScroll">
            <div ref="msgWrap" class="msg-wrap">
              <div class="msg-top-more" v-if="lockEnd">
                <em>没有更多了</em>
              </div>
              <div class="msg-top-load" v-if="lockMore && !lockEnd">
                <i class="msg-loading-icon"></i>
              </div>
              <template v-if="group.chatList.length">
                <msg-item v-for="(item ,key) in group.pinList"
                :key="'pin' + key"
                v-bind="item"
                @quoteMsg="quoteMsg"
                @deleteMsg="deleteMsg"
                >
                </msg-item>
              </template>
              <msg-item v-for="item in group.chatList"
                :key="item.hash"
                v-bind="item"
                :msgItem="item"
                @quoteMsg="quoteMsg"
                @deleteMsg="deleteMsg"
              ></msg-item>
            </div>
          </div>
        </div>

        <div class="box-ft">
          <chat-at
            ref="chatAt"
            v-if="atShow"
            @atperson="atPerson"
            :curInd="atInd"
            :filterList="filterMembers">
          </chat-at>
          <div class="input-ctrl" v-if="showLoginBtn">
            <span v-if="showLoginBtn === 'loading'">登录中...</span>
            <span v-else class="enable" @click="handleLogin">登录</span>
          </div>
          <div class="input-con" v-else>
            <div class="btn-file">
              <input type="file" ref="inputFile" name="res" @change="handleFile">
            </div>
            <div class="btn-emoji" @click.stop="showEmoji = !showEmoji"></div>
            <div class="icon-packet" @click="$packetSend"></div>

            <form class="input-wrap" @submit="handleSend">
              <textarea
                @keydown.up.prevent="handleUp"
                @keydown.down.prevent="handleDown"
                cols="1"
                ref="chatInput"
                rows="1"
                @keydown.enter="handleKeyDown"
                placeholder="Write a message"
                v-model="inputMsg"
                @blur="handleBlur"
              />
            </form>
            <div class="btn-send" @click="handleSend">发送</div>
          </div>

          <div class="emoji-wrap" v-if="showEmoji">
            <emoji-list @addEmoji="addEmoji"></emoji-list>
          </div>
        </div>
      </div>
    </div>
    <div @click="handleToggleChat(true)" class="mini-control" v-show="!showChat">
      <ul v-if="unreadCounts > 0">
        <li class="msg-tips">
          <em>+{{unreadCounts}}</em>
        </li>
        <li class="user-tips">
          <em>{{group.userCounts}}</em>
        </li>
      </ul>
      <div class="meechat-icon" v-else>
        <i class="user-tips">
          <em>{{group.userCounts}}</em>
        </i>
      </div>
    </div>
  </div>
</template>

<script>
import msgItem from '@/components/msgItem'
import emojiList from '@/components/emoji'
import chatAt from '@/components/chatAt'
import chatPin from '@/components/chatPin'
import { mapActions, mapState, mapMutations } from 'vuex'
import API from '@/api'
import { getMiniWsUrl } from '@/util/contract.js'
import { isMobile } from '@/util/util.js'
import WsManager from '@/util/wsManager.js'
import PostMessager from '@/util/postMessager.js'
import ScatterJS from 'scatter-js/dist/scatter.esm'
import { Message } from 'element-ui'
import ImageMin from '@/util/imageMin.js'
import scrollIntoView from 'scroll-into-view-if-needed'
import { chatAtMixin } from '@/mixins'

export default {
  name: 'chatMini',
  mixins: [chatAtMixin],
  components: {
    msgItem,
    emojiList,
    chatAt,
    chatPin
  },
  props: {
    width: {
      type: Number,
      default: 274
    },
    height: {
      type: Number,
      default: 390
    },
    show: {
      type: Boolean,
      default: false
    },
    groupId: [Number, String]
  },
  computed: {
    ...mapState([
      'account',
      'group',
      'userId'
    ]),
    ...mapState({
      chatInputFocus: state => state.group.chatInputFocus,
      blockList: state => state.group.blockList,
      pinMsg: state => state.group.pinMsg,
      pinList: state => state.group.pinList
    }),
    filterMembers () {
      let val = this.inputMsg.replace('@', '')
      let members = this.group.members
      let resArr = []
      for (let k in this.group.members) {
        if (members[k].nick_name.indexOf(val) > -1) {
          resArr.push(members[k])
        }
      }
      return resArr
    },
    atShow () {
      return this.inputMsg.match(/^@/) && this.filterMembers.length
    }
  },
  data () {
    return {
      isMobile: isMobile(),
      showChat: !!this.show, // 显示聊天窗
      showEmoji: false, // 显示表情选择框
      showMenuExtra: false, // 显示左上角菜单
      showLoginBtn: true, // 显示登录按钮
      lockMore: false,
      lockEnd: false,
      loading: false,
      unreadCounts: 0, // 未读消息数
      inputMsg: '', // 用户输入的内容
      atInd: 0 // @人索引
    }
  },
  watch: {
    chatInputFocus (val, newval) {
      if (this.showLoginBtn) return
      let ele = this.$refs.chatInput
      if (val) {
        if (document.activeElement !== ele) {
          this.placeEnd(ele)
          ele.focus()
        }
      } else {
        if (document.activeElement === ele) {
          ele.blur()
        }
      }
    }
  },
  async mounted () {
    // 初始化postMessage消息管理器
    let callback = (event) => {
      if (event.action === 'meechat:setShow') {
        this.handleToggleChat(event.show)
      }
    }

    this.postMessager = new PostMessager('*', { callback })
    window.postMessager = this.postMessager

    // 检查登录态
    let userId = localStorage.getItem('user_id')
    let token = localStorage.getItem('token')
    let account = localStorage.getItem('account')
    if (userId && token && account) {
      this.setUserId(userId)
      this.setToken(token)
      this.setAccount(JSON.parse(account))
      this.showLoginBtn = false
    }

    // 设置groupId
    this.initGroup({
      userId: this.userId,
      groupId: this.groupId,
      useCache: false
    })

    this.$nextTick(this.initChat)

    this.$nextTick(this.initSocket)

    document.addEventListener('contextmenu', e => e.preventDefault())
    document.addEventListener('paste', this.initPaste)
    document.addEventListener('drop', this.initDrop)
    document.addEventListener('dragover', this.initDragOver)

    document.body.addEventListener('click', () => {
      this.showEmoji = false
      this.showMenuExtra = false
    })
  },
  beforeDestroy () {
    document.removeEventListener('paste', this.initPaste)
    document.removeEventListener('drop', this.initDrop)
    document.removeEventListener('dragover', this.initDragOver)
  },
  methods: {
    ...mapMutations([
      'initGroup',
      'setUserId',
      'setToken',
      'addChatItem',
      'deleteChatItem',
      'updateChatInputFocus',
      'updateGroupBlockList',
      'updateMembers',
      'updateGroupPinMsg',
      'repealChatItem',
      'addPacketItem',
      'addPacketTip'
    ]),
    ...mapActions([
      'setScatter',
      'setAccount',
      'doGameLogin',
      'doScatterLogin',
      'doScatterLoginOut',
      'getGroupInfo',
      'getNewMsg',
      'getHistoryMsg',
      'doSendMsg',
      'doSendFile'
    ]),
    // 连接socket
    initSocket () {
      if (!window.WebSocket) {
        console.error('Error: WebSocket is not supported .')
        return
      }

      let host = getMiniWsUrl() + `?group_id=${this.groupId}`
      if (this.socket) {
        this.socket.destroy()
        this.socket = null
      }

      this.socket = new WsManager(host, {
        autoConnect: true, // 自动连接
        reconnection: false, // 断开自动重连
        reconnectionDelay: 5000 // 重连间隔时间，单位秒
      })

      this.socket.on('open', res => {})

      this.socket.on('message', (data) => {
        data = JSON.parse(data)
        if (data.channel.match('chat:group')) {
          if (data.data.type === 'msg' && data.data.from != this.userId) {
            this.getNewMsg().then(() => {
              this.$nextTick(this.resizeToBottom)
            })
            // 未读消息数+1
            if (!this.showChat) {
              if (this.unreadCounts === 0) {
                this.postResize(130, 50)
              }
              this.unreadCounts++
            }
          }

          if (data.data.type === 'repeal') {
            this.repealChatItem(data.data)
          }

          if (data.data.type === 'block') {
            this.updateGroupBlockList({
              type: 'add',
              id: data.data.to
            })
          }

          if (data.data.type === 'unblock') {
            this.updateGroupBlockList({
              type: 'delete',
              id: data.data.to
            })
          }

          if (data.data.type === 'join') {
            this.updateMembers(data.data.user_info)
          }

          if (data.data.type === 'pin_msg') {
            this.updateGroupPinMsg(data.data.pinMsg)
          }

          if (data.data.type === 'unpin_msg') {
            this.updateGroupPinMsg(null)
          }

          if (data.data.type === 'new_redpack') {
            this.addPacketItem(data.data)
            this.$nextTick(this.resizeToBottom)
          }

          if (data.data.type === 'grab_redpack') {
            if (data.data.from == this.userId || data.data.to == this.userId) {
              this.addPacketTip(data.data)
              this.$nextTick(this.resizeToBottom)
            }
          }
        }
      })
    },
    /**
     * 聊天群初始化处理
     * 先后调用 group/info, group/msg
     */
    async initChat () {
      this.handleToggleChat(this.show)

      await this.getGroupInfo()

      await this.getNewMsg()

      if (this.show) {
        this.$nextTick(this.resizeToBottom)
      }
    },
    async handleLogout () {
      this.doScatterLoginOut()
      if (self !== top) {
        localStorage.removeItem('account')
        await this.postMessager.send({
          action: 'meechat:logout'
        })
      }

      this.showLoginBtn = true
    },
    /**
     * 登录处理
     */
    async handleLogin () {
      // 设置登录按钮状态
      this.showLoginBtn = 'loading'
      if (self !== top) {
        this.handleParentLogin()
      } else {
        // 连接scatter
        ScatterJS.scatter.connect('MEE_CHAT').then(async connected => {
          if (connected) {
            // 设置scatter
            this.setScatter(ScatterJS.scatter)

            // 清空全局scatter引用
            window.ScatterJS = null

            // 调起scatter授权登录
            await this.doScatterLogin().catch(e => {
              this.showLoginBtn = true
            })

            localStorage.setItem('account', JSON.stringify(this.account))

            // 签名登录
            await this.doGameLogin().catch(e => {
              this.showLoginBtn = true
            })

            this.showLoginBtn = false

            this.initGroup({
              userId: this.userId,
              groupId: this.groupId,
              useCache: false
            })
          }
        })
      }
    },
    /**
     * @des 调用父级页面getIdentity
    */
    async getParentIdentity () {
      if (self !== top) {
        let response = await this.postMessager.send({ action: 'meechat:getIdentity' })
        if (!response) {
          this.showLoginBtn = true
          return
        }

        let account = response.accounts.find(x => x.blockchain === 'eos')
        if (!account) {
          this.showLoginBtn = true
        }
        localStorage.setItem('account', JSON.stringify(account))
        this.setAccount(account)
      }
    },
    /**
     * @des 调用父级页面登录
     */
    async handleParentLogin () {
      await this.getParentIdentity()

      if (!this.account) {
        console.log('handleParentLogin, !this.account')
        this.showLoginBtn = true
        return
      }

      let publicKey = this.account.publicKey
      let response = null
      if (!publicKey) {
        // 插件版本的scatter拿不到publicKey
        response = await this.postMessager.send({
          action: 'meechat:getAccount',
          data: {
            name: this.account.name
          }
        })

        if (!response) {
          console.log('handleParentLogin, getAccount, !response')
          this.showLoginBtn = true
          return
        }

        let perm = response.permissions.find(x => x.perm_name === 'active')
        publicKey = perm.required_auth.keys[0].key
      }

      let { data } = await API.user.getRandom({ account: this.account.name })
      let random = data.data
      response = await this.postMessager.send({
        action: 'meechat:getArbitrarySignature',
        data: {
          public_key: publicKey,
          random: random
        }
      })

      if (!response) {
        console.log('handleParentLogin, getArbitrarySignature, !response')
        this.showLoginBtn = true
        return
      }

      let param = {
        account: this.account.name,
        sign: response,
        pubkey: publicKey,
        data: random
      }

      response = await API.user.eosLogin(param)
      data = response.data
      if (data.result === 1) {
        let userId = data.data.user_id
        let token = data.data.token
        localStorage.setItem('user_id', userId)
        localStorage.setItem('token', token)

        this.setUserId(userId)
        this.setToken(token)

        // 设置groupId
        this.initGroup({
          userId,
          groupId: this.groupId,
          useCache: false
        })

        this.showLoginBtn = false
      }
    },
    /**
     * 聊天窗体滚动到底部
     */
    resizeToBottom () {
      this.$refs.scrollWrap.scrollTop = this.$refs.msgWrap.offsetHeight
    },
    /**
     * 添加表情
     */
    addEmoji (value) {
      this.inputMsg += value
    },
    /**
     * @des 聊天窗体滚动事件处理集
     */
    handleScroll (e) {
      // 防止切换房间时触发滚动处理
      if (!this.group.chatList.length) {
        return
      }
      let msgWrap = this.$refs.msgWrap
      let totalHeight = msgWrap.offsetHeight
      let scrollTop = e.target.scrollTop
      if (scrollTop === 0 && !this.lockMore) {
        if (this.group.endHash !== null) {
          this.lockMore = true
          this.getHistoryMsg().then((res) => {
            if (res === 'end') {
              this.lockEnd = true
            } else {
              let scrollBottom = totalHeight - scrollTop
              this.$nextTick(() => {
                e.target.scrollTop = msgWrap.offsetHeight - scrollBottom
                this.ps && this.ps.update()
                setTimeout(() => {
                  this.lockMore = false
                }, 800)
              })
            }
          })
        }
      }
    },
    /**
     * @des 处理消息发送
     */
    handleSend (e) {
      // 判断是否被禁言
      if (this.blockList.some(id => id == this.userId)) {
        Message({
          message: '您已被禁言',
          type: 'error'
        })
        return
      }

      let text = this.inputMsg.trim()

      if (text.length === 0) {
        Message({
          message: '聊天内容不能为空',
          type: 'warning'
        })
        return
      }

      let opt = {
        type: 0,
        msg: text
      }

      // 用户不是第一次发言
      if (this.group.members[this.userId]) {
        let createTime = Date.now()
        this.addChatItem({
          from: this.userId,
          content: text,
          hash: `${createTime}`,
          timestamp: createTime,
          createTime,
          msg_type: '0',
          loading: true
        })
        opt.createTime = createTime
      }

      this.doSendMsg(opt)

      // 滚到底部
      this.$nextTick(function () {
        this.inputMsg = ''
        this.resizeToBottom()
        let elem = this.$refs.chatInput
        elem.style.height = 'auto'
        elem.scrollTop = elem.scrollHeight
      })

      e.preventDefault()
      return false
    },
    handleBlur () {
      this.updateChatInputFocus(false)
    },
    placeEnd (el) {
      var range = document.createRange()
      range.selectNodeContents(el)
      range.collapse(false)
      var sel = window.getSelection()
      sel.removeAllRanges()
      sel.addRange(range)
    },
    initDrop (e) {
      e.preventDefault()
      let files = Array.from(e.dataTransfer.files)
      files.forEach(file => this.handleFile(file))
    },
    initDragOver (e) {
      e.preventDefault()
    },
    initPaste (event) {
      var items = (event.clipboardData || window.clipboardData).items
      if (items && items.length) {
        Array.from(items).forEach(item => {
          let file = item.getAsFile()
          if (file) {
            this.handleFile(file)
          }
        })
      }
    },
    /**
     * 文件预处理
     * @return {Object} data 预处理文件信息
     * @param {Number} data.type
     * @param {File} data.res
     */
    async preHandleFile (file) {
      let type = file.type
      let size = file.size
      if (type.match('video')) {
        return size > 3 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
          type: 2,
          res: file
        })
      } else if (type.match('audio')) {
        return size > 2 * 1024 * 1024 ? Promise.reject(new Error(file)) : Promise.resolve({
          type: 3,
          res: file
        })
      } else if (type.match('image')) {
        let image = await new ImageMin({
          file: file,
          maxSize: 1024 * 1024
        })
        return {
          type: 1,
          preview: image.base64,
          res: image.res
        }
      }
    },
    /**
     * @des 处理文件发送
     */
    async handleFile (e) {
      let inputfile
      if (e.constructor === File) {
        inputfile = e
      } else {
        inputfile = e.target.files[0]
      }
      try {
        let fileInfo = await this.preHandleFile(inputfile)
        let opt = { res: fileInfo.res }

        if (this.group.members[this.userId]) {
          let createTime = Date.now()
          this.addChatItem({
            content: fileInfo.preview || '',
            from: this.userId,
            hash: `${createTime}`,
            msg_type: fileInfo.type,
            timestamp: createTime,
            res: fileInfo.res,
            loading: true,
            createTime
          })
          opt.createTime = createTime
        }

        this.doSendFile(opt)

        setTimeout(() => {
          this.$refs.inputFile.value = null
          this.resizeToBottom()
        }, 100)
      } catch (error) {
        Message({
          message: '上传文件大小限制：音频2M以内，视频3M以内',
          type: 'warning'
        })
      }
    },

    /**
     * @des 控制聊天窗口开关
     */
    handleToggleChat (flag) {
      if (flag) {
        this.showChat = true
        this.unreadCounts = 0
        this.$nextTick(() => {
          this.postResize(this.width, this.height + 16)
          this.resizeToBottom()
        })
      } else {
        this.showChat = false
        this.$nextTick(() => {
          this.postResize(56, 50)
        })
      }
    },
    postResize (width, height) {
      let request = {
        action: 'meechat:resize',
        data: {
          ch: height,
          cw: width
        }
      }
      return window.parent.postMessage(request, '*')
    },
    handleKeyDown (event) {
      if (this.atShow) {
        event.preventDefault()
        let item = this.filterMembers[this.atInd]
        this.atPerson(item.nick_name)
        return
      }

      let elem = event.currentTarget
      if (event.altKey || event.ctrlKey) {
        // 单纯换行
        this.inputMsg = this.inputMsg + '\n'

        // 重新调整高度
        if (elem.scrollHeight < 175) {
          elem.style.height = 'auto'
          this.$nextTick(function () {
            elem.scrollTop = 0 // 防抖动
            elem.style.height = elem.scrollHeight + 'px'
          })
        }
      } else {
        event.returnValue = false
        this.handleSend(event)
      }

      return false
    },
    /**
     * @des 引用某条消息
     */
    quoteMsg (msg) {
      this.inputMsg = msg
    },
    /**
     * @des 某条消息被删除
     */
    deleteMsg (hash) {
      this.deleteChatItem(hash)
    },
    pinMsgClose () {
      this.pinMsg.visible = false
    },
    scrollToView () {
      if (this.pinList.length) {
        let node = this.$refs.msgWrap.querySelector('.msg-item')
        scrollIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
      } else {
        let hash = this.pinMsg.hash
        let index = this.group.chatList.findIndex(item => item.hash === hash)
        if (index >= 0) {
          let node = this.$refs.msgWrap.querySelectorAll('.msg-item')[index]
          scrollIntoView(node, { behavior: 'smooth', scrollMode: 'if-needed' })
        }
      }
      // 防止加载更多
      this.lockMore = true
      setTimeout(() => {
        this.lockMore = false
      }, 2000)
    }
  }
}
</script>

<style lang="scss" scoped>
@import "./chatMini.scss";
</style>
