package uk.me.parabola.mkgmap.filters;

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

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.log.Logger;
import uk.me.parabola.mkgmap.general.MapLine;
import uk.me.parabola.util.MultiHashMap;



public class LineMergeFilter{
	private static final Logger log = Logger.getLogger(LineMergeFilter.class);

	private List<MapLine> linesMerged;
	private final MultiHashMap<Coord, MapLine> startPoints = new MultiHashMap<Coord, MapLine>();
	private final MultiHashMap<Coord, MapLine> endPoints = new MultiHashMap<Coord, MapLine>();

	private void addLine(MapLine line) {
		linesMerged.add(line);
		List<Coord> points = line.getPoints();
		startPoints.add(points.get(0), line);
		endPoints.add(points.get(points.size()-1), line);
	}
	
	private void mergeLines(MapLine line1, MapLine line2) {
		// Removes the first line,
		// Merges the points in the second one
		List<Coord> points1 = line1.getPoints();
		List<Coord> points2 = line2.getPoints();
		startPoints.removeMapping(points1.get(0), line1);
		endPoints.removeMapping(points1.get(points1.size() - 1), line1);
		startPoints.removeMapping(points2.get(0), line2);
		startPoints.add(points1.get(0), line2);
		line2.insertPointsAtStart(points1);
		linesMerged.remove(line1);
	}

	private void addPointsAtStart(MapLine line, List<Coord> additionalPoints) {
		log.info("merged lines before " + line.getName());
		List<Coord> points = line.getPoints();
		startPoints.removeMapping(points.get(0), line);
		line.insertPointsAtStart(additionalPoints);
		startPoints.add(points.get(0), line);
	}
	
	private void addPointsAtEnd(MapLine line, List<Coord> additionalPoints) {
		log.info("merged lines after " + line.getName());
		List<Coord> points = line.getPoints();
		endPoints.removeMapping(points.get(points.size() - 1), line);
		line.insertPointsAtEnd(additionalPoints);
		endPoints.add(points.get(points.size()-1), line);
	}

	//TODO: This routine has a side effect: it modifies some of the MapLine instances
	// instead of creating copies. It seems that this has no bad effect, but it is not clean
	public List<MapLine> merge(List<MapLine> lines, int res) {
		linesMerged = new ArrayList<MapLine>(lines.size());	//better use LinkedList??
		for (MapLine line : lines) {
			if (line.getMinResolution() > res || line.getMaxResolution() < res)
				continue;
			
			if (line.isRoad()){
				linesMerged.add(line);
				continue;
			}
			
			boolean isMerged = false;
			List<Coord> points = line.getPoints();
			Coord start = points.get(0); 
			Coord end = points.get(points.size()-1); 

			// Search for start point in hashlist 
			// (can the end of current line connected to an existing line?)
			for (MapLine line2 : startPoints.get(end)) {
				if (line.isSimilar(line2)) {
					addPointsAtStart(line2, points);
					// Search for endpoint in hashlist
					// (if the other end (=start of line =start of line2) could be connected to an existing line,
					//  both lines has to be merged and one of them dropped)
					for (MapLine line1 : endPoints.get(start)) {
						if (line2.isSimilar(line1)
						 && !line2.equals(line1)) // don't make a closed loop a double loop
						{
							mergeLines(line1, line2);
							break;
						}
					}						
					isMerged = true;
					break;
				}
			}
			if (isMerged)
				continue;

			// Search for endpoint in hashlist
			// (can the start of current line connected to an existing line?)
			for (MapLine line2 : endPoints.get(start)) {
				if (line.isSimilar(line2)) {
					addPointsAtEnd(line2, points);
					isMerged = true;
					break;
				}
			}
			if (isMerged)
				continue;

			// No matching, create a copy of line
			MapLine l = line.copy();
			List<Coord> p = new ArrayList<Coord>(line.getPoints());	//use better LinkedList for performance?
			l.setPoints(p);				
			addLine(l);
		}
		return linesMerged;
	}

}

