/* eslint func-style: ["error", "expression"] */
"use strict";
const cm = require("./common.js");
const ge = require("./geometry.js");

/**
 * Manage PDE requests
 * @alias hm:PDEManager
 * @param opt  {Object} - optional object {rps,concurrent} 
 * @param [opt.rps] {int} - requests per second. Default = 10
 * @param [opt.concurrent] - {int}  max concurrent requests. Default = 5 
 * 
 * @example
 * ```js
 * const hm = window.heremap;
 * const pde = new hm.PDEManager({rps:10});
 *
 * const layers = {
 *     ROAD_GEOM_FC: {
 *        callback: pdeGotRoadGeomFC,
 *        fcLayers: [1,2]
 *     }, 
 *     POI_BIGGER: {
 *       callback: pdeGotPOI,
 *       level: 6
 *   };
 * 
 *  let bb = hm.getViewBB();
 *  pde.setBoundingBox(bb);
 *  pde.setLayers(layers);
 *  pde.run().then(nbtile=> {console.log(nbtiles)});
 *  ```
 */
var PDEManager = function (opt) {

    this._rps = 10;
    this._concurrent = 5;
    this._queue = {};
    this._app_id = cm.getAppId();
    this._app_code = cm.getAppCode();
    this._cit = cm.getCIT();

    let cit = "";
    if (this._cit)
        cit = ".cit";
    this._baseUrl = "https://pde" + cit + ".api.here.com/1/tile.json";

    if (opt && opt.rps) this._rps = opt.rps; // set RPS limit
    if (opt && opt.concurrent) this._concurrent = opt.concurrent; // set RPS limit

    /* const throttleSettings = {
        active: true, // set false to pause queue
        rate: this._rps, // how many requests can be sent every `ratePer`
        ratePer: 1000, // number of ms in which `rate` requests may be sent
        concurrent: this._concurrent // how many requests can be sent concurrently
    };

    this._throttle = new Throttle(throttleSettings);
*/
    return this;
};


/**
 * define the layers to be fetched
 * @alias hm:PDEManager.setLayers
 * @param layers {object}  - {layerName:{level,callback,fcLayers},.....}
 * @param [layers.layerName] {string} - name of layer
 * @param [layers.callback] {function} - function to be called for each PDE request. parameters for call: (content,x,y,z,layerName)
 * @param [layers.level] {integer} - level for tile associated to specific layer. if not specified, layer.fcLayers is used 
 * @param [layers.fcLayers]{array} - array of FC level, from 1 to 5
 * ```js
 * const layers = {
 *     ROAD_GEOM_FC: {
 *        callback: pdeGotRoadGeomFC,
 *        fcLayers: [1,2]
 *     }, 
 *     POI_BIGGER: {
 *       callback: pdeGotPOI,
 *       level: 6
 *   };
 * 
 *  pde.setLayers(layers);
 *  ```
 */
PDEManager.prototype.setLayers = function (layers) {
    this._layers = layers;
};

PDEManager.prototype.listLayers = function () {
    const settings = cm.addCredentials();
    const url = cm.buildUrl("pde", "api.here.com/1/doc/layers.json");
    return cm.hereRest(url, settings, "get", false);

};

/*
 * in list of layer, if a leyr has fcLayer set, will create subset for each fcLayer
 * return detailed list 
 */
PDEManager.prototype._layer2FcLayers = function () {

    let fullLayers = {};
    let layers = Object.keys(this._layers);

    for (let i = 0; i < layers.length; i++) {
        let layerName = layers[i];
        let layer = this._layers[layerName];

        // no fcLayers, just push as is 
        if (!Array.isArray(layer["fcLayers"]))
            fullLayers[layerName] = layer;
        else {
            layer["fcLayers"].forEach(fc => {
                let level = fc + 8; // FC1=> level 1+8 = 9
                let name = layerName + fc; // ROAD_GEOM_FC => ROAD_GEOM_FC1
                fullLayers[name] = {
                    level: level,
                    callback: layer["callback"]
                };
            });
        }
    }
    return fullLayers;
};

/**
 * sets a bounding box to retrieve all the tiles in it
 * @alias hm:PDEManager.setBoundingBox
 * @param bb {Object} - bounding box {latm,latM,lngm,lngM}
 * ```js
 * const bb={
 *    latm: 43.3494,
 *    latM: 43.8325,
 *    lngm: 2.2221
 *    lngM: 2.5685
 * };
 * pde.setBoundingBox(bb);
 *  ```
 */
PDEManager.prototype.setBoundingBox = function (bb) {

    if (!this._layers) return;

    this._queue = {}; // reset of whatever is in the queue

    // convert any layer with fcLayers into spearate entries
    let fullLayers = this._layer2FcLayers();
    // get all layer Name to process
    let layers = Object.keys(fullLayers);

    for (let i = 0; i < layers.length; i++) {
        const layerName = layers[i];

        const layer = fullLayers[layerName];

        const level = layer.level;
        const callback = layer.callback;
        //console.log("setBoundingBox bb",bb);

        if (level==-1) {
            let key = `0/0/0/${layerName}`;
            this._queue[key] = callback;
            continue;
        }

        let minY = bb.latm,
            maxY = bb.latM,
            minX = bb.lngm,
            maxX = bb.lngM;
        let tileSizeDegree = 180.0 / (1 << level);
        for (let tileY = Math.floor((minY + 90.0) / tileSizeDegree); tileY <= Math.floor((maxY + 90.0) / tileSizeDegree); tileY++) {
            for (let tileX = Math.floor((minX + 180.0) / tileSizeDegree); tileX <= Math.floor((maxX + 180.0) / tileSizeDegree); tileX++) {
                // add in queue
                //console.log(`setBoundingBox ${tileX}/${tileY}/${level}`);

                let key = `${tileX}/${tileY}/${level}/${layerName}`;
                this._queue[key] = callback;
            }
        }
    }
};

/**
 * sets a list of polygons to contain the tiles.
 * A Til is IN if one of the corner is in one of the polygons 
 * @alias hm:PDEManager.setBoundingPolys
 * @param polygons {array} - array of [[lat,lng],[lat,lng]..]
 * @param add {boolean} - false by default. set to true to add to existing queue
 * ```js
 * const poly=[
 *      [[43.3,2.8],[43.35,2.34],[43.37,2.38]],
 *      [[48.8,0.4],[48.81,0.456],48.82,0.433],[48.83,0.45]
 * ];
 * pde.setBoundingBox(bb);
 *  ```
 */
PDEManager.prototype.setBoundingPolys = function (polygons, add = false) {

    if (!this._layers) return;

    if (!add)
        this._queue = {}; // reset of whatever is in the queue

    // convert any layer with fcLayers into spearate entries
    let fullLayers = this._layer2FcLayers();
    // get all layer Name to process
    let layers = Object.keys(fullLayers);

    for (let i = 0; i < layers.length; i++) {
        const layerName = layers[i];

        const layer = fullLayers[layerName];

        const level = layer.level;
        let tileSizeDegree = 180.0 / (1 << level);
        const callback = layer.callback;

        if (level==-1) {
            let key = `0/0/0/${layerName}`;
            this._queue[key] = callback;
            continue;
        }

        for (let p = 0; p < polygons.length; p++) {
            let poly = polygons[p];

            //si trop long, ajoute les bords et simplifie
            if (poly.length > 20) {
                poly.forEach(coord => {
                    const [y, x] = coord;

                    let tileY = Math.floor((y + 90.0) / tileSizeDegree);
                    let tileX = Math.floor((x + 180.0) / tileSizeDegree);
                    let key = `${tileX}/${tileY}/${level}/${layerName}`;
                    this._queue[key] = callback;

                });
                //console.log("simplify",Object.keys(this._queue).length);
                poly = ge.simplify(poly, tileSizeDegree);
            }

            let bb = ge.coords2BB(poly);
            //console.log("setPolygons bb",bb);
            let minY = bb.latm,
                maxY = bb.latM,
                minX = bb.lngm,
                maxX = bb.lngM;

            for (let tileY = Math.floor((minY + 90.0) / tileSizeDegree); tileY <= Math.floor((maxY + 90.0) / tileSizeDegree); tileY++) {
                for (let tileX = Math.floor((minX + 180.0) / tileSizeDegree); tileX <= Math.floor((maxX + 180.0) / tileSizeDegree); tileX++) {
                    // add in queue

                    // Check if tile intersecs polygon, not only the 4 corners
                    let tileBB = ge.tile2BB(tileX, tileY, level);
                    let tileCoords = ge.bb2Poly(tileBB);

                    let intersect;

                    //const poly1 = turf.polygon([tileCoords]);
                    //const poly2 = turf.polygon([poly]);
                    //intersect = turf.intersect(poly1, poly2);
                    intersect = ge.fastPolyIntersectsPoly(tileCoords, poly);
                    //console.log(`${tileX}/${tileY}/${level} => ${intersect}`);

                    if (!intersect)
                        continue;

                    let key = `${tileX}/${tileY}/${level}/${layerName}`;
                    this._queue[key] = callback;
                }
            }
        } //of polygons

    }
};



/**
 * retrieve all tiles needed for a list of coords
 * @alias hm:PDEManager.setBoundingCoords
 * @param coords {array} - array of [lat,lng]
 * @param add {boolean} - false by default. set to true to add to existing queue
 * ```js
 * const bb={
 *    latm: 43.3494,
 *    latM: 43.8325,
 *    lngm: 2.2221
 *    lngM: 2.5685
 * };
 * pde.setBoundingBox(bb);
 *  ```
 */
PDEManager.prototype.setBoundingCoords = function (coords, add = false) {
    if (!this._layers) return;

    if (!add)
        this._queue = {}; // reset of whatever is in the queue

    // convert any layer with fcLayers into spearate entries
    let fullLayers = this._layer2FcLayers();
    // get all layer Name to process
    let layers = Object.keys(fullLayers);

    for (let i = 0; i < layers.length; i++) {
        const layerName = layers[i];

        const layer = fullLayers[layerName];
        const level = layer.level;
        const callback = layer.callback;
        const tileSizeDegree = 180.0 / (1 << level);

        if (level==-1) {
            let key = `0/0/0/${layerName}`;
            this._queue[key] = callback;
            continue;
        }

        coords.forEach(coord => {

            let tileY = Math.floor((coord[0] + 90.0) / tileSizeDegree);
            let tileX = Math.floor((coord[1] + 180.0) / tileSizeDegree);
            
            let key = `${tileX}/${tileY}/${level}/${layerName}`;
            //console.log("setCoords",coord, key);
            this._queue[key] = callback;
        });
    }
};

/**
 * set the queue to run, form a list of tiles
 * @alias hm:PDEManager.setQueue
 * @param queue {object} - {key:callback,key:callback,....} where key = "x/y/z/layername"
 */
PDEManager.prototype.setQueue = function (queue) {
    this._queue = queue;
};

/**
 * run the queries in the queue, with throttle in place
 * @alias hm:PDEManager.run
 * @returns {Promise} - returns promise with count of tiles as parameter
 */
PDEManager.prototype.run = function () {

    let _urlStart = `${this._baseUrl}?app_id=${this._app_id}&app_code=${this._app_code}&meta=1`;
    const that = this;

    return new Promise(
        (resolve, reject) => {

            const total = Object.keys(this._queue).length;
            if (total == 0) {
                reject("PDEManager: empty queue");
            }
            let count = total;


            Object.keys(this._queue).forEach(key => {
                const cb = this._queue[key];
                const fields = key.split("/");
                //console.log("run fields",fields);
                const tileX = fields[0];
                const tileY = fields[1];
                const level = fields[2];
                const layerName = fields[3];
                let url = `${_urlStart}&layer=${layerName}&level=${level}&tilex=${tileX}&tiley=${tileY}`;
                //console.log(url);

                cm.hereRestThrottle(url, null, null, {
                    rpsMode: "pde",
                    rps: that._rps,
                    concurrent: that._concurrent,
                }).then(res => {
                    cb(res.body, tileX, tileY, level, layerName);
                    count--;
                    if (!count) resolve(total);
                });
            });

        });

};


/**
 * provides array of rectangle showing various info for each tile downloaded
 * @alias hm:PDEManager.getTilesInfo
 * @returns {array} of {bb,tileX,tileY,level} for each tiles downloaded
 */
PDEManager.prototype.getTilesInfo = function () {
    if (!this._queue || !Object.keys(this._queue).length) {
        //    throw "prototype.getTilesRect";
        console.error("prototype.getTilesRect, empty queue");
        return [];
    }

    let results = [];

    Object.keys(this._queue).forEach(key => {
        const fields = key.split("/");
        //console.log("getTilesRect",fields);
        const tileX = fields[0];
        const tileY = fields[1];
        const level = fields[2];

        let bb = ge.tile2BB(tileX, tileY, level);
        results.push({
            bb: bb,
            tileX: tileX,
            tileY: tileY,
            level: level
        });
    });
    return results;
};


/**
 * returns the current queue to be processed
 * @alias hm:PDEManager.getQueue
 * @returns {object} - {key:callback,key:callback,....} where key = "x/y/z/layername"
 */
PDEManager.prototype.getQueue = function () {
    return this._queue;
};



module.exports = {
    PDEManager: PDEManager
};