/*
 * 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.
 */
package org.apache.sis.geometry.wrapper.j2d;

import java.awt.geom.PathIterator;


/**
 * A path iterator with applies on-the-fly decimation for faster drawing.
 * The decimation algorithm is based on a simple distance calculation on
 * each axis (this is not a Douglas-Peucker algorithm).
 *
 * @author  Johann Sorel (Puzzle-GIS, Geomatys)
 * @author  Martin Desruisseaux (Geomatys)
 */
final class DecimatedPathIterator implements PathIterator {
    /**
     * The source of line segments.
     */
    private final PathIterator source;

    /**
     * The desired resolution on each axis.
     */
    private final double xRes, yRes;

    /**
     * Previous coordinates, or NaN if none.
     */
    private double px, py;

    /**
     * Creates a new iterator.
     */
    DecimatedPathIterator(final PathIterator source, final double xRes, final double yRes) {
        this.source = source;
        this.xRes   = xRes;
        this.yRes   = yRes;
        px = py = Double.NaN;
    }

    /**
     * Moves the iterator to the next segment.
     */
    @Override
    public void next() {
        source.next();
    }

    /**
     * Returns {@code true} if iteration is finished.
     */
    @Override
    public boolean isDone() {
        return source.isDone();
    }

    /**
     * Returns the winding rule for determining the interior of the path.
     */
    @Override
    public int getWindingRule() {
        return source.getWindingRule();
    }

    /**
     * Returns the coordinates and type of the current path segment in the iteration.
     * This method has a fallback for quadratic and cubic curves, but this fallback
     * is not very good. This iterator should be used for flat shapes only.
     *
     * @param  coords an array where to store the data returned from this method.
     * @return the path-segment type of the current path segment.
     */
    @Override
    public int currentSegment(final double[] coords) {
        do {
            final int type = source.currentSegment(coords);
            switch (type) {
                default: {
                    px = py = Double.NaN;
                    return type;
                }
                case SEG_MOVETO: {
                    px = coords[0];
                    py = coords[1];
                    return SEG_MOVETO;
                }
                case SEG_LINETO: {
                    if (include(coords[0], coords[1])) {
                        return SEG_LINETO;
                    }
                    break;
                }
            }
            source.next();
        } while (!source.isDone());
        coords[0] = px;
        coords[1] = py;
        return SEG_LINETO;
    }

    /**
     * Returns the coordinates and type of the current path segment in the iteration.
     * This is a copy of {@link #currentSegment(double[])} with only the type changed.
     *
     * @param  coords an array where to store the data returned from this method.
     * @return the path-segment type of the current path segment.
     */
    @Override
    public int currentSegment(final float[] coords) {
        do {
            final int type = source.currentSegment(coords);
            switch (type) {
                default: {
                    px = py = Double.NaN;
                    return type;
                }
                case SEG_MOVETO: {
                    px = coords[0];
                    py = coords[1];
                    return SEG_MOVETO;
                }
                case SEG_LINETO: {
                    if (include(coords[0], coords[1])) {
                        return SEG_LINETO;
                    }
                    break;
                }
            }
            source.next();
        } while (!source.isDone());
        coords[0] = (float) px;
        coords[1] = (float) py;
        return SEG_LINETO;
    }

    /**
     * Returns whether the given point should be returned in a {@link #SEG_LINETO} segment.
     */
    private boolean include(final double x, final double y) {
        if (Math.abs(px - x) < xRes && Math.abs(py - y) < yRes) {
            return false;
        } else {
            px = x;
            py = y;
            return true;
        }
    }
}
