/*
 * Copyright (C) 2006, 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.reader.osm.boundary;

import java.awt.geom.Area;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.mkgmap.reader.osm.Way;
import uk.me.parabola.util.GpxCreator;
import uk.me.parabola.util.Java2DConverter;

public class BoundaryCoverageUtil {
	private final BoundaryQuadTree bqt;

	public BoundaryCoverageUtil(String boundaryDirName, String boundaryFileName) {
		bqt = BoundaryUtil.loadQuadTree(boundaryDirName, boundaryFileName);
	}

	public Area getCoveredArea(int admLevel) {
		return bqt.getCoveredArea(admLevel);
	}

	private static void saveArea(String attribute, Integer level, Area covered) {
		String gpxBasename = "gpx/summary/" + attribute + "/admin_level=" + level;

		List<List<Coord>> coveredPolys = Java2DConverter.areaToShapes(covered);
		Collections.reverse(coveredPolys);
		int i = 0;
		for (List<Coord> coveredPart : coveredPolys) {
			String attr = Way.clockwise(coveredPart) ? "o" : "i";
			GpxCreator.createGpx(gpxBasename + "/" + i + "_" + attr, coveredPart);
			i++;
		}
	}

	public static void main(String[] args) throws InterruptedException {
		int processors = Runtime.getRuntime().availableProcessors();
		ExecutorService excSvc = Executors.newFixedThreadPool(processors);
		ExecutorCompletionService<Area> executor = new ExecutorCompletionService<>(excSvc);
		String workDirName = args[0];
		System.out.println(workDirName);
		File boundaryDir = new File(workDirName);
		final Set<String> boundsFileNames = new HashSet<>();
		if (boundaryDir.isFile() && boundaryDir.getName().endsWith(".bnd")) {
			workDirName = boundaryDir.getParent();
			if (workDirName == null)
				workDirName = ".";
			boundsFileNames.add(boundaryDir.getName());
		} else {
			boundsFileNames.addAll(BoundaryUtil
					.getBoundaryDirContent(workDirName));
		}

		int minLat = Integer.MAX_VALUE;
		int maxLat = Integer.MIN_VALUE;
		int minLon = Integer.MAX_VALUE;
		int maxLon = Integer.MIN_VALUE;
		for (String fileName : boundsFileNames) {
			String[] parts = fileName.substring("bounds_".length(),
					fileName.length() - 4).split("_");
			int lat = Integer.parseInt(parts[0]);
			int lon = Integer.parseInt(parts[1]);
			if (lat < minLat)
				minLat = lat;
			if (lat > maxLat)
				maxLat = lat;
			if (lon < minLon)
				minLon = lon;
			if (lon > maxLon)
				maxLon = lon;
		}
		System.out.format("Covered area: (%d,%d)-(%d,%d)%n", minLat, minLon, maxLat, maxLon);
		int maxSteps = 2;

		final String boundaryDirName = workDirName;
		for (int adminlevel = 2; adminlevel < 12; adminlevel++) {
			final Set<String> boundaryFileNames = Collections.synchronizedSet(new HashSet<>(boundsFileNames));
			final int adminLevel = adminlevel;
			final Queue<Future<Area>> areas = new LinkedBlockingQueue<>();
			for (int lat = minLat; lat <= maxLat; lat += maxSteps * BoundaryUtil.RASTER) {
				for (int lon = minLon; lon <= maxLon; lon += maxSteps * BoundaryUtil.RASTER) {
					for (int latStep = 0; latStep < maxSteps
							&& lat + latStep * BoundaryUtil.RASTER <= maxLat; latStep++) {
						for (int lonStep = 0; lonStep < maxSteps
								&& lon + lonStep * BoundaryUtil.RASTER <= maxLon; lonStep++) {
							final int fLat = lat + latStep * BoundaryUtil.RASTER;
							final int fLon = lon + lonStep * BoundaryUtil.RASTER;

							areas.add(executor.submit(() -> {
								String filename = "bounds_" + fLat + "_" + fLon + ".bnd";
								if (!boundaryFileNames.contains(filename)) {
									return new Area();
								}
								BoundaryCoverageUtil converter = new BoundaryCoverageUtil(boundaryDirName, filename);
								boundaryFileNames.remove(filename);
								System.out.format("%5d bounds files remaining%n", boundaryFileNames.size());
								return converter.getCoveredArea(adminLevel);
							}));
						}
					}
				}
			}

			final AtomicInteger mergeSteps = new AtomicInteger();
			while (areas.size() > 1) {
				final List<Future<Area>> toMerge = new ArrayList<>();
				for (int i = 0; i < maxSteps * 2 && !areas.isEmpty(); i++) {
					toMerge.add(areas.poll());
				}
				mergeSteps.incrementAndGet();
				areas.add(executor.submit(() -> {
					Area a = new Area();
					ListIterator<Future<Area>> mergeAreas = toMerge.listIterator();
					while (mergeAreas.hasNext()) {
						try {
							a.add(mergeAreas.next().get());
						} catch (InterruptedException exp1) {
							System.err.println(exp1);
						} catch (ExecutionException exp2) {
							System.err.println(exp2);
						}
						mergeAreas.remove();
					}
					System.out.format("%5d merges remaining%n",mergeSteps.decrementAndGet());
					return a;
				}));
			}
			try {
				Area finalArea = areas.poll().get();
				System.out.println("Joining finished. Saving results.");
				saveArea("covered", adminlevel, finalArea);
			} catch (Exception exp) {
				System.err.println(exp);
			}
		}
		excSvc.shutdown();
	}

}
