/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import LineDraw from '../helper/LineDraw';
import EffectLine from '../helper/EffectLine';
import Line from '../helper/Line';
import Polyline from '../helper/Polyline';
import EffectPolyline from '../helper/EffectPolyline';
import LargeLineDraw from '../helper/LargeLineDraw';
import linesLayout from './linesLayout';
import {createClipPath} from '../helper/createClipPathFromCoordSys';
import ChartView from '../../view/Chart';
import LinesSeriesModel from './LinesSeries';
import GlobalModel from '../../model/Global';
import ExtensionAPI from '../../core/ExtensionAPI';
import CanvasPainter from 'zrender/src/canvas/Painter';
import { StageHandlerProgressParams, StageHandlerProgressExecutor } from '../../util/types';
import SeriesData from '../../data/SeriesData';
import type Polar from '../../coord/polar/Polar';
import type Cartesian2D from '../../coord/cartesian/Cartesian2D';

class LinesView extends ChartView {

    static readonly type = 'lines';
    readonly type = LinesView.type;

    private _lastZlevel: number;
    private _finished: boolean;

    private _lineDraw: LineDraw | LargeLineDraw;

    private _hasEffet: boolean;
    private _isPolyline: boolean;
    private _isLargeDraw: boolean;

    render(seriesModel: LinesSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
        const data = seriesModel.getData();

        const lineDraw = this._updateLineDraw(data, seriesModel);

        const zlevel = seriesModel.get('zlevel');
        const trailLength = seriesModel.get(['effect', 'trailLength']);

        const zr = api.getZr();
        // Avoid the drag cause ghost shadow
        // FIXME Better way ?
        // SVG doesn't support
        const isSvg = zr.painter.getType() === 'svg';
        if (!isSvg) {
            (zr.painter as CanvasPainter).getLayer(zlevel).clear(true);
        }
        // Config layer with motion blur
        if (this._lastZlevel != null && !isSvg) {
            zr.configLayer(this._lastZlevel, {
                motionBlur: false
            });
        }
        if (this._showEffect(seriesModel) && trailLength) {
            if (__DEV__) {
                let notInIndividual = false;
                ecModel.eachSeries(function (otherSeriesModel) {
                    if (otherSeriesModel !== seriesModel && otherSeriesModel.get('zlevel') === zlevel) {
                        notInIndividual = true;
                    }
                });
                notInIndividual && console.warn('Lines with trail effect should have an individual zlevel');
            }

            if (!isSvg) {
                zr.configLayer(zlevel, {
                    motionBlur: true,
                    lastFrameAlpha: Math.max(Math.min(trailLength / 10 + 0.9, 1), 0)
                });
            }
        }

        lineDraw.updateData(data as SeriesData);

        const clipPath = seriesModel.get('clip', true) && createClipPath(
            (seriesModel.coordinateSystem as Polar | Cartesian2D), false, seriesModel
        );
        if (clipPath) {
            this.group.setClipPath(clipPath);
        }
        else {
            this.group.removeClipPath();
        }

        this._lastZlevel = zlevel;

        this._finished = true;
    }

    incrementalPrepareRender(seriesModel: LinesSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
        const data = seriesModel.getData();

        const lineDraw = this._updateLineDraw(data, seriesModel);

        lineDraw.incrementalPrepareUpdate(data);

        this._clearLayer(api);

        this._finished = false;
    }

    incrementalRender(
        taskParams: StageHandlerProgressParams,
        seriesModel: LinesSeriesModel,
        ecModel: GlobalModel
    ) {
        this._lineDraw.incrementalUpdate(taskParams, seriesModel.getData());

        this._finished = taskParams.end === seriesModel.getData().count();
    }

    updateTransform(seriesModel: LinesSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
        const data = seriesModel.getData();
        const pipelineContext = seriesModel.pipelineContext;

        if (!this._finished || pipelineContext.large || pipelineContext.progressiveRender) {
            // TODO Don't have to do update in large mode. Only do it when there are millions of data.
            return {
                update: true
            } as const;
        }
        else {
            // TODO Use same logic with ScatterView.
            // Manually update layout
            const res = linesLayout.reset(seriesModel, ecModel, api) as StageHandlerProgressExecutor;
            if (res.progress) {
                res.progress({
                    start: 0,
                    end: data.count(),
                    count: data.count()
                }, data);
            }
            // Not in large mode
            (this._lineDraw as LineDraw).updateLayout();
            this._clearLayer(api);
        }
    }

    _updateLineDraw(data: SeriesData, seriesModel: LinesSeriesModel) {
        let lineDraw = this._lineDraw;
        const hasEffect = this._showEffect(seriesModel);
        const isPolyline = !!seriesModel.get('polyline');
        const pipelineContext = seriesModel.pipelineContext;
        const isLargeDraw = pipelineContext.large;

        if (__DEV__) {
            if (hasEffect && isLargeDraw) {
                console.warn('Large lines not support effect');
            }
        }
        if (!lineDraw
            || hasEffect !== this._hasEffet
            || isPolyline !== this._isPolyline
            || isLargeDraw !== this._isLargeDraw
        ) {
            if (lineDraw) {
                lineDraw.remove();
            }
            lineDraw = this._lineDraw = isLargeDraw
                ? new LargeLineDraw()
                : new LineDraw(
                    isPolyline
                        ? (hasEffect ? EffectPolyline : Polyline)
                        : (hasEffect ? EffectLine : Line)
                );
            this._hasEffet = hasEffect;
            this._isPolyline = isPolyline;
            this._isLargeDraw = isLargeDraw;
        }

        this.group.add(lineDraw.group);

        return lineDraw;
    }

    private _showEffect(seriesModel: LinesSeriesModel) {
        return !!seriesModel.get(['effect', 'show']);
    }

    _clearLayer(api: ExtensionAPI) {
        // Not use motion when dragging or zooming
        const zr = api.getZr();
        const isSvg = zr.painter.getType() === 'svg';
        if (!isSvg && this._lastZlevel != null) {
            (zr.painter as CanvasPainter).getLayer(this._lastZlevel).clear(true);
        }
    }

    remove(ecModel: GlobalModel, api: ExtensionAPI) {
        this._lineDraw && this._lineDraw.remove();
        this._lineDraw = null;
        // Clear motion when lineDraw is removed
        this._clearLayer(api);
    }

    dispose(ecModel: GlobalModel, api: ExtensionAPI) {
        this.remove(ecModel, api);
    }

}

export default LinesView;
