/*
 * Copyright (C) 2012.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 or
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 */ 
package uk.me.parabola.mkgmap.filters;

import java.util.ArrayList;
import java.util.List;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapShape;

/**
 * Filter for lines and shapes. Remove obsolete points on straight lines and spikes.
 * @author GerdP
 *
 */
public class RemoveObsoletePointsFilter implements MapFilter {
	private static final Logger log = Logger.getLogger(RemoveObsoletePointsFilter.class);
	
	private boolean checkPreserved;
	
	@Override
	public void init(FilterConfig config) {
		checkPreserved = config.getLevel() == 0 && config.hasNet();
	}

	/**
	 * @param element A map element that will be a line or a polygon.
	 * @param next This is used to pass the possibly transformed element onward.
	 */
	@Override
	public void doFilter(MapElement element, MapFilterChain next) {
		MapLine line = (MapLine) element;
		List<Coord> points = line.getPoints();
		int numPoints = points.size();
		if (numPoints <= 1) {
			return; 
		}
		int requiredPoints = (line instanceof MapShape ) ? 4:2; 
		List<Coord> newPoints = new ArrayList<>(numPoints);
		while (true){
			boolean removedSpike = false;
			numPoints = points.size();
			

			Coord lastP = points.get(0);
			newPoints.add(lastP);
			for (int i = 1; i < numPoints; i++) {
				Coord newP = points.get(i);
				int last = newPoints.size()-1;
				lastP = newPoints.get(last);
				if (lastP.equals(newP)){
					// only add the new point if it has different
					// coordinates to the last point or is preserved
					if (checkPreserved && line.isRoad()) {
						if (!newP.preserved()) {
							continue;
						} else if (!lastP.preserved()) {
							newPoints.set(last, newP); // replace last
							continue;
						}
					} else {
						continue;
					}
				}
				if (newPoints.size() > 1) {
					switch (Utils.isStraight(newPoints.get(last-1), lastP, newP)){
					case Utils.STRICTLY_STRAIGHT:
						if (checkPreserved && lastP.preserved() && line.isRoad()){
							// keep it
						} else {
							log.debug("found three consecutive points on strictly straight line");
							newPoints.set(last, newP);
							continue;
						}
						break;
					case Utils.STRAIGHT_SPIKE:
						if (line instanceof MapShape){
							log.debug("removing spike");
							newPoints.remove(last);
							removedSpike = true;
							if (newPoints.get(last-1).equals(newP))
								continue;
						}
						break;
					default:
						break;
					}
				}

				newPoints.add(newP);
			}
			if (!removedSpike || newPoints.size() < requiredPoints)
				break;
			points = newPoints;
			newPoints = new ArrayList<>(points.size());
		}
		if (line instanceof MapShape){
			// Check special cases caused by the fact that the first and last point 
			// in a shape are identical. 
			while (newPoints.size() > 3){
				int nPoints = newPoints.size();
				switch(Utils.isStraight(newPoints.get(newPoints.size()-2), newPoints.get(0), newPoints.get(1))){
				case Utils.STRAIGHT_SPIKE:
					log.debug("removing closing spike");
					newPoints.remove(0);
					newPoints.set(newPoints.size()-1, newPoints.get(0));
					if (newPoints.get(newPoints.size()-2).equals(newPoints.get(newPoints.size()-1)))
						newPoints.remove(newPoints.size()-2);
					break;
				case Utils.STRICTLY_STRAIGHT:
					log.debug("removing straight line across closing");
					newPoints.remove(newPoints.size()-1);
					newPoints.set(0, newPoints.get(newPoints.size()-1));
					break;
				}
				if (nPoints == newPoints.size())
					break;
			}
		} else if (!checkPreserved && newPoints.size() > 2) {
			// check for special case that line goes a-b-a or a-b-c-b-a or ... and cut in the middle
			// typical for dual-carriage ways at low resolutions
			int lenDup = 0;
			while (newPoints.get(lenDup).equals(newPoints.get(newPoints.size() - 1 - lenDup))) {
				lenDup++;
				if (lenDup > newPoints.size() - 1 - lenDup)
					break;
			}
			if (lenDup > 1 ) {
				// line starts and ends with the same sequence of points, remove one sequence
				newPoints = newPoints.subList(0, newPoints.size() + 1 - lenDup);
			}
		}
		
		if (newPoints.size() != line.getPoints().size()){
			if (line instanceof MapShape && newPoints.size() <= 3 || newPoints.size() <= 1)
				return;
			MapLine newLine = line.copy();
			newLine.setPoints(newPoints);
			next.doFilter(newLine);
		} else {
			// no need to create new object
			next.doFilter(line);
		}
	}
}
