# Source: components/data_layer/line.js

    import * as d3 from 'd3';

    import BaseDataLayer from './base';
    import {merge} from '../../helpers/layouts';
    import {STATUSES} from '../constants';
    import {applyStyles} from '../../helpers/common';

    /**
     * @memberof module:LocusZoom_DataLayers~line
     */
    const default_layout = {
        style: {
            fill: 'none',
            'stroke-width': '2px',
        },
        interpolate: 'curveLinear',
        x_axis: { field: 'x' },
        y_axis: { field: 'y', axis: 1 },
        hitarea_width: 5,
        tooltip: null,
    };

    /*********************
     * Line Data Layer
     * Implements a standard line plot, representing either a trace or a filled curve. Only one line is drawn per layer used.
     * @alias module:LocusZoom_DataLayers~line
     * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options
    */
    class Line extends BaseDataLayer {
        /**
         * @param {object} [layout.style] CSS properties to control how the line is drawn
         * @param {string} [layout.style.fill='none'] Fill color for the area under the curve
         * @param {string} [layout.style.stroke]
         * @param {string} [layout.style.stroke-width='2px']
         * @param {string} [layout.interpolate='curveLinear'] The name of the d3 interpolator to use. This determines how to smooth the line in between data points.
         * @param {number} [layout.hitarea_width=5] The size of mouse event hitareas to use. If tooltips are not used, hitareas are not very important.
         */
        constructor(layout) {
            layout = merge(layout, default_layout);
            if (layout.tooltip) {
                throw new Error('The line / filled curve layer does not support tooltips');
            }
            super(...arguments);
        }

        /**
         * Implement the main render function
         */
        render() {
            // Several vars needed to be in scope
            const panel = this.parent;
            const x_field = this.layout.x_axis.field;
            const y_field = this.layout.y_axis.field;

            // Join data to the line selection
            const selection = this.svg.group
                .selectAll('path.lz-data_layer-line')
                .data([this.data]);

            // Create path element, apply class
            this.path = selection.enter()
                .append('path')
                .attr('class', 'lz-data_layer-line');

            // Generate the line
            let line;
            const x_scale = panel['x_scale'];
            const y_scale = panel[`y${this.layout.y_axis.axis}_scale`];
            if (this.layout.style.fill && this.layout.style.fill !== 'none') {
                // Filled curve: define the line as a filled boundary
                line = d3.area()
                    .x((d) => +x_scale(d[x_field]))
                    .y0(+y_scale(0))
                    .y1((d) => +y_scale(d[y_field]));
            } else {
                // Basic line
                line = d3.line()
                    .x((d) => +x_scale(d[x_field]))
                    .y((d) => +y_scale(d[y_field]))
                    .curve(d3[this.layout.interpolate]);
            }

            // Apply line and style
            selection.merge(this.path)
                .attr('d', line)
                .call(applyStyles, this.layout.style);

            // Remove old elements as needed
            selection.exit()
                .remove();

        }

        /**
         * Redefine setElementStatus family of methods as line data layers will only ever have a single path element
         * @param {String} status A member of `LocusZoom.DataLayer.Statuses.adjectives`
         * @param {String|Object} element
         * @param {Boolean} toggle
         */
        setElementStatus(status, element, toggle) {
            return this.setAllElementStatus(status, toggle);
        }

        setAllElementStatus(status, toggle) {
            // Sanity check
            if (typeof status == 'undefined' || !STATUSES.adjectives.includes(status)) {
                throw new Error('Invalid status');
            }
            if (typeof this._layer_state.status_flags[status] == 'undefined') {
                return this;
            }
            if (typeof toggle == 'undefined') {
                toggle = true;
            }

            // Update global status flag
            this._global_statuses[status] = toggle;

            // Apply class to path based on global status flags
            let path_class = 'lz-data_layer-line';
            Object.keys(this._global_statuses).forEach((global_status) => {
                if (this._global_statuses[global_status]) {
                    path_class += ` lz-data_layer-line-${global_status}`;
                }
            });
            this.path.attr('class', path_class);

            // Trigger layout changed event hook
            this.parent.emit('layout_changed', true);
            return this;
        }
    }

    /**
     * @memberof module:LocusZoom_DataLayers~orthogonal_line
     */
    const default_orthogonal_layout = {
        style: {
            'stroke': '#D3D3D3',
            'stroke-width': '3px',
            'stroke-dasharray': '10px 10px',
        },
        orientation: 'horizontal',
        x_axis: {
            axis: 1,
            decoupled: true,
        },
        y_axis: {
            axis: 1,
            decoupled: true,
        },
        tooltip_positioning: 'vertical',
        offset: 0,
    };

    /**
     *  Orthogonal Line Data Layer
     *  Draw a horizontal or vertical line given an orientation and an offset in the layout
     *  Does not require a data source or fields.
     * @alias module:LocusZoom_DataLayers~orthogonal_line
     * @see {@link module:LocusZoom_DataLayers~BaseDataLayer} for additional layout options
     */
    class OrthogonalLine extends BaseDataLayer {
        /**
         * @param {string} [layout.style.stroke='#D3D3D3']
         * @param {string} [layout.style.stroke-width='3px']
         * @param {string} [layout.style.stroke-dasharray='10px 10px']
         * @param {'horizontal'|'vertical'} [layout.orientation] The orientation of the horizontal line
         * @param {boolean} [layout.x_axis.decoupled=true] If true, the data in this layer will not influence the x-extent of the panel.
         * @param {boolean} [layout.y_axis.decoupled=true] If true, the data in this layer will not influence the y-extent of the panel.
         * @param {'horizontal'|'vertical'} [layout.tooltip_positioning='vertical'] Where to draw the tooltip relative to the mouse pointer.
         * @param {number} [layout.offset=0] Where the line intercepts the orthogonal axis (eg, the y coordinate for a horizontal line, or x for a vertical line)
         */
        constructor(layout) {
            layout = merge(layout, default_orthogonal_layout);
            // Require that orientation be "horizontal" or "vertical" only
            if (!['horizontal', 'vertical'].includes(layout.orientation)) {
                layout.orientation = 'horizontal';
            }
            super(...arguments);
        }

        getElementId(element) {
            // There is only one line per datalayer, so this is sufficient.
            return this.getBaseId();
        }

        /**
         * Implement the main render function
         */
        render() {
            // Several vars needed to be in scope
            const panel = this.parent;
            const x_scale = 'x_scale';
            const y_scale = `y${this.layout.y_axis.axis}_scale`;
            const x_extent = 'x_extent';
            const y_extent = `y${this.layout.y_axis.axis}_extent`;
            const x_range = 'x_range';

            // Generate data using extents depending on orientation
            if (this.layout.orientation === 'horizontal') {
                this.data = [
                    { x: panel[x_extent][0], y: this.layout.offset },
                    { x: panel[x_extent][1], y: this.layout.offset },
                ];
            } else if (this.layout.orientation === 'vertical') {
                this.data = [
                    { x: this.layout.offset, y: panel[y_extent][0] },
                    { x: this.layout.offset, y: panel[y_extent][1] },
                ];
            } else {
                throw new Error('Unrecognized vertical line type. Must be "vertical" or "horizontal"');
            }

            // Join data to the line selection
            const selection = this.svg.group
                .selectAll('path.lz-data_layer-line')
                .data([this.data]);

            // In some cases, a vertical line may overlay a track that has no inherent y-values (extent)
            //  When that happens, provide a default height based on the current panel dimensions (accounting
            //      for any resizing that happened after the panel was created)
            const default_y = [panel.layout.cliparea.height, 0];

            // Generate the line
            const line = d3.line()
                .x((d, i) => {
                    const x = +panel[x_scale](d['x']);
                    return isNaN(x) ? panel[x_range][i] : x;
                })
                .y((d, i) => {
                    const y = +panel[y_scale](d['y']);
                    return isNaN(y) ? default_y[i] : y;
                });

            // Create path element, apply class
            this.path = selection.enter()
                .append('path')
                .attr('class', 'lz-data_layer-line')
                .merge(selection)
                .attr('d', line)
                .call(applyStyles, this.layout.style)
                // Allow the layer to respond to mouseover events and show a tooltip.
                .call(this.applyBehaviors.bind(this));

            // Remove old elements as needed
            selection.exit()
                .remove();
        }

        _getTooltipPosition(tooltip) {
            try {
                const coords = d3.mouse(this.svg.container.node());
                const x = coords[0];
                const y = coords[1];
                return { x_min: x - 1, x_max: x + 1, y_min: y - 1, y_max: y + 1 };
            } catch (e) {
                // On redraw, there won't be a mouse event, so skip tooltip repositioning.
                return null;
            }
        }

    }

    export { Line as line, OrthogonalLine as orthogonal_line };