/*
 * 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.
 */
/**
 * @fileOverview
 * instance controls from native
 *
 * - fire event
 * - callback
 * - refresh
 * - destroy
 *
 * corresponded with the API of instance manager (framework.js)
 */
import { extend, typof } from '../../util/index'

/**
 * Refresh an app with data to its root component options.
 * @param  {object} app
 * @param  {any}    data
 */
export function refresh (app, data) {
  console.debug(`[JS Framework] Refresh with`, data, `in instance[${app.id}]`)
  const vm = app.vm
  if (vm && data) {
    if (typeof vm.refreshData === 'function') {
      vm.refreshData(data)
    }
    else {
      extend(vm, data)
    }
    app.differ.flush()
    app.doc.taskCenter.send('dom', { action: 'refreshFinish' }, [])
    return
  }
  return new Error(`invalid data "${data}"`)
}

/**
 * Destroy an app.
 * @param  {object} app
 */
export function destroy (app) {
  console.debug(`[JS Framework] Destory an instance(${app.id})`)

  if (app.vm) {
    destroyVm(app.vm)
  }

  app.id = ''
  app.options = null
  app.blocks = null
  app.vm = null
  app.doc.taskCenter.destroyCallback()
  app.doc.destroy()
  app.doc = null
  app.customComponentMap = null
  app.commonModules = null
}

/**
 * Destroy an Vm.
 * @param {object} vm
 */
export function destroyVm (vm) {
  delete vm._app
  delete vm._computed
  delete vm._css
  delete vm._data
  delete vm._ids
  delete vm._methods
  delete vm._options
  delete vm._parent
  delete vm._parentEl
  delete vm._rootEl

  // remove all watchers
  if (vm._watchers) {
    let watcherCount = vm._watchers.length
    while (watcherCount--) {
      vm._watchers[watcherCount].teardown()
    }
    delete vm._watchers
  }

  // destroy child vms recursively
  if (vm._childrenVms) {
    let vmCount = vm._childrenVms.length
    while (vmCount--) {
      destroyVm(vm._childrenVms[vmCount])
    }
    delete vm._childrenVms
  }

  console.debug(`[JS Framework] "destroyed" lifecycle in Vm(${vm._type})`)
  vm.$emit('hook:destroyed')

  delete vm._type
  delete vm._vmEvents
}

/**
 * Get a JSON object to describe the document body.
 * @param  {object} app
 * @return {object}
 */
export function getRootElement (app) {
  const doc = app.doc || {}
  const body = doc.body || {}
  return body.toJSON ? body.toJSON() : {}
}

/**
 * Fire an event from renderer. The event has type, an event object and an
 * element ref. If the event comes with some virtual-DOM changes, it should
 * have one more parameter to describe the changes.
 * @param  {object} app
 * @param  {string} ref
 * @param  {type}   type
 * @param  {object} e
 * @param  {object} domChanges
 */
export function fireEvent (app, ref, type, e, domChanges) {
  console.debug(`[JS Framework] Fire a "${type}" event on an element(${ref}) in instance(${app.id})`)
  if (Array.isArray(ref)) {
    ref.some((ref) => {
      return fireEvent(app, ref, type, e) !== false
    })
    return
  }
  const el = app.doc.getRef(ref)
  if (el) {
    const result = app.doc.fireEvent(el, type, e, domChanges)
    app.differ.flush()
    app.doc.taskCenter.send('dom', { action: 'updateFinish' }, [])
    return result
  }
  return new Error(`invalid element reference "${ref}"`)
}

/**
 * Make a callback for a certain app.
 * @param  {object}   app
 * @param  {number}   callbackId
 * @param  {any}      data
 * @param  {boolean}  ifKeepAlive
 */
export function callback (app, callbackId, data, ifKeepAlive) {
  console.debug(`[JS Framework] Invoke a callback(${callbackId}) with`, data, `in instance(${app.id})`)
  const result = app.doc.taskCenter.callback(callbackId, data, ifKeepAlive)
  updateActions(app)
  app.doc.taskCenter.send('dom', { action: 'updateFinish' }, [])
  return result
}

/**
 * Collect all virtual-DOM mutations together and send them to renderer.
 * @param  {object} app
 */
export function updateActions (app) {
  app.differ.flush()
}

/**
 * Call all tasks from an app to renderer (native).
 * @param  {object} app
 * @param  {array}  tasks
 */
export function callTasks (app, tasks) {
  let result

  /* istanbul ignore next */
  if (typof(tasks) !== 'array') {
    tasks = [tasks]
  }

  tasks.forEach(task => {
    result = app.doc.taskCenter.send(
      'module',
      {
        module: task.module,
        method: task.method
      },
      task.args
    )
  })

  return result
}
