/*
 * 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.
 */
import chai from 'chai'
const { expect } = chai

import {
  createElement,
  createBlock,
  createBody,
  attachTarget,
  moveTarget,
  removeTarget
} from '../../../../../runtime/frameworks/legacy/vm/dom-helper'
import { Document } from '../../../../../runtime/vdom'
import Listener from '../../../../../runtime/bridge/Listener'

describe('help create body', () => {
  let vm

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('create body with type', () => {
    const result = createBody(vm, 'bar')
    expect(result).is.an.object
    expect(result.type).eql('bar')
    expect(result.ref).eql('_root')
    expect(result.docId).is.not.ok
  })
})

describe('help create element', () => {
  let vm

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('create element with type', () => {
    const result = createElement(vm, 'bar')
    expect(result).is.an.object
    expect(result.type).eql('bar')
    expect(result.docId).is.not.ok
  })
})

describe('help create block', () => {
  let vm

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('create block with element', () => {
    const element = vm._app.doc.createElement('bar')
    const result = createBlock(vm, element)
    expect(result).is.an.object
    expect(result.start).is.an.object
    expect(result.end).is.an.object
    expect(result.start.type).eql('comment')
    expect(result.end.type).eql('comment')
    expect(result.start.value).eql('start')
    expect(result.end.value).eql('end')
    expect(result.element).equal(element)
    expect(result.element.children).eql([result.start, result.end])
  })
})

describe('help attach target', () => {
  let vm

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('attach body to documentElement', () => {
    const oriCallnative = global.callNative
    global.callNative = function () {}
    const target = createBody(vm, 'bar')
    const dest = vm._app.doc.documentElement
    attachTarget(vm, target, dest)
    expect(dest.children).eql([target])
    global.callNative = oriCallnative
  })

  it('attach element to body', () => {
    const target = createElement(vm, 'bar')
    const dest = createBody(vm, 'baz')
    attachTarget(vm, target, dest)
    expect(dest.children).eql([target])
  })

  it('attach element to element', () => {
    const target = createElement(vm, 'bar')
    const dest = createElement(vm, 'baz')
    attachTarget(vm, target, dest)
    expect(dest.children).eql([target])
  })

  it('attach block to element', () => {
    const parent = createElement(vm, 'bar')
    const target = createBlock(vm, parent)
    const dest = createElement(vm, 'baz')
    attachTarget(vm, target, dest)
    // block can't attach to another element
    expect(dest.children).eql([])
    expect(parent.children).eql([target.start, target.end])
  })

  it('attach element to block', () => {
    const target = createElement(vm, 'bar')
    const parent = createElement(vm, 'baz')
    const dest = createBlock(vm, parent)
    attachTarget(vm, target, dest)
    expect(parent.children).eql([dest.start, target, dest.end])
  })

  it('attach block to block', () => {
    const element = createElement(vm, 'bar')
    const target = createBlock(vm, element)
    const parent = createElement(vm, 'baz')
    const dest = createBlock(vm, parent)
    attachTarget(vm, target, dest)
    // block can't attach to another element
    expect(parent.children).eql([dest.start, dest.end])
    expect(element.children).eql([target.start, target.end])
  })

  it('attach element to block with an update mark', () => {
    const target = createElement(vm, 'bar')
    const parent = createElement(vm, 'baz')
    const dest = createBlock(vm, parent)
    const mark = createElement(vm, 'qux')

    attachTarget(vm, target, dest)
    attachTarget(vm, mark, dest)
    expect(parent.children).eql([dest.start, target, mark, dest.end])

    dest.updateMark = mark
    attachTarget(vm, target, dest)
    expect(parent.children).eql([dest.start, mark, target, dest.end])
    expect(dest.updateMark).eql(target)
  })

  it('attach block to block with an update mark', () => {
    const element = createElement(vm, 'bar')
    const target = createBlock(vm, element)
    const parent = createElement(vm, 'baz')
    const dest = createBlock(vm, parent)
    const mark = createElement(vm, 'qux')

    attachTarget(vm, target, dest)
    attachTarget(vm, mark, dest)
    // block can't attach to another element
    expect(parent.children).eql([dest.start, mark, dest.end])
    expect(element.children).eql([target.start, target.end])

    dest.updateMark = mark
    attachTarget(vm, target, dest)
    // block can't attach to another element
    expect(parent.children).eql([dest.start, mark, dest.end])
    expect(element.children).eql([target.start, target.end])
  })
})

describe('help move target', () => {
  let vm, parent, dest, target1, block1, target2, block2, target3, block3

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
    parent = createElement(vm, 'r')
    dest = createBlock(vm, parent)
    target1 = createElement(vm, 't1')
    attachTarget(vm, target1, dest)
    block1 = createBlock(vm, dest)
    target2 = createElement(vm, 't2')
    attachTarget(vm, target2, dest)
    block2 = createBlock(vm, dest)
    target3 = createElement(vm, 't3')
    attachTarget(vm, target3, block2)
    block3 = createBlock(vm, block2)
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('move an element to an element mark', () => {
    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      target1, block1.start, block1.end,
      target2, block2.start,
        target3, block3.start, block3.end,
      block2.end,
      dest.end])
    /* eslint-enable indent */

    const mark = target2
    dest.updateMark = mark

    moveTarget(vm, target1, mark)

    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      block1.start, block1.end,
      target2, target1, block2.start,
        target3, block3.start, block3.end,
      block2.end,
      dest.end])
    /* eslint-enable indent */
  })

  it('move an element to a block mark', () => {
    const mark = block2.end
    dest.updateMark = mark

    moveTarget(vm, target1, mark)

    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      block1.start, block1.end,
      target2, block2.start,
        target3, block3.start, block3.end,
      block2.end, target1,
      dest.end])
    /* eslint-enable indent */
  })

  it('move a block to an element mark', () => {
    const mark = target2
    dest.updateMark = mark

    moveTarget(vm, block1, mark)

    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      target1,
      target2, block1.start, block1.end,
      block2.start,
        target3, block3.start, block3.end,
      block2.end,
      dest.end])
    /* eslint-enable indent */
  })

  it('move a block to a block mark', () => {
    const mark = block2.end
    dest.updateMark = mark

    moveTarget(vm, block1, mark)

    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      target1,
      target2,
      block2.start,
        target3, block3.start, block3.end,
      block2.end, block1.start, block1.end,
      dest.end])
    /* eslint-enable indent */
  })

  it('move a whole block to a block mark', () => {
    const mark = block1.end
    dest.updateMark = mark

    moveTarget(vm, block2, mark)

    /* eslint-disable indent */
    expect(parent.children).eql([
      dest.start,
      target1, block1.start, block1.end,
      block2.start,
        target3, block3.start, block3.end,
      block2.end,
      target2,
      dest.end])
    /* eslint-enable indent */
  })
})

describe('help remove target', () => {
  let vm

  beforeEach(() => {
    vm = {
      _app: { doc: new Document('foo', null, null, Listener) }
    }
  })

  afterEach(() => {
    vm._app.doc.destroy()
    vm = null
  })

  it('remove body', () => {
    const oriCallnative = global.callNative
    global.callNative = function () {}
    const parent = vm._app.doc.documentElement
    const element = createBody(vm, 'baz')
    parent.appendChild(element)
    expect(parent.children).eql([element])
    removeTarget(vm, element)
    expect(parent.children).eql([])
    global.callNative = oriCallnative
  })

  it('remove element', () => {
    const parent = createElement(vm, 'bar')
    const element = createElement(vm, 'baz')
    parent.appendChild(element)
    expect(parent.children).eql([element])
    removeTarget(vm, element)
    expect(parent.children).eql([])
  })

  it('remove block', () => {
    const element = createElement(vm, 'baz')
    const prevElement = createElement(vm, 'prev')
    const nextElement = createElement(vm, 'next')
    const parent = createElement(vm, 'bar')
    attachTarget(vm, prevElement, parent)
    const block = createBlock(vm, parent)
    attachTarget(vm, element, block)
    attachTarget(vm, nextElement, parent)

    expect(parent.children).eql([
      prevElement, block.start, element, block.end, nextElement])

    removeTarget(vm, block)
    expect(parent.children).eql([
      prevElement, nextElement])
  })

  it('remove block but preserved itself', () => {
    const element = createElement(vm, 'baz')
    const prevElement = createElement(vm, 'prev')
    const nextElement = createElement(vm, 'next')
    const parent = createElement(vm, 'bar')
    attachTarget(vm, prevElement, parent)
    const block = createBlock(vm, parent)
    attachTarget(vm, element, block)
    attachTarget(vm, nextElement, parent)

    expect(parent.children).eql([
      prevElement, block.start, element, block.end, nextElement])

    removeTarget(vm, block, true)
    expect(parent.children).eql([
      prevElement, block.start, block.end, nextElement])
  })
})
