/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */

/**
* Create the action object.
* @param {string} name
* @param {array} arguments
* @return {object} action
*/
function createAction (name, args = []) {
  return { module: 'dom', method: name, args: args }
}

export default class Listener {
  constructor (id, handler) {
    this.id = id
    this.batched = false
    this.updates = []
    if (typeof handler === 'function') {
      Object.defineProperty(this, 'handler', {
        configurable: true,
        enumerable: true,
        writable: true,
        value: handler
      })
    }
    else {
      console.error('[JS Runtime] invalid parameter, handler must be a function')
    }
  }

  /**
   * Send the "createFinish" signal.
   * @param {function} callback
   * @return {undefined | number} the signal sent by native
   */
  createFinish (callback) {
    const handler = this.handler
    return handler([createAction('createFinish')], callback)
  }

  /**
   * Send the "updateFinish" signal.
   * @param {function} callback
   * @return {undefined | number} the signal sent by native
   */
  updateFinish (callback) {
    const handler = this.handler
    return handler([createAction('updateFinish')], callback)
  }

  /**
   * Send the "refreshFinish" signal.
   * @param {function} callback
   * @return {undefined | number} the signal sent by native
   */
  refreshFinish (callback) {
    const handler = this.handler
    return handler([createAction('refreshFinish')], callback)
  }

  /**
   * Send the "createBody" signal.
   * @param {object} element
   * @return {undefined | number} the signal sent by native
   */
  createBody (element) {
    const body = element.toJSON()
    const children = body.children
    delete body.children
    const actions = [createAction('createBody', [body])]
    if (children) {
      actions.push.apply(actions, children.map(child => {
        return createAction('addElement', [body.ref, child, -1])
      }))
    }
    return this.addActions(actions)
  }

  /**
   * Send the "addElement" signal.
   * @param {object} element
   * @param {stirng} reference id
   * @param {number} index
   * @return {undefined | number} the signal sent by native
   */
  addElement (element, ref, index) {
    if (!(index >= 0)) {
      index = -1
    }
    return this.addActions(createAction('addElement', [ref, element.toJSON(), index]))
  }

  /**
   * Send the "removeElement" signal.
   * @param {stirng} reference id
   * @return {undefined | number} the signal sent by native
   */
  removeElement (ref) {
    if (Array.isArray(ref)) {
      const actions = ref.map((r) => createAction('removeElement', [r]))
      return this.addActions(actions)
    }
    return this.addActions(createAction('removeElement', [ref]))
  }

  /**
   * Send the "moveElement" signal.
   * @param {stirng} target reference id
   * @param {stirng} parent reference id
   * @param {number} index
   * @return {undefined | number} the signal sent by native
   */
  moveElement (targetRef, parentRef, index) {
    return this.addActions(createAction('moveElement', [targetRef, parentRef, index]))
  }

  /**
   * Send the "updateAttrs" signal.
   * @param {stirng} reference id
   * @param {stirng} key
   * @param {stirng} value
   * @return {undefined | number} the signal sent by native
   */
  setAttr (ref, key, value) {
    const result = {}
    result[key] = value
    return this.addActions(createAction('updateAttrs', [ref, result]))
  }

  /**
   * Send the "updateStyle" signal, update a sole style.
   * @param {stirng} reference id
   * @param {stirng} key
   * @param {stirng} value
   * @return {undefined | number} the signal sent by native
   */
  setStyle (ref, key, value) {
    const result = {}
    result[key] = value
    return this.addActions(createAction('updateStyle', [ref, result]))
  }

  /**
   * Send the "updateStyle" signal.
   * @param {stirng} reference id
   * @param {object} style
   * @return {undefined | number} the signal sent by native
   */
  setStyles (ref, style) {
    return this.addActions(createAction('updateStyle', [ref, style]))
  }

  /**
   * Send the "addEvent" signal.
   * @param {stirng} reference id
   * @param {string} event type
   * @return {undefined | number} the signal sent by native
   */
  addEvent (ref, type) {
    return this.addActions(createAction('addEvent', [ref, type]))
  }

  /**
   * Send the "removeEvent" signal.
   * @param {stirng} reference id
   * @param {string} event type
   * @return {undefined | number} the signal sent by native
   */
  removeEvent (ref, type) {
    return this.addActions(createAction('removeEvent', [ref, type]))
  }

  /**
   * Default handler.
   * @param {object | array} actions
   * @param {function} callback
   * @return {} anything returned by callback function
   */
  handler (actions, cb) {
    return cb && cb()
  }

  /**
   * Add actions into updates.
   * @param {object | array} actions
   * @return {undefined | number} the signal sent by native
   */
  addActions (actions) {
    const updates = this.updates
    const handler = this.handler

    if (!Array.isArray(actions)) {
      actions = [actions]
    }

    if (this.batched) {
      updates.push.apply(updates, actions)
    }
    else {
      return handler(actions)
    }
  }
}
