/*
 * Copyright (C) 2013.
 *
 * 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.Collections;
import java.util.List;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.trergn.LinePreparer;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.mkgmap.general.MapShape;

/**
 * This filter does more or less the same calculations as LinePreparer.calcDeltas   
 * It rejects lines that have not enough different points or too many, and it optimises
 * shapes so that they require fewer bits in the img file.
 * @author GerdP
 *
 */
public class LinePreparerFilter implements MapFilter {

	private int shift;
	private final Subdivision subdiv;

	public LinePreparerFilter(Subdivision subdiv) {
		this.subdiv = subdiv;
	}

	@Override
	public void init(FilterConfig config) {
		shift = config.getShift();
	}

	/**
	 * @param element A map element that will be a line or a polygon.
	 * @param next This is used to pass the element onward.
	 */
	@Override
	public void doFilter(MapElement element, MapFilterChain next) {
		MapLine line = (MapLine) element;

		int numPoints = line.getPoints().size();
		if (line instanceof MapShape && numPoints > PolygonSplitterFilter.MAX_POINT_IN_ELEMENT)
			throw new MustSplitException();

		boolean first = true;
		int minPointsRequired = (element instanceof MapShape) ? 3:2;
		if (minPointsRequired == 3 && line.getPoints().get(0).equals(line.getPoints().get(numPoints-1)))
			++minPointsRequired;

		int lastLat = 0;
		int lastLong = 0;
		int numPointsEncoded = 1;
		// fields to keep track of the largest delta values  
		int[] maxBits = {0,0};
		int[] maxBits2nd = {0,0};
		int[] maxBitsPos = {0,0};
		
		for (int i = 0; i < numPoints; i++) {
			Coord co = line.getPoints().get(i);

			int lat = subdiv.roundLatToLocalShifted(co.getLatitude());
			int lon = subdiv.roundLonToLocalShifted(co.getLongitude());
			
			if (first) {
				lastLat = lat;
				lastLong = lon;
				first = false;
				continue;
			}

			// compute normalized differences
			//   -2^(shift-1) <= dx, dy < 2^(shift-1)
			// XXX: relies on the fact that java integers are 32 bit signed
			final int offset = 8+shift;
			int dx = (lon - lastLong) << offset >> offset;
			int dy = (lat - lastLat) << offset >> offset;
			lastLong = lon;
			lastLat = lat;
			if (dx == 0 && dy == 0 && !(line.isRoad() && co.isNumberNode())) {
				continue;
			}
			++numPointsEncoded;
			if (numPointsEncoded >= minPointsRequired && !(element instanceof MapShape))
				break;
			// find out largest and 2nd largest delta for both dx and dy
			for (int k = 0; k < 2; k++){
				int nBits = LinePreparer.bitsNeeded((k==0) ? dx:dy);
				if (nBits > maxBits2nd[k]){
					if (nBits > maxBits[k]){
						maxBits2nd[k] = maxBits[k];
						maxBits[k] = nBits;
						maxBitsPos[k] = i;
					} 
					else
						maxBits2nd[k] = nBits;
				}
			}
			
		}		
		if(numPointsEncoded < minPointsRequired)
			return;
		if (minPointsRequired >= 3){
			// check if we can optimise shape by rotating
			// so that the line segment that requires the highest number of bits 
			// is not encoded and thus fewer bits 
			// are required for all points
			// TODO: maybe add additional points to further reduce max. delta values
			// or reverse order if largest delta is negative
			int maxReduction = 0;
			int rotation = 0;
			
			for (int k = 0; k < 2; k++){
				int delta = maxBits[k] - maxBits2nd[k]; 
				// prefer largest delta, then smallest rotation
				if (delta > maxReduction || delta == maxReduction && rotation > maxBitsPos[k]){
					maxReduction = delta;
					rotation = maxBitsPos[k];
				} 
			}
			if (rotation != 0){
				List<Coord> points = line.getPoints();
				if (minPointsRequired == 4)
					points.remove(numPoints-1);
				Collections.rotate(points, -rotation);
				if (minPointsRequired == 4)
					points.add(points.get(0));
			}
		}
		next.doFilter(element);
	}
}
