import {
  invoke
} from 'uni-core/service/bridge'

import {
  onMethod,
  invokeMethod
} from '../../platform'

class SocketTask {
  constructor (socketTaskId) {
    this.id = socketTaskId
    this._callbacks = {
      open: [],
      close: [],
      error: [],
      message: []
    }
    this.CLOSED = 3
    this.CLOSING = 2
    this.CONNECTING = 0
    this.OPEN = 1
    this.readyState = this.CLOSED
  }

  send (args) {
    if (this.readyState !== this.OPEN) {
      this._callback(args, 'sendSocketMessage:fail WebSocket is not connected')
    }
    const {
      errMsg
    } = invokeMethod('operateSocketTask', Object.assign({}, args, {
      operationType: 'send',
      socketTaskId: this.id
    }))
    this._callback(args, errMsg.replace('operateSocketTask', 'sendSocketMessage'))
  }

  close (args) {
    this.readyState = this.CLOSING
    const {
      errMsg
    } = invokeMethod('operateSocketTask', Object.assign({}, args, {
      operationType: 'close',
      socketTaskId: this.id
    }))
    this._callback(args, errMsg.replace('operateSocketTask', 'closeSocket'))
  }

  onOpen (callback) {
    this._callbacks.open.push(callback)
  }

  onClose (callback) {
    this._callbacks.close.push(callback)
  }

  onError (callback) {
    this._callbacks.error.push(callback)
  }

  onMessage (callback) {
    this._callbacks.message.push(callback)
  }

  _callback ({
    success,
    fail,
    complete
  } = {}, errMsg) {
    var data = {
      errMsg
    }
    if (/:ok$/.test(errMsg)) {
      if (typeof success === 'function') {
        success(data)
      }
    } else {
      if (typeof fail === 'function') {
        fail(data)
      }
    }
    if (typeof complete === 'function') {
      complete(data)
    }
  }
}

const socketTasks = Object.create(null)
const socketTasksArray = []
const callbacks = Object.create(null)
onMethod('onSocketTaskStateChange', ({
  socketTaskId,
  state,
  data,
  errMsg
}) => {
  const socketTask = socketTasks[socketTaskId]
  if (!socketTask) {
    return
  }
  if (state === 'open') {
    socketTask.readyState = socketTask.OPEN
  }
  if (socketTask === socketTasksArray[0] && callbacks[state]) {
    invoke(callbacks[state], state === 'message' ? {
      data
    } : {})
  }
  if (state === 'error' || state === 'close') {
    socketTask.readyState = socketTask.CLOSED
    delete socketTasks[socketTaskId]
    const index = socketTasksArray.indexOf(socketTask)
    if (index >= 0) {
      socketTasksArray.splice(index, 1)
    }
  }
  socketTask._callbacks[state].forEach(callback => {
    if (typeof callback === 'function') {
      callback(state === 'message' ? {
        data
      } : {})
    }
  })
})

export function connectSocket (args, callbackId) {
  const {
    socketTaskId
  } = invokeMethod('createSocketTask', args)
  const task = new SocketTask(socketTaskId)
  socketTasks[socketTaskId] = task
  socketTasksArray.push(task)
  setTimeout(() => {
    invoke(callbackId, {
      errMsg: 'connectSocket:ok'
    })
  }, 0)
  return task
}

export function sendSocketMessage (args, callbackId) {
  const socketTask = socketTasksArray[0]
  if (!socketTask || socketTask.readyState !== socketTask.OPEN) {
    invoke(callbackId, {
      errMsg: 'sendSocketMessage:fail WebSocket is not connected'
    })
    return
  }
  return invokeMethod('operateSocketTask', Object.assign({}, args, {
    operationType: 'send',
    socketTaskId: socketTask.id
  }))
}

export function closeSocket (args, callbackId) {
  const socketTask = socketTasksArray[0]
  if (!socketTask) {
    invoke(callbackId, {
      errMsg: 'closeSocket:fail WebSocket is not connected'
    })
    return
  }
  socketTask.readyState = socketTask.CLOSING
  return invokeMethod('operateSocketTask', Object.assign({}, args, {
    operationType: 'close',
    socketTaskId: socketTask.id
  }))
}

export function onSocketOpen (callbackId) {
  callbacks.open = callbackId
}

export function onSocketError (callbackId) {
  callbacks.error = callbackId
}

export function onSocketMessage (callbackId) {
  callbacks.message = callbackId
}

export function onSocketClose (callbackId) {
  callbacks.close = callbackId
}
