/*
 * 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.sea.optional;

import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
import java.util.zip.GZIPOutputStream;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.mkgmap.reader.osm.SeaGenerator;
import uk.me.parabola.mkgmap.reader.osm.Way;
import uk.me.parabola.splitter.BinaryMapWriter;
import uk.me.parabola.splitter.Node;
import uk.me.parabola.splitter.OSMWriter;
import uk.me.parabola.splitter.OSMXMLWriter;

class PrecompSeaSaver implements Runnable {
	private final AtomicBoolean finished = new AtomicBoolean(false);
	private final CountDownLatch finishWait;

	private final Map<String, String> index;

	private final Map<Integer, String> idMapping;
	private int nextId = 0;
	private final boolean usePbf;

	private final File outputDir;
	
	private final BlockingQueue<Entry<String, List<Way>>> saveQueue = new LinkedBlockingQueue<Entry<String, List<Way>>>();

	public PrecompSeaSaver(File outputDir, boolean usePbf) {
		this.outputDir = outputDir;
		finishWait = new CountDownLatch(1);
		this.usePbf = usePbf;
		idMapping = new HashMap<Integer, String>();
		index = new TreeMap<String, String>();
		this.outputDir.mkdirs();
	}
	
	public BlockingQueue<Entry<String, List<Way>>> getQueue() {
		return saveQueue;
	}

	private OSMWriter createWriter(int id, String key) {
		String[] parts = key.split(Pattern.quote("_"));
		int lat = Integer.valueOf(parts[0]);
		int lon = Integer.valueOf(parts[1]);
		uk.me.parabola.splitter.Area bounds = new uk.me.parabola.splitter.Area(
				lat, lon, lat + SeaGenerator.PRECOMP_RASTER, lon + SeaGenerator.PRECOMP_RASTER);
		OSMWriter writer = (usePbf ? new BinaryMapWriter(bounds, outputDir, nextId, 0)
				: new OSMXMLWriter(bounds, outputDir, nextId, 0));
		idMapping.put(id, key);
		writer.initForWrite();
		return writer;
	}
	
	public void waitForFinish() throws InterruptedException {
		this.finished.set(true);
		this.finishWait.await();
	}

	public void run() {
		while (saveQueue.isEmpty() == false || finished.get() == false) {
			Entry<String, List<Way>> tileData = null;
			try {
				tileData = saveQueue.poll(1, TimeUnit.MINUTES);
			} catch (InterruptedException exp) {
				exp.printStackTrace();
			}
			if (tileData != null) {
				int id = ++nextId;

				if (tileData.getValue().size() == 1) {
					// do not write the tile because it consists of one
					// natural type only
					// write it only to the index
					Way singleWay = tileData.getValue().get(0);
					String naturalTag = singleWay.getTag("natural");
					index.put(tileData.getKey(), naturalTag);
				} else {
					String ext = (usePbf ? "pbf" : "gz");
					index.put(tileData.getKey(), "sea_" + tileData.getKey()
							+ ".osm." + ext);

					OSMWriter writer = createWriter(id, tileData.getKey());

					Long2ObjectOpenHashMap<Long> coordIds = new Long2ObjectOpenHashMap<>();
					Map<Long,uk.me.parabola.splitter.Way> pbfWays = new TreeMap<Long, uk.me.parabola.splitter.Way>();
					long maxNodeId = 1;
					for (Way w : tileData.getValue()) {
						uk.me.parabola.splitter.Way pbfWay = new uk.me.parabola.splitter.Way();
						pbfWay.set(w.getId());
						for (Entry<String, String> tag : w
								.getTagEntryIterator()) {
							pbfWay.addTag(tag.getKey(), tag.getValue());
						}
						for (Coord c : w.getPoints()) {
							Node n = new Node();
							long key = Utils.coord2Long(c);
							Long nodeId = coordIds.get(key);
							if (nodeId == null) {
								nodeId = new Long(maxNodeId++);
								coordIds.put(key, nodeId);
								n.set(nodeId,
										c.getLatDegrees(),
										c.getLonDegrees());
								try {
									writer.write(n);
								} catch (IOException exp) {
									exp.printStackTrace();
								}
							}
							pbfWay.addRef(nodeId);
						}
						pbfWays.put(pbfWay.getId(),pbfWay);
					}
					for (uk.me.parabola.splitter.Way pbfWay : pbfWays.values()) {
						try {
							writer.write(pbfWay);
						} catch (IOException exp) {
							exp.printStackTrace();
						}
					}
					writer.finishWrite();

					File tileFile = new File(outputDir, String.format(
							"%08d.osm." + ext, id));
					File precompFile = new File(outputDir, "sea_"
							+ tileData.getKey() + ".osm." + ext);
					if (precompFile.exists()) {
						precompFile.delete();
					}
					
					tileFile.renameTo(precompFile);
				}
			}
		}
		
		writeIndex();

		finishWait.countDown();
	}
	
	private void writeIndex() {
		try {
			PrintWriter indexWriter = new PrintWriter(
					new GZIPOutputStream(new FileOutputStream(
					new File(outputDir, "index.txt.gz"))));
			
			for (Entry<String, String> ind : index.entrySet()) {
				indexWriter.format("%s;%s\n", ind.getKey(), ind.getValue());
			}

			indexWriter.close();
		} catch (IOException exp1) {
			exp1.printStackTrace();
		}
	}
}