class Draggable {
    
    #requiredClass = 'Draggable'
    #eventMap = new Map( [ // used to both define listeners and dispatch events
        [new Set( ['mousedown', 'touchstart'] ),            this.startDrag],
        [new Set( ['mousemove', 'touchmove']  ),            this.drag],
        [new Set( ['mouseup', 'touchend', 'touchcancel'] ), this.endDrag],
    ] )

    constructor( svgID, draggableID ) {
        this.draggable = document.getElementById( draggableID )
        if ( ! this.draggable.classList.contains( this.#requiredClass ) ) {
            console.log( 'Draggable: SVG element \'' + draggableID +
                '\' is not of class ' + '\'' + this.#requiredClass + '\'.')
            return false
        }
        this.selectedElement = false
        this.button = 0
        this.clicked = false
        this.delta = 1
        this.fixedY = 0
        this.offset = 0
        this.origin = 0
        this.startX = 0
        this.startY = 0
        this.svg = document.getElementById( svgID )

        for ( let [ s, f ] of this.#eventMap ) {
            for ( let eventType of s ) {
                this.draggable.addEventListener( eventType, this)
            }
        }
    } // constructor

    handleEvent( e ) {
        for ( let [ s, f ] of this.#eventMap ) {
            if ( s.has( e.type ) ) {
                this[ f.name ]( e )
                break
            }
        }
    } // handleEvent
    
    startDrag( e ) {
        this.selectedElement = false
        if ( e.target.classList.contains( this.#requiredClass ) ) {
            e.preventDefault()
            this.selectedElement = e.target
            this.button = e.button
            this.clicked = false
            this.origin = {x: this.selectedElement.getAttribute( 'x' ),
                           y: this.selectedElement.getAttribute( 'y' ) }
            this.offset = this.#getMousePosition( e )
            this.offset.x -= this.origin.x
            this.offset.y -= this.origin.y
            this.startX = e.pageX
            this.startY = e.pageY
        }
    } // startDrag
    
    drag( e ) {
        return this.selectedElement ? this.#getMousePosition( e ) : false
    }  // drag
    
    endDrag( e ) {
        this.selectedElement = false
        let diffX = Math.abs( e.pageX - this.startX )
        let diffY = Math.abs( e.pageY - this.startY )
        if ( diffX < this.delta && diffY < this.delta ) { this.clicked = true }
        return this.clicked
    } // endDrag
    
    #getMousePosition( e ) { // get mouse coordinates in SVG frame
        let pt = this.svg.createSVGPoint()
        if ( e.touches ) { e = e.touches[ 0 ] }
        pt.x = e.clientX
        pt.y = e.clientY
        let svgP = pt.matrixTransform( this.svg.getScreenCTM().inverse() )
        return { x: svgP.x, y: svgP.y }
    } // #getMousePosition

} // Draggable

