import _, { isEmpty, isEqual } from "lodash";
import { CompositeLayer } from "@deck.gl/core";
import { PathStyleExtension } from "@deck.gl/extensions";
import { GeoJsonLayer } from "@deck.gl/layers";
import { GL } from "@luma.gl/constants";
import { createPropertyData, getFromAccessor, getLayersById, isDrawingEnabled, } from "../utils/layerTools";
import { SectionViewport } from "../../viewports";
import { WellLabelLayer } from "./layers/wellLabelLayer";
import { abscissaTransform } from "./utils/abscissaTransform";
import { GetBoundingBox, checkWells, coarsenWells, invertPath, splineRefine, } from "./utils/spline";
import { getColor, getMd, getTrajectory, getTvd } from "./utils/trajectory";
import { LogCurveLayer } from "./layers/logCurveLayer";
import { getWellMds, getWellObjectByName, getWellObjectsByName, } from "./utils/wells";
import { getLegendData, getLogProperty } from "./utils/log";
import { getSize, LINE, POINT, DEFAULT_LINE_WIDTH } from "./utils/features";
import { scaleArray } from "../../utils/arrays";
const DEFAULT_DASH = [5, 5];
export var SubLayerId;
(function (SubLayerId) {
    SubLayerId["COLORS"] = "colors";
    SubLayerId["SIMPLE"] = "simple";
    SubLayerId["OUTLINE"] = "outline";
    SubLayerId["HIGHLIGHT"] = "highlight";
    SubLayerId["HIGHLIGHT_2"] = "highlight2";
    SubLayerId["LOG_CURVE"] = "log_curve";
    SubLayerId["SELECTION"] = "selection";
    SubLayerId["LABELS"] = "labels";
})(SubLayerId || (SubLayerId = {}));
const defaultProps = {
    "@@type": "WellsLayer",
    name: "Wells",
    id: "wells-layer",
    autoHighlight: true,
    opacity: 1,
    lineWidthScale: 1,
    pointRadiusScale: 1,
    lineStyle: { dash: false },
    outline: true,
    logRadius: 10,
    logCurves: true,
    refine: false,
    visible: true,
    selectedWell: "@@#editedData.selectedWells", // used to get data from deckgl layer
    depthTest: true,
    ZIncreasingDownwards: true,
    simplifiedRendering: false,
    section: false,
    dataTransform: coarsenWells,
    // Deprecated props
    wellNameColor: [0, 0, 0, 255],
    wellNameSize: 10,
};
export default class WellsLayer extends CompositeLayer {
    recomputeDataState() {
        const { data, refine, ZIncreasingDownwards, section } = this.props;
        const doRefine = typeof refine === "number" ? refine > 1 : refine;
        const stepCount = typeof refine === "number" ? refine : 5;
        if (!dataIsReady(data)) {
            return;
        }
        let transformedData = data;
        if (ZIncreasingDownwards) {
            transformedData = invertPath(transformedData);
        }
        // Create a state for the section projection of the data, which by default is
        // identical to the original transformed data.
        let sectionData = transformedData;
        // Mutate data to remove duplicates
        checkWells(transformedData);
        // Apply sectional projection if requested
        if (section) {
            if (typeof section === "function") {
                sectionData = section(transformedData);
            }
            else if (section === true) {
                sectionData = abscissaTransform(transformedData);
            }
        }
        // Mutate data to remove duplicates
        checkWells(sectionData);
        // Conditionally apply spline interpolation to refine the well path.
        if (doRefine) {
            transformedData = splineRefine(transformedData, stepCount);
            sectionData = splineRefine(sectionData, stepCount);
        }
        this.setState({ data: transformedData, sectionData });
    }
    shouldUpdateState({ changeFlags }) {
        return (changeFlags.viewportChanged ||
            changeFlags.propsOrDataChanged ||
            typeof changeFlags.updateTriggersChanged === "object");
    }
    updateState({ props, oldProps }) {
        const needs_reload = !isEqual(props.data, oldProps.data) ||
            !isEqual(props.ZIncreasingDownwards, oldProps.ZIncreasingDownwards) ||
            !isEqual(props.refine, oldProps.refine);
        if (needs_reload) {
            this.recomputeDataState();
        }
    }
    onClick(info) {
        // Make selection only when drawing is disabled
        if (isDrawingEnabled(this.context.layerManager)) {
            return false;
        }
        else {
            this.context.userData.setEditedData({
                selectedWell: info.wellName,
            });
            return false; // do not return true to allow DeckGL props.onClick to be called
        }
    }
    setSelection(well, selection) {
        if (!this.internalState)
            return;
        if (!well || !selection) {
            this.setState({
                logDomainSelection: undefined,
            });
        }
        else {
            this.setState({
                logDomainSelection: { well, selection },
            });
        }
    }
    setMultiSelection(wells) {
        if (this.internalState) {
            this.setState({
                selectedMultiWells: wells,
            });
        }
    }
    getWellDataState() {
        const { data, sectionData } = this.state;
        if (this.context.viewport.constructor === SectionViewport) {
            return sectionData;
        }
        return data;
    }
    // ? Unused ?
    getLegendData(value) {
        return getLegendData(value, "", this.props.logName, this.props.logColor);
    }
    setLegend(value) {
        this.setState({
            legend: this.getLegendData(value),
        });
    }
    getLogLayer() {
        var _a, _b;
        const sub_layers = (_a = this.internalState) === null || _a === void 0 ? void 0 : _a.subLayers;
        const log_layers = getLayersById(sub_layers, "wells-layer-log_curve");
        return (_b = log_layers === null || log_layers === void 0 ? void 0 : log_layers[0]) === null || _b === void 0 ? void 0 : _b.getCurveLayer();
    }
    getSelectionLayer() {
        var _a, _b;
        const sub_layers = (_a = this.internalState) === null || _a === void 0 ? void 0 : _a.subLayers;
        const log_layer = getLayersById(sub_layers, "wells-layer-log_curve");
        return (_b = log_layer === null || log_layer === void 0 ? void 0 : log_layer[0]) === null || _b === void 0 ? void 0 : _b.getSelectionLayer();
    }
    getLogCurveData() {
        return this.state.logData;
    }
    setupLegend() {
        const data = this.getLogCurveData();
        if (data)
            this.setLegend(data);
    }
    getWellLabelPosition() {
        var _a;
        if ((_a = this.props.wellLabel) === null || _a === void 0 ? void 0 : _a.getPositionAlongPath) {
            return this.props.wellLabel.getPositionAlongPath;
        }
        else if (!_.isUndefined(this.props.wellNameAtTop)) {
            // Backwards compatibility for `wellNameAtTop` prop.
            if (typeof this.props.wellNameAtTop === "boolean") {
                if (this.props.wellNameAtTop) {
                    return 0;
                }
                // if wellNameAtTop is false, then place name at bottom.
                return 1;
            }
            // `wellNameAtTop` can also be a number [0, 100] indicating the
            // percentage along the trajectory from the top.
            return this.props.wellNameAtTop / 100;
        }
        return 0;
    }
    createWellLabelLayer(data) {
        var _a, _b, _c, _d, _e;
        if (!this.props.wellLabel && !this.props.wellNameVisible) {
            return null;
        }
        const parameters = {
            [GL.DEPTH_TEST]: this.props.depthTest,
            [GL.POLYGON_OFFSET_FILL]: true,
        };
        const fastDrawing = this.props.simplifiedRendering;
        const wellLabelProps = this.getSubLayerProps(Object.assign(Object.assign({}, this.props.wellLabel), { id: SubLayerId.LABELS, data, pickable: true, 
            // Z is always increasing upwards at this stage
            zIncreasingDownwards: false, getPositionAlongPath: this.getWellLabelPosition(), getColor: (_b = (_a = this.props.wellLabel) === null || _a === void 0 ? void 0 : _a.getColor) !== null && _b !== void 0 ? _b : this.props.wellNameColor, getAnchor: "start", getSize: (_d = (_c = this.props.wellLabel) === null || _c === void 0 ? void 0 : _c.getSize) !== null && _d !== void 0 ? _d : this.props.wellNameSize, parameters, visible: !fastDrawing, background: ((_e = this.props.wellLabel) === null || _e === void 0 ? void 0 : _e.background) ||
                this.props.hideOverlappingWellNames }));
        return new WellLabelLayer(wellLabelProps);
    }
    renderLayers() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
        const data = this.getWellDataState();
        const positionFormat = "XYZ";
        const isDashed = !!((_a = this.props.lineStyle) === null || _a === void 0 ? void 0 : _a.dash);
        if (!data || !(data === null || data === void 0 ? void 0 : data.features.length)) {
            return [];
        }
        const extensions = [
            new PathStyleExtension({
                dash: isDashed,
                highPrecisionDash: isDashed,
            }),
        ];
        const parameters = {
            [GL.DEPTH_TEST]: this.props.depthTest,
            [GL.POLYGON_OFFSET_FILL]: true,
        };
        // Reduced details when rotating or panning the view if "fastDrawing " is set.
        const fastDrawing = this.props.simplifiedRendering;
        const defaultLayerProps = {
            data,
            pickable: false,
            stroked: false,
            positionFormat,
            pointRadiusUnits: "pixels",
            lineWidthUnits: "pixels",
            pointRadiusScale: this.props.pointRadiusScale,
            lineWidthScale: this.props.lineWidthScale,
            getLineWidth: getSize(LINE, (_b = this.props.lineStyle) === null || _b === void 0 ? void 0 : _b.width, -1),
            getPointRadius: getSize(POINT, (_c = this.props.wellHeadStyle) === null || _c === void 0 ? void 0 : _c.size, -1),
            lineBillboard: true,
            pointBillboard: true,
            parameters,
            visible: fastDrawing,
        };
        const colorsLayerProps = this.getSubLayerProps(Object.assign(Object.assign({}, defaultLayerProps), { id: SubLayerId.COLORS, pickable: true, extensions, getDashArray: getDashFactor((_d = this.props.lineStyle) === null || _d === void 0 ? void 0 : _d.dash, getSize(LINE, (_e = this.props.lineStyle) === null || _e === void 0 ? void 0 : _e.width), -1), visible: !fastDrawing, getLineColor: getColor((_f = this.props.lineStyle) === null || _f === void 0 ? void 0 : _f.color), getFillColor: getColor((_g = this.props.wellHeadStyle) === null || _g === void 0 ? void 0 : _g.color) }));
        const fastLayerProps = this.getSubLayerProps(Object.assign(Object.assign({}, defaultLayerProps), { id: SubLayerId.SIMPLE, positionFormat, getLineColor: getColor((_h = this.props.lineStyle) === null || _h === void 0 ? void 0 : _h.color), getFillColor: getColor((_j = this.props.wellHeadStyle) === null || _j === void 0 ? void 0 : _j.color) }));
        const outlineLayerProps = this.getSubLayerProps(Object.assign(Object.assign({}, defaultLayerProps), { id: SubLayerId.OUTLINE, getLineWidth: getSize(LINE, (_k = this.props.lineStyle) === null || _k === void 0 ? void 0 : _k.width), getPointRadius: getSize(POINT, (_l = this.props.wellHeadStyle) === null || _l === void 0 ? void 0 : _l.size), extensions, getDashArray: getDashFactor((_m = this.props.lineStyle) === null || _m === void 0 ? void 0 : _m.dash), visible: this.props.outline && !fastDrawing, parameters: Object.assign(Object.assign({}, parameters), { [GL.POLYGON_OFFSET_FACTOR]: 1, [GL.POLYGON_OFFSET_UNITS]: 1 }) }));
        const highlightLayerProps = this.getSubLayerProps(Object.assign(Object.assign({}, defaultLayerProps), { id: SubLayerId.HIGHLIGHT, data: getWellObjectByName(data.features, this.props.selectedWell), getLineWidth: getSize(LINE, (_o = this.props.lineStyle) === null || _o === void 0 ? void 0 : _o.width, 2), getPointRadius: getSize(POINT, (_p = this.props.wellHeadStyle) === null || _p === void 0 ? void 0 : _p.size, 2), getLineColor: getColor((_q = this.props.lineStyle) === null || _q === void 0 ? void 0 : _q.color), getFillColor: getColor((_r = this.props.wellHeadStyle) === null || _r === void 0 ? void 0 : _r.color), visible: this.props.logCurves && !fastDrawing }));
        const highlightMultiWellsLayerProps = this.getSubLayerProps(Object.assign(Object.assign({}, defaultLayerProps), { id: SubLayerId.HIGHLIGHT_2, data: getWellObjectsByName(data.features, this.state.selectedMultiWells), getPointRadius: getSize(POINT, (_s = this.props.wellHeadStyle) === null || _s === void 0 ? void 0 : _s.size, 2), getFillColor: [255, 140, 0], getLineColor: [255, 140, 0], visible: this.props.logCurves && !fastDrawing }));
        const fastLayer = new GeoJsonLayer(fastLayerProps);
        const outlineLayer = new GeoJsonLayer(outlineLayerProps);
        const colorsLayer = new GeoJsonLayer(colorsLayerProps);
        // Highlight the selected well.
        const highlightLayer = new GeoJsonLayer(highlightLayerProps);
        // Highlight the multi selected wells.
        const highlightMultiWellsLayer = new GeoJsonLayer(highlightMultiWellsLayerProps);
        const logLayer = new LogCurveLayer(Object.assign({}, this.getSubLayerProps({
            id: SubLayerId.LOG_CURVE,
            visible: this.props.logCurves && !fastDrawing,
            pickable: true,
            parameters,
            positionFormat,
            data: this.props.logData,
            trajectoryData: data,
            domainSelection: this.state.logDomainSelection,
            logWidth: this.props.logRadius || "value",
            activeLog: {
                logRun: this.props.logrunName,
                curveName: this.props.logName,
            },
            colorScale: {
                name: this.props.logColor,
                isLogarithmic: this.props.isLog,
                colorTables: this.context.userData
                    .colorTables,
                mappingFunction: this.props.colorMappingFunction,
            },
            getWellMds: getWellMds,
            getTrajectoryPath: (d) => { var _a; return getTrajectory(d, (_a = this.props.lineStyle) === null || _a === void 0 ? void 0 : _a.color); },
            onLogDataComputed: (value) => {
                this.setState({ logData: value });
                this.setLegend(value);
            },
            updateTriggers: {
                getTrajectoryPath: [this.props.lineStyle],
            },
        })));
        const namesLayer = this.createWellLabelLayer(data.features);
        const layers = [
            outlineLayer,
            colorsLayer,
            highlightLayer,
            highlightMultiWellsLayer,
            namesLayer,
            logLayer,
        ];
        if (fastDrawing) {
            layers.push(fastLayer);
        }
        return layers;
    }
    getPickingInfo({ info }) {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
        const noLog = {
            properties: [],
            logName: "",
        };
        if (!info.object || ((_a = info.sourceLayer) === null || _a === void 0 ? void 0 : _a.constructor) === WellLabelLayer) {
            return Object.assign(Object.assign({}, info), noLog);
        }
        const features = (_c = (_b = this.getWellDataState()) === null || _b === void 0 ? void 0 : _b.features) !== null && _c !== void 0 ? _c : [];
        const coordinate = info.coordinate || [0, 0, 0];
        const zScale = this.props.modelMatrix ? this.props.modelMatrix[10] : 1;
        if (typeof coordinate[2] !== "undefined") {
            coordinate[2] /= Math.max(0.001, zScale);
        }
        let wellName = undefined;
        let md_property = null;
        let tvd_property = null;
        let log_property = null;
        // ! This needs to be updated if we ever change the sub-layer id!
        if (((_d = info.sourceLayer) === null || _d === void 0 ? void 0 : _d.id) === `${this.props.id}-${SubLayerId.LOG_CURVE}`) {
            // The user is hovering a well log entry
            const logPick = info;
            wellName = (_f = (_e = logPick.object) === null || _e === void 0 ? void 0 : _e.header) === null || _f === void 0 ? void 0 : _f.well;
            md_property = getLogProperty(coordinate, features, logPick.object, this.props.logrunName, "MD");
            tvd_property = getLogProperty(coordinate, features, logPick.object, this.props.logrunName, "TVD");
            log_property = getLogProperty(coordinate, features, info.object, this.props.logrunName, this.props.logName);
        }
        else {
            // User is hovering a wellbore path
            const wellpickInfo = info;
            wellName = (_h = (_g = wellpickInfo.object) === null || _g === void 0 ? void 0 : _g.properties) === null || _h === void 0 ? void 0 : _h.name;
            md_property = getMdProperty(coordinate, wellpickInfo.object, (_j = this.props.lineStyle) === null || _j === void 0 ? void 0 : _j.color, info.featureType);
            tvd_property = getTvdProperty(coordinate, info.object, (_k = this.props.lineStyle) === null || _k === void 0 ? void 0 : _k.color, info.featureType);
        }
        // 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, wellName: wellName });
    }
}
WellsLayer.layerName = "WellsLayer";
WellsLayer.defaultProps = Object.assign(Object.assign({}, defaultProps), { onDataLoad: (data, context) => onDataLoad(data, context) });
//================= Local help functions. ==================
function onDataLoad(data, context) {
    const bbox = GetBoundingBox(data);
    if (typeof context.layer.props.reportBoundingBox !== "undefined") {
        context.layer.props.reportBoundingBox({ layerBoundingBox: bbox });
    }
}
// Create a dash accessor function that scales
function getDashFactor(accessor, widthAccessor, offset = 0) {
    return (object, ctx) => {
        var _a;
        const dashValue = getFromAccessor(accessor, object, ctx);
        const width = (_a = getFromAccessor(widthAccessor, object, ctx)) !== null && _a !== void 0 ? _a : DEFAULT_LINE_WIDTH;
        const factor = width / (width + offset);
        let dashArray = [0, 0];
        if (dashValue && Array.isArray(dashValue)) {
            dashArray = dashValue;
        }
        else if (dashValue) {
            dashArray = DEFAULT_DASH;
        }
        return scaleArray(dashArray, factor);
    };
}
function dataIsReady(layerData) {
    // Deck.gl always shows prop.data as `[]` while external data is being loaded
    return !!layerData && !isEmpty(layerData);
}
function getMdProperty(coord, feature, accessor, featureType) {
    var _a, _b;
    if (featureType === "points") {
        return null;
    }
    const md = getMd(coord, feature, accessor);
    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 getTvdProperty(coord, feature, accessor, featureType) {
    var _a, _b;
    if (featureType === "points") {
        return null;
    }
    const tvd = getTvd(coord, feature, accessor);
    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;
}
//# sourceMappingURL=wellsLayer.js.map