const simplifyJs = require("simplify-js");

/**
 * Convert array of [lat,lng] to array of {x,y}
 * @alias hm:coords2XY
 * @param {array} coords array of [lat,lng]
 * @return {array}array of {x,y}
 */
function coords2XY(coords) {
    return coords.map(coord => {
        return {
            x: coord[1],
            y: coord[0]
        };
    });
}
/**
 * Convert array of {x,y} to array of [lat,lng]
 * @alias hm:xy2Coords
 * @param {array} coords array of {x,y} 
 * @return {array}array of [lat,lng]
 */
function xy2Coords(xys) {
    return xys.map(xy => [xy.y, xy.x]);
}



/**
 * Convert an object {lat,lng} to [lat,lng]
 * @alias hm:coordO2A
 * @param {object} obj {lat,lng}
 * @return {array} [lat,lng]
 */
function coordO2A(obj) {
    return [obj.lat, obj.lng];
}

/**
 * Convert an array [lat,lng] to {lat,lng}
 * @alias hm:coordA2O
 * @param {object} arr [lat,lng]
 * @return {array} {lat,lng}
 */
function coordA2O(arr) {
    return {
        lat: arr[0],
        lng: arr[1]
    };
}


/**
 * Convert coords of format PDE: lat or lng  initial/100000, delta / 100000
 * @alias hm:coordsPDE2Array
 * @param {array} lats
 * @param {array} lng
 * @return {array} [lat,lng]
 */
function coordsPDE2Array(latsInput, lngsInput) {
    var lats = latsInput.split(",");
    var lngs = lngsInput.split(",");
    //_HM.log("+pdeGotRoadGeomFC lat: "+resp.Rows[i].LAT+" "+lats.length);

    var k = 100000,
        coords = [];
    var lat = parseInt(lats[0]);
    var lng = parseInt(lngs[0]);

    coords.push(new Array(lat / k, lng / k));

    for (var j = 1; j < lats.length; j++) {
        lat += (lats[j] == "") ? 0 : parseInt(lats[j]);
        lng += (lngs[j] == "") ? 0 : parseInt(lngs[j]);
        coords.push(new Array(lat / k, lng / k));
    }
    return coords;
}



/**
 * Convert [lat,lng] to {lat,lng}
 * @alias hm:coord2Point
 * @param {array}  - [lat,lng]
 * @return {object} - {lat,lng}
 */
function coord2Point(coord) {
    return {
        lat: coord[0],
        lng: coord[1]
    };
}
/**
 * Convert {lat,lng} to [lat,lng]
 * @alias hm:point2Coord
 * @param {object} {lat,lng}  - object to convert
 * @return {array} [lat,lng]  - array converted
 */
function point2Coord(point) {
    return [point.lat, point.lng];
}


/**
 * Simplify a polyline by using the Ramer-Douglas-Peucker algorithm
 * @alias hm:simplify
 * @param coords {array} - array of [lat,lng]
 * @param tolerance {number} - tolerance for simplification
 * @return {array} simplified polyline as array of [lat,lng]
 */
function simplify(coords, tolerance, highacc = false) {

    // convert to xy
    let xy = coords2XY(coords);
    let simplified = simplifyJs(xy, tolerance, highacc);

    if (simplified.length < 1) // not enough points
        return coords;

    // convert back to [lat,lng]
    return simplified.map(xy => [xy.y, xy.x]);
}

/**
 * count the number of tiles for a given bounding box and zoom level
 * @alias hm:countTiles
 * @param bbox {array}  - bounding box, as {latm,latM,lngm,lngM};
 * @param level {int}  - zoom level
 * @return {int} count of tiles
 */
function countTiles(bbox, level) {

    let count = 0;
    let minY = bbox.latm,
        maxY = bbox.latM,
        minX = bbox.lngm,
        maxX = bbox.lngM,
        tileSizeDegree = 180.0 / (1 << level);
    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++)
            count++;
    }
    return count;
}


/**
 * convert a bounding box into a list of five [lat,lng], last is same as first
 * @alias hm:bb2Poly
 * @param bb {Object} - {latm,latM,lngm,lngM}
 * @return {array} - [[latm,lngm],[latM,lngm],[latM,lngM],[latm,lngM],[latm,lngm]]
 */
function bb2Poly(bb) {
    return [
        [bb.latm, bb.lngm],
        [bb.latM, bb.lngm],
        [bb.latM, bb.lngM],
        [bb.latm, bb.lngM],
        [bb.latm, bb.lngm]
    ];
}

/**
 * compute the bounding box of an array of corrds
 * @alias hm:coords2bb
 * @param coords {array} - [[lat,lng],[lat,lng]]
 * @return {Object} - {latm,latM,lngm,lngM}
 */
function coords2BB(coords) {
    let bb = {
        latm: 1 / 0,
        latM: -1 / 0,
        lngm: 1 / 0,
        lngM: -1 / 0
    };
    if (!coords) return null;

    coords.forEach(coord => {
        if (coord[0] < bb.latm) bb.latm = coord[0];
        if (coord[1] < bb.lngm) bb.lngm = coord[1];
        if (coord[0] > bb.latM) bb.latM = coord[0];
        if (coord[1] > bb.lngM) bb.lngM = coord[1];
    });

    return bb;
}

/**
 * compute the bounding box of a Tile
 * @alias hm:tile2BB
 * @param tileX {int} - X coord of the tile
 * @param tileY {int} - Y coord of the tile
 * @param level {int} - zoom level
 * @return {Object} - {latm,latM,lngm,lngM}
 */
function tile2BB(tileX, tileY, level) {
    const tileSizeDegree = 180.0 / (1 << level);

    const miny = tileY * tileSizeDegree - 90.0;
    const minx = tileX * tileSizeDegree - 180.0;
    const maxy = miny + tileSizeDegree - 0.000000001;
    const maxx = minx + tileSizeDegree - 0.000000001;
    return {
        latm: miny,
        latM: maxy,
        lngm: minx,
        lngM: maxx
    };
}


/**
 * Check if a point is inside a polygon
 * @alias hm.pointInPoly
 * @param {coord} point as [lat,lng]
 * @param {array} poly - array of [[lat,lng],[lat,lng]]
 */
function pointInPoly(point, poly) {
    // ray-casting algorithm based on
    // http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html

    var x = point[0],
        y = point[1];

    var inside = false;
    for (var i = 0, j = poly.length - 1; i < poly.length; j = i++) {
        var xi = poly[i][0],
            yi = poly[i][1];
        var xj = poly[j][0],
            yj = poly[j][1];

        var intersect = ((yi > y) != (yj > y)) &&
            (x < (xj - xi) * (y - yi) / (yj - yi) + xi);
        if (intersect) inside = !inside;
    }

    return inside;
}


/**
 * 
 * @param {*} A 
 * @param {*} B 
 * @param {*} C 
 * @param {*} D 
 */
function lineIntersects(A, B, C, D) {

    const y1 = A[0],
        x1 = A[1];
    const y2 = B[0],
        x2 = B[1];
    const y3 = C[0],
        x3 = C[1];
    const y4 = D[0],
        x4 = D[1];

    const denom = ((y4 - y3) * (x2 - x1)) - ((x4 - x3) * (y2 - y1));
    const numeA = ((x4 - x3) * (y1 - y3)) - ((y4 - y3) * (x1 - x3));
    const numeB = ((x2 - x1) * (y1 - y3)) - ((y2 - y1) * (x1 - x3));

    if (denom == 0) {
        return false;
    }

    const uA = numeA / denom;
    const uB = numeB / denom;

    if (uA >= 0 && uA <= 1 && uB >= 0 && uB <= 1) {
        return true;
    }

    return false;
}

/**
 * Check if a polygon intersects another polygon
 * check only if a point of a polygon is inside the other
 * @alias hm.polyIntersectsPoly
 * @param {*} polyA 
 * @param {*} polyB 
 */
function fastPolyIntersectsPoly(polyA, polyB) {
    //un point de A dans B ?
    let inside = false;
    for (let i = 0; i < polyA.length; i++) {
        const point = polyA[i];
        //console.log("A",point,polyB);
        if (pointInPoly(point, polyB)) {
            inside = true;
            break;
        }
    }
    if (inside) return true;

    for (let i = 0; i < polyB.length; i++) {
        const point = polyB[i];
        // console.log("B",point,polyA);

        if (pointInPoly(point, polyA)) {
            inside = true;
            break;
        }
    }
    if (inside) return true;

    return false;

}

module.exports = {
    coordO2A: coordO2A,
    coordA2O: coordA2O,
    coordsPDE2Array: coordsPDE2Array,
    coords2XY: coords2XY,
    xy2Coords: xy2Coords,
    coord2Point: coord2Point,
    point2Coord: point2Coord,
    simplify: simplify,
    countTiles: countTiles,
    bb2Poly: bb2Poly,
    coords2BB: coords2BB,
    tile2BB: tile2BB,
    pointInPoly: pointInPoly,
    lineIntersects:lineIntersects,
    fastPolyIntersectsPoly: fastPolyIntersectsPoly
};