import { Colors } from '../../utils.js'
import { WorkerTileLoader, PIXITileLoader } from './loader.js'

/**
 * A layer of tiles that represents a zoom level of a DeepZoomImage as a grid
 * of sprites.
 * @constructor
 * @param {number} level - the zoom level of the tile layer
 * @param {DeepZoomImage} view - the zoomable image the layer belongs to
 * @param {number} scale - the scale of the tile layer
 * @param {number} cols - the number of columns of the layer
 * @param {number} rows - the number of rows of the layer
 * @param {number} width - the width of the layer in pixel
 * @param {number} height - the height of the layer in pixel
 * @param {number} tileSize - the size of a single tile in pixel
 * @param {number} overlap - the overlap of the tiles in pixel
 * @param {number} fadeInTime - time needed to fade in tiles if TweenLite is set
 **/
export class Tiles extends PIXI.Container {
    constructor(level, view, scale, cols, rows, width, height, tileSize, overlap, fadeInTime = 0.33) {
        super()
        this.debug = false
        this.showGrid = false
        this.view = view
        this.level = level
        this.cols = cols
        this.rows = rows
        this.pixelWidth = width
        this.pixelHeight = height
        this.tileSize = tileSize
        this.overlap = overlap
        this.needed = new Map() // url as key, [col, row] as value
        this.requested = new Set()
        this.available = new Map()
        this.scale.set(scale, scale)
        this.tileScale = scale
        this.fadeInTime = fadeInTime
        this.keep = false
        if (this.view.useWorker && view.info.compression && view.info.compression.length > 0) {
            this.loader = new WorkerTileLoader(this, this.view.useWorker)
        } else {
            this.loader = new PIXITileLoader(this, view.info.compression)
        }
        this.interactive = false
        this._highlight = null

        this._info = null

        this._centerPoint = null
        this._boundsRect = null

        this.infoColor = Colors.random()
        this.pprint()
        //this.destroyed = false
    }

    /** Tests whether all tiles are loaded. **/
    isComplete() {
        return this.cols * this.rows === this.children.length
    }

    /** Returns the highligh graphics layer for debugging purposes.
     **/
    get highlight() {
        if (this._highlight == null) {
            let graphics = new PIXI.Graphics()
            graphics.beginFill(0xffff00, 0.1)
            graphics.lineStyle(2, 0xffff00)
            graphics.drawRect(1, 1, this.tileSize - 2, this.tileSize - 2)
            graphics.endFill()
            graphics.interactive = false
            this._highlight = graphics
        }
        return this._highlight
    }

    /** Returns the highligh graphics layer for debugging purposes.
     **/
    get info() {
        if (this._info == null) {
            let graphics = new PIXI.Graphics()
            graphics.lineStyle(4, 0xff0000)
            graphics.interactive = false
            this._info = graphics
            this.addChild(this._info)
        }
        return this._info
    }

    /** Helper method pretty printing debug information. **/
    pprint() {
        if (this.debug)
            console.log(
                'Tiles level: ' +
                    this.level +
                    ' scale: ' +
                    this.scale.x +
                    ' cols: ' +
                    this.cols +
                    ' rows: ' +
                    this.rows +
                    ' w: ' +
                    this.pixelWidth +
                    ' h: ' +
                    this.pixelHeight +
                    ' tsize:' +
                    this.tileSize
            )
    }

    /** Computes the tile position and obeys the overlap.
     * @param {number} col - The column of the tile
     * @param {number} row - The row of the tile
     * @returns {PIXI.Point} obj
     **/
    tilePosition(col, row) {
        let x = col * this.tileSize
        let y = row * this.tileSize
        let overlap = this.overlap
        if (col != 0) {
            x -= overlap
        }
        if (row != 0) {
            y -= overlap
        }
        return new PIXI.Point(x, y)
    }

    /** Computes the tile size without overlap
     * @param {number} col - The column of the tile
     * @param {number} row - The row of the tile
     * @returns {PIXI.Point} obj
     **/
    tileDimensions(col, row) {
        let w = this.tileSize
        let h = this.tileSize
        let pos = this.tilePosition(col, row)
        if (col == this.cols - 1) {
            w = this.pixelWidth - pos.x
        }
        if (row == this.rows - 1) {
            h = this.pixelHeight - pos.y
        }
        return new PIXI.Point(w, h)
    }

    /** Method to support debugging. Highlights the specified tile at col, row **/
    highlightTile(col, row) {
        if (col > -1 && row > -1 && col < this.cols && row < this.rows) {
            let graphics = this.highlight
            let dim = this.tileDimensions(col, row)
            graphics.position = this.tilePosition(col, row)
            graphics.clear()
            graphics.beginFill(0xff00ff, 0.1)
            graphics.lineStyle(2, 0xffff00)
            graphics.drawRect(1, 1, dim.x - 2, dim.y - 2)
            graphics.endFill()
            this.addChild(this.highlight)
        } else {
            this.removeChild(this.highlight)
        }
    }

    /** Loads the tiles for the given urls and adds the tiles as sprites.
     * @param {array} urlpos - An array of URL, pos pairs
     * @param {boolean} onlyone - Loads only on tile at a time if true
     **/
    loadTiles(urlpos, onlyone, refCol, refRow) {
        if (this.showGrid) {
            this.highlightTile(refCol, refRow)
        }
        urlpos.forEach((d) => {
            let [url, col, row] = d
            if (this.loader.schedule(url, col, row)) {
                if (onlyone) {
                    return this.loader.loadOneTile()
                }
            }
        })
        this.loader.loadAll()
    }

    /** Private method: add a red border to a tile for debugging purposes. **/
    _addTileBorder(tile, col, row) {
        let dim = this.tileDimensions(col, row)
        let graphics = new PIXI.Graphics()
        graphics.beginFill(0, 0)
        graphics.lineStyle(2, 0xff0000)
        graphics.drawRect(1, 1, dim.x - 2, dim.y - 2)
        graphics.endFill()
        tile.addChild(graphics)
    }

    /** Adds a tile. **/
    addTile(tile, col, row, url) {
        if (this.available.has(url)) {
            console.warn('Trying to add available tile')
            return
        }
        this.addChildAt(tile, 0)
        this.available.set(url, tile)
        if (this.destroyed) {
            console.warn('Adding to destroyed tiles layer')
        }
        // this._addTileBorder(tile, col, row)
    }

    /** Called by the loader after each successfull loading of a single tile.
     * Adds the sprite to the tile layer.
     * @param {Object} tile - the loaded tile sprite
     * @param {Number} col - the col position
     * @param {Number} row - the rowposition
     **/
    tileAvailable(tile, col, row, url) {
        let pos = this.tilePosition(col, row)
        if (this.showGrid) {
            this._addTileBorder(tile, col, row)
        }
        tile.position = pos
        tile.interactive = false
        if (TweenLite) {
            tile.alpha = 0
            TweenLite.to(tile, this.fadeInTime, { alpha: this.alpha })
        }
        this.addTile(tile, col, row, url)
    }

    /** Destroys the tiles layer and destroys the loader. Async load calls are
     * cancelled.
     **/
    destroy() {
        //this.destroyed = true
        this.loader.destroy()
        super.destroy({ children: true }) // Calls destroyChildren
        this.available.clear()
        this.requested.clear()
        this.needed.clear()
    }

    destroyTile(url, tile) {
        this.loader.unschedule(url)
        this.removeChild(tile)
        tile.destroy()
        this.available.delete(url)
    }

    destroyTileByUrl(url) {
        if (this.available.has(url)) {
            let tile = this.available.get(url)
            this.destroyTile(url, tile)
        }
    }

    /* Destroys the tiles which are not with the bounds of the app to free
     * memory.
     **/
    destroyTiles(quadTrees) {
        let count = 0
        for (let [url, tile] of this.available.entries()) {
            if (!quadTrees.has(url)) {
                this.destroyTile(url, tile)
                count += 1
            }
        }
        if (count && this.debug) console.log('destroyTiles', this.level, count)
    }

    destroyUnneededTiles() {
        let count = 0
        for (let [url, tile] of this.available.entries()) {
            if (!this.needed.has(url)) {
                this.destroyTile(url, tile)
                count += 1
            }
        }
        if (count && this.debug) console.log('destroyUnneededTiles', this.level, count)
    }

    highlightInfos() {
        let graphics = this.info
        let color = this.infoColor
        graphics.clear()
        graphics.lineStyle(2, color)
        for (let [col, row] of this.needed.values()) {
            let dim = this.tileDimensions(col, row)
            let pos = this.tilePosition(col, row)
            graphics.beginFill(color, 0.2)
            graphics.drawRect(pos.x + 1, pos.y + 1, dim.x - 2, dim.y - 2)
            graphics.endFill()
        }
        let r = this._boundsRect
        if (r != null) {
            graphics.lineStyle(20, color)
            graphics.drawRect(r.x, r.y, r.width, r.height)
            graphics.moveTo(r.x, r.y)
            graphics.lineTo(r.x + r.width, r.y + r.height)

            graphics.moveTo(r.x, r.y + r.height)
            graphics.lineTo(r.x + r.width, r.y)
        }

        let p = this._centerPoint
        if (p != null) {
            graphics.drawCircle(p.x, p.y, 20)
        }
    }

    tintTiles(quadTrees) {
        for (let [url, tile] of this.available.entries()) {
            if (!quadTrees.has(url)) tile.tint = 0xff0000
        }
    }

    untintTiles() {
        for (let [url, tile] of this.available.entries()) {
            tile.tint = 0xffffff
        }
    }
}