/*
 * Copyright (C) 2008 Steve Ratcliffe
 * 
 * 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.
 */
/* Create date: 17-Feb-2009 */
package func.files;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.mps.MapBlock;
import uk.me.parabola.imgfmt.mps.MpsFileReader;
import uk.me.parabola.imgfmt.mps.ProductBlock;
import uk.me.parabola.imgfmt.sys.ImgFS;
import uk.me.parabola.mkgmap.main.Main;

import func.Base;
import func.lib.Args;
import func.lib.Outputs;
import func.lib.TestUtils;
import org.junit.Test;

import static org.junit.Assert.*;

public class GmapsuppTest extends Base {
	private static final String GMAPSUPP_IMG = "gmapsupp.img";

	@Test
	public void testBasic() throws IOException {
		File f = new File(GMAPSUPP_IMG);
		assertFalse("does not pre-exist", f.exists());

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		assertTrue("gmapsupp.img is created", f.exists());

		FileSystem fs = openFs(GMAPSUPP_IMG);
		DirectoryEntry entry = fs.lookup("63240001.TRE");
		assertNotNull("first file TRE", entry);
		assertEquals("first file TRE size", getFileSize(Args.TEST_RESOURCE_IMG + "63240001.img", "63240001.TRE"), entry.getSize());

		entry = fs.lookup("63240002.TRE");
		assertNotNull("second file TRE", entry);
		assertEquals("second file TRE size", getFileSize(Args.TEST_RESOURCE_IMG + "63240002.img", "63240002.TRE"), entry.getSize());
	}

	/**
	 * Check the values inside the MPS file, when the family id etc is
	 * common to all files.
	 */
	@Test
	public void testMpsFile() throws IOException {
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=150",
				"--product-id=24",
				"--series-name=tst series",
				"--family-name=tst family",
				"--area-name=tst area",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		MpsFileReader reader = getMpsFile();
		List<MapBlock> list = reader.getMaps();
		reader.close();
		assertEquals("number of map blocks", 2, list.size());

		// All maps will have the same parameters apart from map name here
		int count = 0;
		for (MapBlock map : list) {
			assertEquals("map number", 63240001 + count++, map.getMapNumber());
			assertEquals("family id", 150, map.getFamilyId());
			assertEquals("product id", 24, map.getProductId());
			assertEquals("series name", "tst series", map.getSeriesName());
			assertEquals("area name", "tst area", map.getAreaName());
			assertEquals("map description", "uk test " + count, map.getMapDescription());
		}
	}

	/**
	 * Test combining gmapsupp files.  The family id etc should be taken from
	 * the MPS file in the gmapsupp.
	 */
	@Test
	public void testCombiningSupps() throws IOException {
		TestUtils.registerFile("g1.img", "g2.img");
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=150",
				"--product-id=24",
				"--series-name=tst series",
				"--family-name=tst family",
				"--area-name=tst area",
				Args.TEST_RESOURCE_IMG + "63240001.img");

		File f = new File("gmapsupp.img");
		f.renameTo(new File("g1.img"));

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=152",
				"--product-id=26",
				"--series-name=tst series 2",
				"--family-name=tst family 2",
				"--area-name=tst area 2",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		f.renameTo(new File("g2.img"));

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG, "--gmapsupp", "g1.img", "g2.img");


		MpsFileReader reader = getMpsFile();
		List<MapBlock> list = reader.getMaps();
		assertEquals("number of map blocks", 2, list.size());

		for (MapBlock map : list) {
			if (map.getMapNumber() == 63240001) {
				assertEquals("family id", 150, map.getFamilyId());
				assertEquals("product id", 24, map.getProductId());
				assertEquals("series name", "tst series", map.getSeriesName());
				assertEquals("area name", "tst area", map.getAreaName());
				assertEquals("hex name", 63240001, map.getHexNumber());
				assertEquals("map description", "uk test 1", map.getMapDescription());
			} else if (map.getMapNumber() == 63240002) {
				assertEquals("family id", 152, map.getFamilyId());
				assertEquals("product id", 26, map.getProductId());
				assertEquals("series name", "tst series 2", map.getSeriesName());
				assertEquals("area name", "tst area 2", map.getAreaName());
				assertEquals("hex name", 63240002, map.getHexNumber());
				assertEquals("map description", "uk test 2", map.getMapDescription());
			} else {
				assertTrue("Unexpected map found", false);
			}
		}
	}

	/**
	 * Test the case where we are combining img files with different family
	 * and product ids.
	 */
	@Test
	public void testDifferentFamilies() throws IOException {
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=101",
				"--product-id=1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				"--family-id=102",
				"--product-id=2",
				"--series-name=tst series2",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		MpsFileReader reader = getMpsFile();
		List<MapBlock> list = reader.getMaps();
		reader.close();
		assertEquals("number of map blocks", 2, list.size());

		// Directly check the family id's
		assertEquals("family in map1", 101, list.get(0).getFamilyId());
		assertEquals("family in map2", 102, list.get(1).getFamilyId());

		// Check more things
		int count = 0;
		for (MapBlock map : list) {
			count++;
			assertEquals("family in map" + count, 100 + count, map.getFamilyId());
			assertEquals("product in map" + count, count, map.getProductId());
			assertEquals("series name in map" + count, "tst series" + count, map.getSeriesName());
		}
	}

	/**
	 * The mps file has a block for each family/product in the map set.
	 */
	@Test
	public void testProductBlocks() throws IOException {
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=101",
				"--product-id=1",
				"--family-name=tst family1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				"--family-id=102",
				"--product-id=2",
				"--family-name=tst family2",
				"--series-name=tst series2",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		MpsFileReader reader = getMpsFile();

		List<ProductBlock> products = reader.getProducts();
		products.sort((o1, o2) -> {
			if (o1.getFamilyId() == o2.getFamilyId())
				return 0;
			else if (o1.getFamilyId() > o2.getFamilyId())
				return 1;
			else return -1;
		});

		ProductBlock block = products.get(0);
		assertEquals("product block first family", 101, block.getFamilyId());
		assertEquals("product block first product id", 1, block.getProductId());
		assertEquals("product block first family name", "tst family1", block.getDescription());
		
		block = products.get(1);
		assertEquals("product block second family", 102, block.getFamilyId());
		assertEquals("product block first product id", 2, block.getProductId());
		assertEquals("product block first family name", "tst family2", block.getDescription());
	}

	/**
	 * Make sure that if we have multiple maps in the same family, which after
	 * all is the common case, that we only get one product block.
	 */
	@Test
	public void testProductWithSeveralMaps() throws IOException {
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--family-id=101",
				"--product-id=1",
				"--family-name=tst family1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		MpsFileReader reader = getMpsFile();
		assertEquals("number of map blocks", 2, reader.getMaps().size());
		assertEquals("number of product blocks", 1, reader.getProducts().size());
	}

	@Test
	public void testWithIndex() throws IOException {
		new File("osmmap_mdr.img").delete();
		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--index",
				"--latin1",
				"--family-id=101",
				"--product-id=1",
				"--family-name=tst family1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		assertFalse(new File("osmmap_mdr.img").exists());

		// All we are doing here is checking that the file was created and that it is
		// not completely empty.
		FileSystem fs = openFs(GMAPSUPP_IMG);
		ImgChannel r = fs.open("00000101.MDR", "r");
		r.position(2);
		ByteBuffer buf = ByteBuffer.allocate(1024);
		
		int read = r.read(buf);
		assertEquals(1024, read);

		buf.flip();
		byte[] b = new byte[3];
		buf.get(b, 0, 3);
		assertEquals('G', b[0]);
	}

	@Test
	public void testWithTwoIndexes() throws IOException {
		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--index",
				"--tdbfile",
				"--latin1",
				"--family-id=101",
				"--product-id=1",
				"--family-name=tst family1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_IMG + "63240002.img");

		assertTrue(new File("osmmap_mdr.img").exists());

		// All we are doing here is checking that the file was created and that it is
		// not completely empty.
		FileSystem fs = openFs(GMAPSUPP_IMG);
		ImgChannel r = fs.open("00000101.MDR", "r");
		r.position(2);
		ByteBuffer buf = ByteBuffer.allocate(1024);

		int read = r.read(buf);
		assertEquals(1024, read);

		buf.flip();
		byte[] b = new byte[3];
		buf.get(b, 0, 3);
		assertEquals('G', b[0]);
	}

	/**
	 * If there are files in two (or more) families then there should be a MDR and SRT for each.
	 */
	@Test
	public void testTwoFamilyIndex() throws IOException {
		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--index",
				"--latin1",
				"--family-id=101",
				"--product-id=1",
				"--family-name=tst family1",
				"--series-name=tst series1",
				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz",
				"--family-id=202",
				"--family-name=tst family2",
				"--series-name=tst series2",
				Args.TEST_RESOURCE_OSM + "uk-test-2.osm.gz");

		assertFalse(new File("osmmap_mdr.img").exists());

		// All we are doing here is checking that the file was created and that it is
		// not completely empty.
		FileSystem fs = openFs(GMAPSUPP_IMG);
		ImgChannel r = fs.open("00000101.MDR", "r");
		r.position(2);
		ByteBuffer buf = ByteBuffer.allocate(1024);
		int read = r.read(buf);
		assertEquals(1024, read);

		fs = openFs(GMAPSUPP_IMG);
		r = fs.open("00000202.MDR", "r");
		r.position(2);
		buf.clear();
		read = r.read(buf);
		assertEquals(1024, read);

		r = fs.open("00000202.SRT", "r");
		buf = ByteBuffer.allocate(512);
		read = r.read(buf);
		assertEquals(512, read);
		r = fs.open("00000101.SRT", "r");
		buf.clear();
		read = r.read(buf);
		assertEquals(512, read);
	}

	/**
	 * If no code page is given for the index, it is taken from the input files.
	 */
	@Test
	public void testImplicitCodePageIndex() throws IOException {
		TestUtils.registerFile("osmmap_mdr.img", "osmmap.img", "osmmap.tbd", "osmmap.mdx");

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG, "--code-page=1256",
				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz");

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG, "--gmapsupp", "--index", "63240001.img");

		assertFalse(new File("osmmap_mdr.img").exists());

		FileSystem fs = openFs(GMAPSUPP_IMG);
		ImgChannel r = fs.open("00006324.MDR", "r");

		ByteBuffer buf = ByteBuffer.allocate(1024);
		buf.order(ByteOrder.LITTLE_ENDIAN);

		r.read(buf);
		assertEquals(1256, buf.getChar(0x15));
		assertEquals(0x020009, buf.getInt(0x17));
	}

	/**
	 * If there are mis-matching code-pages in the input files there should be a warning.
	 */
	@Test
	public void testWarningOnMismatchedCodePages() {
		TestUtils.registerFile("osmmap.img");

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG, "--route", "--code-page=1256",
				Args.TEST_RESOURCE_OSM + "uk-test-1.osm.gz",
				"--latin1",
				Args.TEST_RESOURCE_OSM + "uk-test-2.osm.gz");

		Outputs outputs = TestUtils.runAsProcess(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				"--index",

				"63240001.img",
				"63240002.img"
		);

		outputs.checkError("different code page");
	}

	@Test
	public void testWithTypFile() throws IOException {
		File f = new File(GMAPSUPP_IMG);
		assertFalse("does not pre-exist", f.exists());

		Main.mainNoSystemExit(Args.TEST_STYLE_ARG,
				"--gmapsupp",
				Args.TEST_RESOURCE_IMG + "63240001.img",
				Args.TEST_RESOURCE_TYP + "test.txt");

		assertTrue("gmapsupp.img is created", f.exists());

		FileSystem fs = openFs(GMAPSUPP_IMG);
		DirectoryEntry entry = fs.lookup("63240001.TRE");
		assertNotNull("first file TRE", entry);
		assertEquals("first file TRE size", getFileSize(Args.TEST_RESOURCE_IMG + "63240001.img", "63240001.TRE"), entry.getSize());

		entry = fs.lookup("0000TEST.TYP");
		assertNotNull("contains typ file", entry);
	}

	private MpsFileReader getMpsFile() throws IOException {
		FileSystem fs = openFs(GMAPSUPP_IMG);
		MpsFileReader reader = new MpsFileReader(fs.open("MAKEGMAP.MPS", "r"), 0);
		TestUtils.registerFile(reader);
		return reader;
	}

	private int getFileSize(String imgName, String fileName) throws IOException {
		FileSystem fs = ImgFS.openFs(imgName);
		try {
			return fs.lookup(fileName).getSize();
		} finally {
			Utils.closeFile(fs);
		}
	}
}
