/* eslint-disable no-console */

let _HighlightEnabled = true
let _CircleIds = 0

/** Helper method to round values with one digit precision */
function round(value) {
    return Math.round(parseFloat(value) * 10) / 10
}

/**
 * A namespace with static functions to expand and shrink highlighted image regions.
 * Assumes an SVG image with the following structure:
 *
 * <svg viewbox="0 0 100 100">
 *       <!-- The defs section must be defined and cannot be generated in JavaScript -->
 *      <defs>
 *      </defs>
 *       <image width="100" height="100" xlink:href="../assets/chess.jpg"/>
 *        <circle onclick="Highlight.animateZoom(event)" cx="47" cy="18" r="8" stroke-width="0.5" />
 *       <circle onclick="Highlight.animateZoom(event)" cx="60" cy="67" r="8" stroke-width="0.5" />
 *   </svg>
 *
 * The SVG root element should use a viewbox with 0 0 100 100 to ensure that the positions and size of the
 * circles can be represented in percent.
 *
 * @class Highlight
 * @extends {Object}
 */

const SCALE = 1.5

export default class Highlight extends Object {
    static disableAnimations() {
        _HighlightEnabled = false
        let expanded = document.querySelectorAll('.' + this.expandedClass)
        for (let obj of expanded) {
            this.shrink(obj)
        }
    }

    static enableAnimations() {
        _HighlightEnabled = true
    }

    static removeAnimations(svgRoot) {
        let expanded = svgRoot.querySelectorAll('.' + this.expandedClass)
        for (let obj of expanded) {
            TweenLite.set(obj, { scale: 1 })
            obj.classList.remove('zooming')
            obj.classList.remove(this.expandedClass)
        }
        let defs = svgRoot.querySelector('defs')
        while (defs.firstChild) {
            defs.firstChild.remove()
        }
        let maskImages = svgRoot.querySelectorAll('.addedImage')
        for (let m of maskImages) {
            m.remove()
        }
        let circles = svgRoot.querySelectorAll('circle')
        for (let circle of circles) {
            if (circle.classList.length == 0) {
                circle.removeAttribute('class')
            }
            if (circle.hasAttribute('id') && circle.getAttribute('id').startsWith('@@')) {
                circle.removeAttribute('id')
            }
            circle.removeAttribute('data-svg-origin')
            circle.removeAttribute('transform')
            circle.removeAttribute('style')
            let cx = circle.getAttribute('cx')
            let cy = circle.getAttribute('cy')
            let r = circle.getAttribute('r')
            circle.setAttribute('cx', round(cx))
            circle.setAttribute('cy', round(cy))
            circle.setAttribute('r', round(r))
        }
    }

    static expand(obj, { scale = SCALE, duration = 3, stroke = 2, onComplete = null } = {}) {
        if (obj == null) return
        //console.log("expand")
        obj.classList.add('zooming')
        TweenLite.to(obj, duration, {
            scale,
            onUpdate: () => {
                let scale = obj._gsTransform.scaleX
                obj.setAttribute('stroke-width', stroke / scale)
            },
            onComplete: () => {
                console.log('expand complete')
                obj.classList.remove('zooming')
                obj.classList.add(this.expandedClass)
                obj.setAttribute('stroke-width', stroke / scale)
                if (onComplete) onComplete()
            },
        })
    }

    static shrink(obj, { duration = 0.5, stroke = 2 } = {}) {
        //console.log("shrink")
        if (obj == null) return
        obj.classList.add('zooming')
        TweenLite.to(obj, duration, {
            scale: 1,
            onUpdate: () => {
                let scale = obj._gsTransform.scaleX
                obj.setAttribute('stroke-width', stroke / scale)
            },
            onComplete: () => {
                //console.log("shrink complete")
                obj.classList.remove('zooming')
                obj.classList.remove(this.expandedClass)
                obj.setAttribute('stroke-width', stroke)
            },
        })
    }

    static animateCircle(target, callback) {
        console.log('ANIMATE CIRCLE', this)
        // ** DEBUG OUTPUTS **

        let circle = target
        // We need a unique id to ensure correspondence between circle, mask, and maskImage
        if (!circle.hasAttribute('id')) {
            _CircleIds += 1
            circle.setAttribute('id', '@@' + _CircleIds)
        }
        let id = circle.getAttribute('id')
        TweenLite.set(circle, { transformOrigin: '50% 50%' })
        /*if (circle.classList.contains('zooming')) {
                console.log("already zooming")
                return
            }*/

        let svgRoot = circle.closest('svg')
        let circleGroup = circle.parentNode
        let image = svgRoot.querySelector('image')

        let stroke = parseFloat(circleGroup.getAttribute('stroke-width') || 6)

        let defs = svgRoot.querySelector('defs')
        if (defs == null) {
            defs = document.createElementNS(svgRoot, 'defs')
            svgRoot.insertBefore(defs, image)
        }

        // // We need direct children, therefore we cannot use querySelectorAll
        let maskImageId = 'maskImage' + id
        let maskImage = svgRoot.getElementById(maskImageId)

        if (circle.classList.contains(this.expandedClass)) {
            if (!circle.classList.contains('zooming')) {
                this.shrink(circle, { stroke })
                this.shrink(maskImage, { stroke })
                return
            }
            //console.log("animate called while zooming out -> expand")
        } else if (circle.classList.contains('zooming')) {
            //console.log("animate called while zooming in -> shrink")
            this.shrink(circle, { stroke })
            this.shrink(maskImage, { stroke })
            return
        }
        let circles = Array.from(circleGroup.children).filter((e) => e.tagName == 'circle')
        for (let c of circles) {
            //console.log("shrinking all circles")
            this.shrink(c, { stroke })
        }
        let maskImages = circleGroup.querySelectorAll('.addedImage')
        for (let m of maskImages) {
            this.shrink(m, { stroke })
        }

        this._createSVGMask(svgRoot, image, id)

        // TweenLite.set(maskImage, { transformOrigin: `${tx}% ${ty}%` })

        this.expand(circle, { stroke, onComplete: callback })
        this.expand(maskImage)

        return false
    }

    static openHighlight(target, { animation = 0.5, scale = SCALE, onExpanded = null } = {}) {
        if (this._isExpanded(target)) {
            console.log('Target is already expanded!')
            return
        } else {
            let targetId = target.getAttribute('id')
            if (targetId && targetId.startsWith('@@')) {
                let id = targetId.slice(2)
                const imageId = '#maskImage' + id
                const parent = target.parentNode
                if (parent != null) {
                    let image = parent.querySelector(imageId)
                    if (image) {
                        this._bringToFront(image)
                    } else console.error('Could not find corresponding image element.')
                } else console.log('Element was no parent:', target)
            }
            this._bringToFront(target)
            let svgRoot = target.closest('svg')
            if (svgRoot == null) {
                return
            }
            let image = svgRoot.querySelector('image')

            // eslint-disable-next-line no-unused-vars
            let [mask, maskImage] = this._getSVGMask(target, {
                svgRoot,
                image,
            })
            let center = this._calculateCenterRelativeTo(target, image)
            TweenLite.set(maskImage, {
                transformOrigin: `${center.x} ${center.y}`,
            })
            TweenLite.set(target, { transformOrigin: '50% 50%' })

            TweenLite.to([target, maskImage], animation, {
                scale,
                onComplete: onExpanded,
            })

            target.classList.add(this.expandedClass)
        }
    }

    static toggleHighlight(node, options = {}) {
        console.log('toggleHighlight', this._isExpanded(node))
        if (this._isExpanded(node)) {
            this.closeHighlight(node, options)
        } else {
            this.openHighlight(node, options)
        }
    }

    static _bringToFront(target) {
        const parent = target.parentNode
        if (target && parent) {
            parent.removeChild(target)
            parent.appendChild(target)
        } else console.error('Could not bring to front. Either no target or no parent.', target, parent)
    }

    static _getSVGMask(circle, { svgRoot = null, image = null } = {}) {
        const id = this._retrieveId(circle)
        const maskId = 'mask' + id
        const maskImageId = 'maskImage' + id

        if (!svgRoot) svgRoot = circle.closest('svg')

        let mask = svgRoot.getElementById(maskId)
        let maskImage = svgRoot.getElementById(maskImageId)

        if (!mask || !maskImage)
            [mask, maskImage] = this._createSVGMask(circle, {
                svgRoot,
                image,
                id,
            })

        return [mask, maskImage]
    }

    /**
     * Creates an SVG mask for a provided svgElement.
     *
     * @static
     * @param {SVGElement} element - Element that should be masked.
     * @param {object} opts - Optional parameters to avoid unnecessary fetching of elements.
     * @param {SVGElement} opts.svgRoot - The root <svg> element of the element.
     * @param {SVGImageElement} opts.image - The image that is used in the mask.
     * @param {number} opts.id - The id of the mask.
     * @returns
     * @memberof Highlight
     */
    static _createSVGMask(element, { svgRoot = null, image = null, id = null } = {}) {
        // We can fetch these values here, but it's more efficient to
        // simply pass them in, as it's likely they were already retrieved beforehand.
        if (svgRoot == null) svgRoot = element.closest('svg')
        if (image == null) image = svgRoot.querySelector('image')
        if (id == null) id = this._retrieveId(element)

        let svg = 'http://www.w3.org/2000/svg'
        let xlink = 'http://www.w3.org/1999/xlink'

        let svgGroup = element.parentNode

        let src = image.getAttributeNS(xlink, 'href')

        let maskId = 'mask' + id
        let maskImageId = 'maskImage' + id
        let mask = svgRoot.getElementById(maskId)
        let maskImage = svgRoot.getElementById(maskImageId)

        let defs = svgRoot.querySelector('defs')
        if (defs == null) {
            defs = document.createElementNS(svgRoot, 'defs')
            svgRoot.insertBefore(defs, image)
        }

        if (mask == null) {
            mask = document.createElementNS(svg, 'mask')
            mask.setAttribute('id', maskId)
            let maskCircle = element.cloneNode(true)
            mask.appendChild(maskCircle)
            defs.appendChild(mask)
        }

        let bbox = svgRoot.getElementsByTagName('image')[0].getBBox()
        let width = bbox.width
        let height = bbox.height

        if (maskImage == null) {
            maskImage = document.createElementNS(svg, 'image')
            maskImage.style.pointerEvents = 'none'
            maskImage.setAttribute('id', maskImageId)
            maskImage.setAttributeNS(xlink, 'href', src)
            maskImage.setAttribute('width', width)
            maskImage.setAttribute('height', height)
            maskImage.setAttribute('class', 'addedImage')
            TweenLite.set(maskImage, { scale: 1 })
            maskImage.style.mask = 'url(#' + maskId + ')'
        }

        element.insertAdjacentElement('beforebegin', maskImage) // image.nextSibling)
        // svgGroup.appendChild(element)

        return [mask, maskImage]
    }

    static _calculateCenterRelativeTo(target, image) {
        let bbox = image.getBBox()
        let width = bbox.width
        let height = bbox.height
        let cx = target.getAttribute('cx')
        let cy = target.getAttribute('cy')
        let x = cx.endsWith('%') ? cx : round((cx / width) * 100) + '%'
        let y = cy.endsWith('%') ? cy : round((cy / height) * 100) + '%'
        return { x, y }
    }

    static _isExpanded(target) {
        return target.classList.contains(this.expandedClass)
    }

    static closeHighlight(target, { animation = 0.5 } = {}) {
        target.classList.remove(this.expandedClass)
        // eslint-disable-next-line no-unused-vars
        let [mask, maskImage] = this._getSVGMask(target)
        // console.log('Close Highlight', maskImage)
        TweenLite.to([target, maskImage], animation, {
            scale: 1,
        })
    }

    static animate(event) {
        if (!_HighlightEnabled) return

        event.stopPropagation()
        this.animateCircle(event.target)

        return false
    }

    static _retrieveId(target) {
        let id = target.getAttribute('id')
        // We need a unique id to ensure correspondence between circle, mask, and maskImage
        if (!id) {
            _CircleIds += 1
            target.setAttribute('id', '@@' + _CircleIds)
            id = _CircleIds
        } else {
            id = parseInt(id.substring(2))
        }

        return id
    }
}

Highlight.expandedClass = 'expanded'