/*
 * Copyright (C) 2010, 2011.
 *
 * 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.log.Logger;
import uk.me.parabola.mkgmap.build.MapSplitter;
import uk.me.parabola.mkgmap.general.MapElement;
import uk.me.parabola.mkgmap.general.MapShape;

/**
 * Split polygon so that it does not exceed the limits of a subdivision.  The plan
 * here is simple, if its too big, then cut it in half.  As we always cut the largest
 * dimension, then we will soon enough have cut it down to be small enough.
 *
 * @author WanMil
 */
public class PolygonSubdivSizeSplitterFilter extends PolygonSplitterBase implements MapFilter {
	private static final Logger log = Logger.getLogger(PolygonSubdivSizeSplitterFilter.class);

	private int maxSize;

	/**
	 * Get the scale factor so that we don't over split.
	 *
	 * @param config configuration information, giving parameters of the map level
	 * that is being produced through this filter.
	 */
	public void init(FilterConfig config) {
		int shift = config.getShift();
		if (shift > 15)
			shift = 16;
		// allow a size of 0x8000 to avoid splitting of generated precomp-sea polygons
		maxSize = Math.min((1<<24)-1, Math.max(MAX_SIZE << shift, 0x8000));
	}

	/**
	 * Split up polygons that exceeds the limits of a subdivision.
	 *
	 * @param element A map element, only polygons will be processed.
	 * @param next	This is used to pass the possibly transformed element onward.
	 */
	public void doFilter(MapElement element, MapFilterChain next) {
		assert element instanceof MapShape;
		MapShape shape = (MapShape) element;

		if (isSizeOk(shape)) {
			// This is ok let it through and return.
			next.doFilter(element);
			return;
		}

		List<MapShape> outputs = new ArrayList<MapShape>();

		// Do an initial split
		split(shape, outputs);

		// Now check that all the resulting parts are also small enough.
		// NOTE: the end condition is changed from within the loop.
		for (int i = 0; i < outputs.size(); i++) {
			MapShape s = outputs.get(i);
			if (!isSizeOk(s)) {
				// Not small enough, so remove it and split it again.  The resulting
				// pieces will be placed at the end of the list and will be
				// picked up later on.
				outputs.set(i, null);
				split(s, outputs);
			}
		}

		// Now add all to the chain.
		for (MapShape s : outputs) {
			if (s == null)
				continue;
			next.doFilter(s);
		}
	}

	private boolean isSizeOk(MapShape shape) {
		// do not cut the background shape
		if (shape.getType() == 0x4a)
			return true;
		
		// Estimate the size taken by lines and shapes as a constant plus
		// a factor based on the number of points.
		int numPoints = shape.getPoints().size();
		int numElements = 1 + ((numPoints - 1) / PolygonSplitterFilter.MAX_POINT_IN_ELEMENT);
		int size =  numElements * 11 + numPoints * 4;
		
		if (shape.hasExtendedType()) {
			if (size > MapSplitter.MAX_XT_SHAPES_SIZE) {
				log.debug("XTSize larger than", MapSplitter.MAX_XT_SHAPES_SIZE);
				return false;
			}
		} else if (size > MapSplitter.MAX_RGN_SIZE) {
			log.debug("RGN Size larger than", MapSplitter.MAX_RGN_SIZE);
			return false;
		}
		int maxDim = shape.getBounds().getMaxDimension();
		if (maxDim > maxSize){
			log.debug("Size ", maxDim," larger than ", maxSize);
			return false;
		}
		return true;
	}

}
