import { CompositeLayer } from "@deck.gl/core";
import { isDrawingEnabled } from "../utils/layerTools";
import { GeoJsonLayer, PathLayer } from "@deck.gl/layers";
import { subtract, distance, dot } from "mathjs";
import { rgbValues, colorTableData, } from "@emerson-eps/color-tables/";
import { createPropertyData, } from "../utils/layerTools";
import { splineRefine } from "./utils/spline";
import { interpolateNumberArray } from "d3";
import { layersDefaultProps } from "../layersDefaultProps";
export default class WellsLayer extends CompositeLayer {
    onClick(info) {
        var _a;
        // Make selection only when drawing is disabled
        if (isDrawingEnabled(this.context.layerManager)) {
            return false;
        }
        else {
            this.context.userData.setEditedData({
                selectedWell: (_a = info.object.properties) === null || _a === void 0 ? void 0 : _a["name"],
            });
            return true;
        }
    }
    shouldUpdateState({ changeFlags, }) {
        return (changeFlags.viewportChanged ||
            changeFlags.propsOrDataChanged ||
            changeFlags.updateTriggersChanged);
    }
    renderLayers() {
        if (!this.props.data.features) {
            return [];
        }
        const refine = this.props.refine;
        const data = refine
            ? splineRefine(this.props.data) // smooth well paths.
            : this.props.data;
        const is3d = this.context.viewport.constructor.name === "OrbitViewport";
        const positionFormat = is3d ? "XYZ" : "XY";
        const outline = new GeoJsonLayer(this.getSubLayerProps({
            id: "outline",
            data,
            pickable: false,
            stroked: false,
            positionFormat,
            pointRadiusUnits: "pixels",
            lineWidthUnits: "pixels",
            pointRadiusScale: this.props.pointRadiusScale,
            lineWidthScale: this.props.lineWidthScale,
        }));
        const getColor = (d) => { var _a; return (_a = d === null || d === void 0 ? void 0 : d.properties) === null || _a === void 0 ? void 0 : _a["color"]; };
        const colors = new GeoJsonLayer(this.getSubLayerProps({
            id: "colors",
            data,
            pickable: true,
            stroked: false,
            positionFormat,
            pointRadiusUnits: "pixels",
            lineWidthUnits: "pixels",
            pointRadiusScale: this.props.pointRadiusScale - 1,
            lineWidthScale: this.props.lineWidthScale - 1,
            getFillColor: getColor,
            getLineColor: getColor,
        }));
        // Highlight the selected well.
        const highlight = new GeoJsonLayer(this.getSubLayerProps({
            id: "highlight",
            data: getWellObjectByName(data.features, this.props.selectedWell),
            pickable: false,
            stroked: false,
            positionFormat,
            pointRadiusUnits: "pixels",
            lineWidthUnits: "pixels",
            pointRadiusScale: this.props.pointRadiusScale + 2,
            lineWidthScale: this.props.lineWidthScale + 2,
            getFillColor: getColor,
            getLineColor: getColor,
        }));
        const log_layer = new PathLayer(this.getSubLayerProps({
            id: "log_curve",
            data: this.props.logData,
            positionFormat,
            pickable: true,
            widthScale: 10,
            widthMinPixels: 1,
            miterLimit: 100,
            getPath: (d) => getLogPath(data.features, d, this.props.logrunName),
            getColor: (d) => getLogColor(d, this.props.logrunName, this.props.logName, this.props.logColor, this.context.userData
                .colorTables),
            getWidth: (d) => this.props.logRadius ||
                getLogWidth(d, this.props.logrunName, this.props.logName),
            updateTriggers: {
                getColor: [
                    this.props.logrunName,
                    this.props.logName,
                    this.context.userData
                        .colorTables,
                    this.props.logName,
                    this.props.logColor,
                ],
                getWidth: [
                    this.props.logrunName,
                    this.props.logName,
                    this.props.logRadius,
                ],
                getPath: [positionFormat],
            },
            onDataLoad: (value) => {
                this.setState({
                    legend: getLegendData(value, this.props.logName, this.props.logColor),
                });
            },
        }));
        const layers = [colors, highlight];
        if (this.props.outline) {
            layers.splice(0, 0, outline);
        }
        if (this.props.logCurves) {
            layers.splice(1, 0, log_layer);
        }
        return layers;
    }
    // For now, use `any` for the picking types because this function should
    // recieve PickInfo<FeatureCollection>, but it recieves PickInfo<Feature>.
    //eslint-disable-next-line
    getPickingInfo({ info }) {
        if (!info.object)
            return info;
        const md_property = getMdProperty(info.coordinate, info.object);
        const tvd_property = getTvdProperty(info.coordinate, info.object);
        const log_property = getLogProperty(info.coordinate, this.props.data.features, info.object, this.props.logrunName, this.props.logName);
        // Patch for inverting tvd readout to fix issue #830,
        // should make proper fix when handling z increase direction - issue #842
        const inverted_tvd_property = tvd_property && Object.assign(Object.assign({}, tvd_property), { value: (tvd_property === null || tvd_property === void 0 ? void 0 : tvd_property.value) * -1 });
        const layer_properties = [];
        if (md_property)
            layer_properties.push(md_property);
        if (inverted_tvd_property)
            layer_properties.push(inverted_tvd_property);
        if (log_property)
            layer_properties.push(log_property);
        return Object.assign(Object.assign({}, info), { properties: layer_properties, logName: log_property === null || log_property === void 0 ? void 0 : log_property.name });
    }
}
WellsLayer.layerName = "WellsLayer";
WellsLayer.defaultProps = layersDefaultProps["WellsLayer"];
//================= Local help functions. ==================
function getColumn(data, col) {
    const column = [];
    for (let i = 0; i < data.length; i++) {
        column.push(data[i][col]);
    }
    return column;
}
function getLogMd(d, logrun_name) {
    if (!isSelectedLogRun(d, logrun_name))
        return [];
    const names_md = ["DEPTH", "DEPT", "MD", "TDEP"]; // aliases for MD
    const log_id = getLogIndexByNames(d, names_md);
    return log_id >= 0 ? getColumn(d.data, log_id) : [];
}
export function getLogValues(d, logrun_name, log_name) {
    if (!isSelectedLogRun(d, logrun_name))
        return [];
    const log_id = getLogIndexByName(d, log_name);
    return log_id >= 0 ? getColumn(d.data, log_id) : [];
}
export function getLogInfo(d, logrun_name, log_name) {
    if (!isSelectedLogRun(d, logrun_name))
        return undefined;
    const log_id = getLogIndexByName(d, log_name);
    return d.curves[log_id];
}
function getDiscreteLogMetadata(d, log_name) {
    return d === null || d === void 0 ? void 0 : d.metadata_discrete[log_name];
}
function isSelectedLogRun(d, logrun_name) {
    return d.header.name.toLowerCase() === logrun_name.toLowerCase();
}
function getWellObjectByName(wells_data, name) {
    return wells_data === null || wells_data === void 0 ? void 0 : wells_data.find((item) => { var _a, _b; return ((_b = (_a = item.properties) === null || _a === void 0 ? void 0 : _a["name"]) === null || _b === void 0 ? void 0 : _b.toLowerCase()) === (name === null || name === void 0 ? void 0 : name.toLowerCase()); });
}
function getWellHeadCoordinates(well_object) {
    var _a, _b;
    return (_b = (_a = well_object === null || well_object === void 0 ? void 0 : well_object.geometry) === null || _a === void 0 ? void 0 : _a.geometries.find((item) => item.type == "Point")) === null || _b === void 0 ? void 0 : _b.coordinates;
}
function getWellCoordinates(well_object) {
    var _a, _b;
    return (_b = (_a = well_object === null || well_object === void 0 ? void 0 : well_object.geometry) === null || _a === void 0 ? void 0 : _a.geometries.find((item) => item.type == "LineString")) === null || _b === void 0 ? void 0 : _b.coordinates;
}
function getWellMds(well_object) {
    var _a;
    return (_a = well_object === null || well_object === void 0 ? void 0 : well_object.properties) === null || _a === void 0 ? void 0 : _a["md"][0];
}
function getNeighboringMdIndices(mds, md) {
    const idx = mds.findIndex((x) => x >= md);
    return idx === 0 ? [idx, idx + 1] : [idx - 1, idx];
}
function getLogPath(wells_data, d, logrun_name) {
    const well_object = getWellObjectByName(wells_data, d.header.well);
    const well_xyz = getWellCoordinates(well_object);
    const well_mds = getWellMds(well_object);
    if (well_xyz == undefined ||
        well_mds == undefined ||
        well_xyz.length == 0 ||
        well_mds.length == 0)
        return [];
    const log_xyz = [];
    const log_mds = getLogMd(d, logrun_name);
    log_mds.forEach((md) => {
        const [l_idx, h_idx] = getNeighboringMdIndices(well_mds, md);
        const md_normalized = (md - well_mds[l_idx]) / (well_mds[h_idx] - well_mds[l_idx]);
        const xyz = interpolateNumberArray(well_xyz[l_idx], well_xyz[h_idx])(md_normalized);
        log_xyz.push(xyz);
    });
    return log_xyz;
}
function getLogIndexByName(d, log_name) {
    return d.curves.findIndex((item) => item.name.toLowerCase() === log_name.toLowerCase());
}
function getLogIndexByNames(d, names) {
    for (const name of names) {
        const index = d.curves.findIndex((item) => item.name.toLowerCase() === name.toLowerCase());
        if (index >= 0)
            return index;
    }
    return -1;
}
function getLogColor(d, logrun_name, log_name, logColor, colorTables) {
    var _a;
    const log_data = getLogValues(d, logrun_name, log_name);
    const log_info = getLogInfo(d, logrun_name, log_name);
    if (log_data.length == 0 || log_info == undefined)
        return [];
    const log_color = [];
    if (log_info.description == "continuous") {
        const min = Math.min(...log_data);
        const max = Math.max(...log_data);
        const max_delta = max - min;
        log_data.forEach((value) => {
            const rgb = rgbValues((value - min) / max_delta, logColor, colorTables);
            if (rgb != undefined) {
                if (Array.isArray(rgb)) {
                    log_color.push([rgb[0], rgb[1], rgb[2]]);
                }
                else {
                    log_color.push([rgb.r, rgb.g, rgb.b]);
                }
            }
        });
    }
    else {
        const colorsArray = colorTableData(logColor, colorTables);
        const log_attributes = (_a = getDiscreteLogMetadata(d, log_name)) === null || _a === void 0 ? void 0 : _a.objects;
        // eslint-disable-next-line
        const attributesObject = {};
        Object.keys(log_attributes).forEach((key) => {
            // get the code from log_attributes
            const code = log_attributes[key][1];
            // compare the code and first value from colorsArray(colortable)
            const colorArrays = colorsArray.find((value) => {
                return value[0] == code;
            });
            if (colorArrays)
                attributesObject[key] = [
                    [colorArrays[1], colorArrays[2], colorArrays[3]],
                    code,
                ];
        });
        log_data.forEach((log_value) => {
            var _a;
            const dl_attrs = (_a = Object.entries(attributesObject).find(([, value]) => value[1] == log_value)) === null || _a === void 0 ? void 0 : _a[1];
            dl_attrs ? log_color.push(dl_attrs[0]) : log_color.push([0, 0, 0]);
        });
    }
    return log_color;
}
function getLogWidth(d, logrun_name, log_name) {
    return getLogValues(d, logrun_name, log_name);
}
function squared_distance(a, b) {
    const dx = a[0] - b[0];
    const dy = a[1] - b[1];
    return dx * dx + dy * dy;
}
// Return distance between line segment vw and point p
function distToSegmentSquared(v, w, p) {
    const l2 = squared_distance(v, w);
    if (l2 == 0)
        return squared_distance(p, v);
    let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
    t = Math.max(0, Math.min(1, t));
    return squared_distance(p, [
        v[0] + t * (w[0] - v[0]),
        v[1] + t * (w[1] - v[1]),
    ]);
}
// Interpolates point closest to the coords on trajectory
function interpolateDataOnTrajectory(coord, data, trajectory) {
    // if number of data points in less than 1 or
    // length of data and trajectory are different we cannot interpolate.
    if (data.length <= 1 || data.length != trajectory.length)
        return -1;
    // Identify closest well path leg to coord.
    const segment_index = getSegmentIndex(coord, trajectory);
    const index0 = segment_index;
    const index1 = index0 + 1;
    // Get the nearest data.
    const data0 = data[index0];
    const data1 = data[index1];
    // Get the nearest survey points.
    const survey0 = trajectory[index0];
    const survey1 = trajectory[index1];
    const dv = distance(survey0, survey1);
    if (dv === 0) {
        return -1;
    }
    // Calculate the scalar projection onto segment.
    const v0 = subtract(coord, survey0);
    const v1 = subtract(survey1, survey0);
    // scalar_projection in interval [0,1]
    const scalar_projection = dot(v0, v1) / (dv * dv);
    // Interpolate data.
    return data0 * (1.0 - scalar_projection) + data1 * scalar_projection;
}
function getMd(coord, feature) {
    var _a, _b;
    if (!((_b = (_a = feature.properties) === null || _a === void 0 ? void 0 : _a["md"]) === null || _b === void 0 ? void 0 : _b[0]) || !feature.geometry)
        return null;
    const measured_depths = feature.properties["md"][0];
    const trajectory3D = getWellCoordinates(feature);
    if (trajectory3D == undefined)
        return null;
    let trajectory;
    // In 2D view coord is of type Position2D and in 3D view it's Position3D,
    // so use apropriate trajectory for interpolation
    if (coord.length == 2) {
        const trajectory2D = trajectory3D.map((v) => {
            return v.slice(0, 2);
        });
        trajectory = trajectory2D;
    }
    else {
        trajectory = trajectory3D;
    }
    return interpolateDataOnTrajectory(coord, measured_depths, trajectory);
}
function getMdProperty(coord, feature) {
    var _a, _b;
    const md = getMd(coord, feature);
    if (md != null) {
        const prop_name = "MD " + ((_a = feature.properties) === null || _a === void 0 ? void 0 : _a["name"]);
        return createPropertyData(prop_name, md, (_b = feature.properties) === null || _b === void 0 ? void 0 : _b["color"]);
    }
    return null;
}
function getTvd(coord, feature) {
    var _a;
    const trajectory3D = getWellCoordinates(feature);
    // if trajectory is not found or if it has a data single point then get tvd from well head
    if (trajectory3D == undefined || (trajectory3D === null || trajectory3D === void 0 ? void 0 : trajectory3D.length) <= 1) {
        const wellhead_xyz = getWellHeadCoordinates(feature);
        return (_a = wellhead_xyz === null || wellhead_xyz === void 0 ? void 0 : wellhead_xyz[2]) !== null && _a !== void 0 ? _a : null;
    }
    let trajectory;
    // For 2D view coord is Position2D and for 3D view it's Position3D
    if (coord.length == 2) {
        const trajectory2D = trajectory3D === null || trajectory3D === void 0 ? void 0 : trajectory3D.map((v) => {
            return v.slice(0, 2);
        });
        trajectory = trajectory2D;
    }
    else {
        trajectory = trajectory3D;
    }
    const tvds = trajectory3D.map((v) => {
        return v[2];
    });
    return interpolateDataOnTrajectory(coord, tvds, trajectory);
}
function getTvdProperty(coord, feature) {
    var _a, _b;
    const tvd = getTvd(coord, feature);
    if (tvd != null) {
        const prop_name = "TVD " + ((_a = feature.properties) === null || _a === void 0 ? void 0 : _a["name"]);
        return createPropertyData(prop_name, tvd, (_b = feature.properties) === null || _b === void 0 ? void 0 : _b["color"]);
    }
    return null;
}
// Identify closest path leg to coord.
function getSegmentIndex(coord, path) {
    let min_d = Number.MAX_VALUE;
    let segment_index = 0;
    for (let i = 0; i < (path === null || path === void 0 ? void 0 : path.length) - 1; i++) {
        const d = distToSegmentSquared(path[i], path[i + 1], coord);
        if (d > min_d)
            continue;
        segment_index = i;
        min_d = d;
    }
    return segment_index;
}
// Returns segment index of discrete logs
function getLogSegmentIndex(coord, wells_data, log_data, logrun_name) {
    const trajectory = getLogPath(wells_data, log_data, logrun_name);
    return getSegmentIndex(coord, trajectory);
}
function getLogProperty(coord, wells_data, log_data, logrun_name, log_name) {
    var _a, _b, _c;
    if (!log_data.data)
        return null;
    const segment_index = getLogSegmentIndex(coord, wells_data, log_data, logrun_name);
    let log_value = getLogValues(log_data, logrun_name, log_name)[segment_index];
    let dl_attrs = undefined;
    const dl_metadata = (_a = getDiscreteLogMetadata(log_data, log_name)) === null || _a === void 0 ? void 0 : _a.objects;
    if (dl_metadata) {
        dl_attrs = Object.entries(dl_metadata).find(([, value]) => value[1] == log_value);
    }
    const log = (_b = getLogInfo(log_data, logrun_name, log_name)) === null || _b === void 0 ? void 0 : _b.name;
    const prop_name = log + " " + log_data.header.well;
    log_value = dl_attrs ? dl_attrs[0] + " (" + log_value + ")" : log_value;
    if (log_value) {
        const well_object = getWellObjectByName(wells_data, log_data.header.well);
        return createPropertyData(prop_name, log_value, (_c = well_object === null || well_object === void 0 ? void 0 : well_object.properties) === null || _c === void 0 ? void 0 : _c["color"]);
    }
    else
        return null;
}
// Return data required to build welllayer legend
function getLegendData(logs, logName, logColor) {
    const logInfo = getLogInfo(logs[0], logs[0].header.name, logName);
    const title = "Wells / " + logName;
    const legendProps = [];
    if ((logInfo === null || logInfo === void 0 ? void 0 : logInfo.description) == "discrete") {
        const meta = logs[0]["metadata_discrete"];
        const metadataDiscrete = meta[logName].objects;
        legendProps.push({
            title: title,
            colorName: logColor,
            discrete: true,
            metadata: metadataDiscrete,
            valueRange: [],
        });
        return legendProps;
    }
    else {
        const minArray = [];
        const maxArray = [];
        logs.forEach(function (log) {
            const logValues = getLogValues(log, log.header.name, logName);
            minArray.push(Math.min(...logValues));
            maxArray.push(Math.max(...logValues));
        });
        legendProps.push({
            title: title,
            name: logName,
            colorName: logColor,
            discrete: false,
            metadata: { objects: {} },
            valueRange: [Math.min(...minArray), Math.max(...maxArray)],
        });
        return legendProps;
    }
}
//# sourceMappingURL=wellsLayer.js.map