/* Copyright 2015 Malcolm Herring
 *
 * This is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License.
 *
 * For a copy of the GNU General Public License, see <http://www.gnu.org/licenses/>.
 */

package s57;

import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.CRC32;

import s57.S57obj.*;
import s57.S57val.AttVal;
import s57.S57att.Att;
import s57.S57dat.*;
import s57.S57map.*;

public class S57enc { // S57 ENC file generation

	private static final byte[] header = {
		
		'0', '1', '5', '7', '6', '3', 'L', 'E', '1', ' ', '0', '9', '0', '0', '2', '0', '1', ' ', '!', ' ', '3', '4', '0', '4', // Leader
		'0', '0', '0', '0', '1', '2', '3', '0', '0', '0', '0', '0', '0', '0', '1', '0', '4', '7', '0', '1', '2', '3',
		'D', 'S', 'I', 'D', '1', '5', '9', '0', '1', '7', '0', 'D', 'S', 'S', 'I', '1', '1', '3', '0', '3', '2', '9',
		'D', 'S', 'P', 'M', '1', '3', '0', '0', '4', '4', '2', 'F', 'R', 'I', 'D', '1', '0', '0', '0', '5', '7', '2',
		'F', 'O', 'I', 'D', '0', '7', '0', '0', '6', '7', '2', 'A', 'T', 'T', 'F', '0', '5', '9', '0', '7', '4', '2',
		'N', 'A', 'T', 'F', '0', '6', '8', '0', '8', '0', '1', 'F', 'F', 'P', 'T', '0', '8', '6', '0', '8', '6', '9',
		'F', 'S', 'P', 'T', '0', '9', '0', '0', '9', '5', '5', 'V', 'R', 'I', 'D', '0', '7', '8', '1', '0', '4', '5',
		'A', 'T', 'T', 'V', '0', '5', '8', '1', '1', '2', '3', 'V', 'R', 'P', 'T', '0', '7', '6', '1', '1', '8', '1',
		'S', 'G', '2', 'D', '0', '4', '8', '1', '2', '5', '7', 'S', 'G', '3', 'D', '0', '7', '0', '1', '3', '0', '5', 0x1e,
		// File control field
		'0', '0', '0', '0', ';', '&', ' ', ' ', ' ', 0x1f,
		'0', '0', '0', '1', 'D', 'S', 'I', 'D', 'D', 'S', 'I', 'D', 'D', 'S', 'S', 'I', '0', '0', '0', '1', 'D', 'S', 'P', 'M',
		'0', '0', '0', '1', 'F', 'R', 'I', 'D', 'F', 'R', 'I', 'D', 'F', 'O', 'I', 'D', 'F', 'R', 'I', 'D', 'A', 'T', 'T', 'F',
		'F', 'R', 'I', 'D', 'N', 'A', 'T', 'F', 'F', 'R', 'I', 'D', 'F', 'F', 'P', 'T', 'F', 'R', 'I', 'D', 'F', 'S', 'P', 'T',
		'0', '0', '0', '1', 'V', 'R', 'I', 'D', 'V', 'R', 'I', 'D', 'A', 'T', 'T', 'V', 'V', 'R', 'I', 'D', 'V', 'R', 'P', 'T',
		'V', 'R', 'I', 'D', 'S', 'G', '2', 'D', 'V', 'R', 'I', 'D', 'S', 'G', '3', 'D', 0x1e,
		// Record identifier fields
		'0', '5', '0', '0', ';', '&', ' ', ' ', ' ', 'I', 'S', 'O', '/', 'I', 'E', 'C', ' ', '8', '2', '1', '1', ' ',
		'R', 'e', 'c', 'o', 'r', 'd', ' ', 'I', 'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r', 0x1f, 0x1f, '(', 'b', '1', '2', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'D', 'a', 't', 'a', ' ', 'S', 'e', 't', ' ', 'I', 'd', 'e', 'n', 't', 'i',
		'f', 'i', 'c', 'a', 't', 'i', 'o', 'n', 0x1f, 'R', 'C', 'N', 'M', '!', 'R', 'C', 'I', 'D', '!', 'E', 'X', 'P', 'P', '!',
		'I', 'N', 'T', 'U', '!', 'D', 'S', 'N', 'M', '!', 'E', 'D', 'T', 'N', '!', 'U', 'P', 'D', 'N', '!', 'U', 'A', 'D', 'T',
		'!', 'I', 'S', 'D', 'T', '!', 'S', 'T', 'E', 'D', '!', 'P', 'R', 'S', 'P', '!', 'P', 'S', 'D', 'N', '!', 'P', 'R', 'E',
		'D', '!', 'P', 'R', 'O', 'F', '!', 'A', 'G', 'E', 'N', '!', 'C', 'O', 'M', 'T', 0x1f, '(', 'b', '1', '1', ',', 'b', '1',
		'4', ',', '2', 'b', '1', '1', ',', '3', 'A', ',', '2', 'A', '(', '8', ')', ',', 'R', '(', '4', ')', ',', 'b', '1', '1',
		',', '2', 'A', ',', 'b', '1', '1', ',', 'b', '1', '2', ',', 'A', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'D', 'a', 't', 'a', ' ', 's', 'e', 't', ' ', 's', 't', 'r', 'u', 'c', 't',
		'u', 'r', 'e', ' ', 'i', 'n', 'f', 'o', 'r', 'm', 'a', 't', 'i', 'o', 'n', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f,
		'D', 'S', 'T', 'R', '!', 'A', 'A', 'L', 'L', '!', 'N', 'A', 'L', 'L', '!', 'N', 'O', 'M', 'R', '!', 'N', 'O', 'C', 'R',
		'!', 'N', 'O', 'G', 'R', '!', 'N', 'O', 'L', 'R', '!', 'N', 'O', 'I', 'N', '!', 'N', 'O', 'C', 'N', '!', 'N', 'O', 'E',
		'D', '!', 'N', 'O', 'F', 'A', 0x1f, '(', '3', 'b', '1', '1', ',', '8', 'b', '1', '4', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'D', 'a', 't', 'a', ' ', 's', 'e', 't', ' ', 'p', 'a', 'r', 'a', 'm', 'e',
		't', 'e', 'r', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, 'R', 'C', 'N', 'M', '!', 'R', 'C', 'I', 'D', '!', 'H', 'D', 'A', 'T',
		'!', 'V', 'D', 'A', 'T', '!', 'S', 'D', 'A', 'T', '!', 'C', 'S', 'C', 'L', '!', 'D', 'U', 'N', 'I', '!', 'H', 'U', 'N',
		'I', '!', 'P', 'U', 'N', 'I', '!', 'C', 'O', 'U', 'N', '!', 'C', 'O', 'M', 'F', '!', 'S', 'O', 'M', 'F', '!', 'C', 'O',
		'M', 'T', 0x1f, '(', 'b', '1', '1', ',', 'b', '1', '4', ',', '3', 'b', '1', '1', ',', 'b', '1', '4', ',', '4', 'b', '1',
		'1', ',', '2', 'b', '1', '4', ',', 'A', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'i',
		'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, 'R', 'C', 'N', 'M', '!', 'R', 'C', 'I', 'D',
		'!', 'P', 'R', 'I', 'M', '!', 'G', 'R', 'U', 'P', '!', 'O', 'B', 'J', 'L', '!', 'R', 'V', 'E', 'R', '!', 'R', 'U', 'I', 'N', 0x1f,
		'(', 'b', '1', '1', ',', 'b', '1', '4', ',', '2', 'b', '1', '1', ',', '2', 'b', '1', '2', ',', 'b', '1', '1', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 'i',
		'd', 'e', 'n', 't', 'i', 'f', 'i', 'e', 'r', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, 'A', 'G', 'E', 'N', '!', 'F', 'I', 'D', 'N',
		'!', 'F', 'I', 'D', 'S', 0x1f, '(', 'b', '1', '2', ',', 'b', '1', '4', ',', 'b', '1', '2', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', '-', 'A', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'a',
		't', 't', 'r', 'i', 'b', 'u', 't', 'e', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, '*', 'A', 'T', 'T', 'L', '!', 'A', 'T', 'V', 'L', 0x1f,
		'(', 'b', '1', '2', ',', 'A', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', '-', 'A', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'n', 'a',
		't', 'i', 'o', 'n', 'a', 'l', ' ', 'a', 't', 't', 'r', 'i', 'b', 'u', 't', 'e', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, '*', 'A', 'T',
		'T', 'L', '!', 'A', 'T', 'V', 'L', 0x1f, '(', 'b', '1', '2', ',', 'A', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 't', 'o',
		' ', 'f', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'o', 'b', 'j', 'e', 'c', 't', ' ', 'p', 'o', 'i', 'n', 't', 'e', 'r', ' ', 'f', 'i',
		'e', 'l', 'd', 0x1f, '*', 'L', 'N', 'A', 'M', '!', 'R', 'I', 'N', 'D', '!', 'C', 'O', 'M', 'T', 0x1f, '(', 'B', '(', '6', '4',
		')', ',', 'b', '1', '1', ',', 'A', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'F', 'e', 'a', 't', 'u', 'r', 'e', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 't', 'o',
		' ', 's', 'p', 'a', 't', 'i', 'a', 'l', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'p', 'o', 'i', 'n', 't', 'e', 'r', ' ', 'f', 'i',
		'e', 'l', 'd', 0x1f, '*', 'N', 'A', 'M', 'E', '!', 'O', 'R', 'N', 'T', '!', 'U', 'S', 'A', 'G', '!', 'M', 'A', 'S', 'K', 0x1f,
		'(', 'B', '(', '4', '0', ')', ',', '3', 'b', '1', '1', ')', 0x1e,
		'1', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'V', 'e', 'c', 't', 'o', 'r', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'i', 'd', 'e',
		'n', 't', 'i', 'f', 'i', 'e', 'r', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, 'R', 'C', 'N', 'M', '!', 'R', 'C', 'I', 'D', '!', 'R', 'V',
		'E', 'R', '!', 'R', 'U', 'I', 'N', 0x1f, '(', 'b', '1', '1', ',', 'b', '1', '4', ',', 'b', '1', '2', ',', 'b', '1', '1', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'V', 'e', 'c', 't', 'o', 'r', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'a', 't', 't',
		'r', 'i', 'b', 'u', 't', 'e', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, '*', 'A', 'T', 'T', 'L', '!', 'A', 'T', 'V', 'L', 0x1f, '(', 'b',
		'1', '2', ',', 'A', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', 'V', 'e', 'c', 't', 'o', 'r', ' ', 'r', 'e', 'c', 'o', 'r', 'd', ' ', 'p', 'o', 'i',
		'n', 't', 'e', 'r', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, '*', 'N', 'A', 'M', 'E', '!', 'O', 'R', 'N', 'T', '!', 'U', 'S', 'A', 'G',
		'!', 'T', 'O', 'P', 'I', '!', 'M', 'A', 'S', 'K', 0x1f, '(', 'B', '(', '4', '0', ')', ',', '4', 'b', '1', '1', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', '2', '-', 'D', ' ', 'c', 'o', 'o', 'r', 'd', 'i', 'n', 'a', 't', 'e', ' ', 'f', 'i',
		'e', 'l', 'd', 0x1f, '*', 'Y', 'C', 'O', 'O', '!', 'X', 'C', 'O', 'O', 0x1f, '(', '2', 'b', '2', '4', ')', 0x1e,
		'2', '6', '0', '0', ';', '&', ' ', ' ', ' ', '3', '-', 'D', ' ', 'c', 'o', 'o', 'r', 'd', 'i', 'n', 'a', 't', 'e', ' ', '(', 's',
		'o', 'u', 'n', 'd', 'i', 'n', 'g', ' ', 'a', 'r', 'r', 'a', 'y', ')', ' ', 'f', 'i', 'e', 'l', 'd', 0x1f, '*', 'Y', 'C', 'O', 'O',
		'!', 'X', 'C', 'O', 'O', '!', 'V', 'E', '3', 'D', 0x1f, '(', '3', 'b', '2', '4', ')', 0x1e
	};
	
	static final double COMF=10000000;
	static final double SOMF=10;
	
	static String file = "0S000000.000";
	static int intu = 0;
	static String code = "0S";
	static int agen = 3878;
	static int cscl = 10000;
	static int vdat = 23;
	static int duni = 1;
	static int huni = 1;
	
	static int idx;
	static int recs;
	
	static int isols;
	static int conns;
	static int metas;
	static int geos;
	static int edges;
	
	static long hash(long val) {
		byte[] bval = ByteBuffer.allocate(Long.SIZE).putLong(val).array();
		CRC32 crc = new CRC32();
		crc.update(bval);
		return crc.getValue();
	}

	public static int encodeChart(S57map map, HashMap<String, String> meta, byte[] buf) throws IndexOutOfBoundsException, UnsupportedEncodingException {
		
		for (Entry<String, String> entry : meta.entrySet()) {
			try {
				switch (entry.getKey()) {
				case "FILE":
					file = entry.getValue();
					break;
				case "INTU":
					intu = Integer.parseInt(entry.getValue());
					break;
				case "AGEN":
					String[] tokens = entry.getValue().split("/");
					code = tokens[0];
					agen = Integer.parseInt(tokens[1]);
					break;
				case "VDAT":
					vdat = Integer.parseInt(entry.getValue());
					break;
				case "CSCL":
					cscl = Integer.parseInt(entry.getValue());
					break;
				case "DUNI":
					duni = Integer.parseInt(entry.getValue());
					break;
				case "HUNI":
					huni = Integer.parseInt(entry.getValue());
					break;
				}
			} catch (Exception e) {
				System.err.println("Meta data (" + entry.getKey() + "=" + entry.getValue() + "):" + e.getMessage());
				System.exit(-1);
			}
		}
		
		//M_COVR & MNSYS in BB if not in map
		if (!map.features.containsKey(Obj.M_COVR)) {
			S57osm.OSMmeta(map);
		}
		
		S57dat.S57geoms(map);

		byte[] record;
		ArrayList<Fparams> fields;

		isols = conns = metas = geos = edges = 0;
		String date = new SimpleDateFormat("yyyyMMdd").format(Calendar.getInstance().getTime());
		ArrayList<Fparams> ds = new ArrayList<>();
		ds.add(new Fparams(S57field.DSID, new Object[] { 10, 1, 1, intu, file, "1", "0", date, date, "03.1", 1, "ENC", "2.0", 1, agen, "Generated by OpenSeaMap.org" }));
		ds.add(new Fparams(S57field.DSSI, new Object[] { 2, 1, 2, metas, 0, geos, 0, isols, conns, edges, 0 }));
		ArrayList<Fparams> dp = new ArrayList<>();
		dp.add(new Fparams(S57field.DSPM, new Object[] { 20, 2, 2, vdat, vdat, cscl, duni, huni, 1, 1, 10000000, 10, "" }));

		System.arraycopy(header, 0, buf, 0, header.length);
		idx = header.length;
		record = S57dat.encRecord(1, ds);
		System.arraycopy(record, 0, buf, idx, record.length);
		idx += record.length;
		record = S57dat.encRecord(2, dp);
		System.arraycopy(record, 0, buf, idx, record.length);
		idx += record.length;
		recs = 3;

		// Depths
		Object[] depths = new Object[0];
		for (Map.Entry<Long, S57map.Snode> entry : map.nodes.entrySet()) {
			S57map.Snode node = entry.getValue();
			if (node.flg == Nflag.DPTH) {
				Object[] dval = new Object[] { (Math.toDegrees(node.lat) * COMF), (Math.toDegrees(node.lon) * COMF), (node.val * SOMF) };
				depths = Arrays.copyOf(depths, (depths.length + dval.length));
				System.arraycopy(dval, 0, depths, (depths.length - dval.length), dval.length);
			}
		}
		if (depths.length > 0) {
			fields = new ArrayList<>();
			fields.add(new Fparams(S57field.VRID, new Object[] { 110, -2, 1, 1 }));
			fields.add(new Fparams(S57field.SG3D, depths));
			record = S57dat.encRecord(recs++, fields);
			System.arraycopy(record, 0, buf, idx, record.length);
			idx += record.length;
			isols++;
		}

		// Isolated nodes
		for (Map.Entry<Long, S57map.Snode> entry : map.nodes.entrySet()) {
			S57map.Snode node = entry.getValue();
			if (node.flg == Nflag.ISOL) {
				fields = new ArrayList<>();
				fields.add(new Fparams(S57field.VRID, new Object[] { 110, hash(entry.getKey()), 1, 1 }));
				fields.add(new Fparams(S57field.SG2D, new Object[] { (Math.toDegrees(node.lat) * COMF), (Math.toDegrees(node.lon) * COMF) }));
				record = S57dat.encRecord(recs++, fields);
				System.arraycopy(record, 0, buf, idx, record.length);
				idx += record.length;
				isols++;
			}
		}

		// Connected nodes
		for (Map.Entry<Long, S57map.Snode> entry : map.nodes.entrySet()) {
			S57map.Snode node = entry.getValue();
			if (node.flg == Nflag.CONN) {
				fields = new ArrayList<>();
				fields.add(new Fparams(S57field.VRID, new Object[] { 120, hash(entry.getKey()), 1, 1 }));
				fields.add(new Fparams(S57field.SG2D, new Object[] { (Math.toDegrees(node.lat) * COMF), (Math.toDegrees(node.lon) * COMF) }));
				record = S57dat.encRecord(recs++, fields);
				System.arraycopy(record, 0, buf, idx, record.length);
				idx += record.length;
				conns++;
			}
		}

		// Edges
		for (Map.Entry<Long, S57map.Edge> entry : map.edges.entrySet()) {
			S57map.Edge edge = entry.getValue();
			fields = new ArrayList<>();
			fields.add(new Fparams(S57field.VRID, new Object[] { 130, hash(entry.getKey()), 1, 1 }));
			fields.add(new Fparams(S57field.VRPT, new Object[] { (((hash(edge.first) & 0xffffffff) << 8) + 120l), 255, 255, 1, 255, (((hash(edge.last) & 0xffffffff) << 8) + 120l), 255, 255, 2, 255 }));
			Object[] nodes = new Object[0];
			for (long ref : edge.nodes) {
				Object[] nval = new Object[] { (Math.toDegrees(map.nodes.get(ref).lat) * COMF), (Math.toDegrees(map.nodes.get(ref).lon) * COMF) };
				nodes = Arrays.copyOf(nodes, (nodes.length + nval.length));
				System.arraycopy(nval, 0, nodes, (nodes.length - nval.length), nval.length);
			}
			if (nodes.length > 0) {
				fields.add(new Fparams(S57field.SG2D, nodes));
			}
			record = S57dat.encRecord(recs++, fields);
			System.arraycopy(record, 0, buf, idx, record.length);
			idx += record.length;
			edges++;
		}

		// Meta & Geo objects
		boolean soundings = false;
		for (Entry<Obj, ArrayList<Feature>> entry : map.features.entrySet()) {
			Obj obj = entry.getKey();
			for (Feature feature : entry.getValue()) {
				if (obj == Obj.SOUNDG) {
					if (soundings) {
						continue;
					} else {
						soundings = true;
					}
				}
				int prim = feature.geom.prim.ordinal();
				prim = (prim == 0) ? 255 : prim;
				int grup = ((obj == Obj.DEPARE) || (obj == Obj.DRGARE) || (obj == Obj.FLODOC) || (obj == Obj.HULKES) || (obj == Obj.LNDARE) || (obj == Obj.PONTON) || (obj == Obj.UNSARE)) ? 1 : 2;

				ArrayList<Fparams> geom = new ArrayList<>();
				int outers = 0;
				outers = (feature.geom.prim == Pflag.POINT) ? 1 : feature.geom.comps.get(0).size;
				for (Prim elem : feature.geom.elems) {
					if (feature.geom.prim == Pflag.POINT) {
						if (obj == Obj.SOUNDG) {
							geom.add(new Fparams(S57field.FSPT, new Object[] { ((-2 << 8) + 110l), 255, 255, 255 }));
						} else {
							geom.add(new Fparams(S57field.FSPT, new Object[] { ((hash(elem.id) << 8) + ((map.nodes.get(elem.id).flg == Nflag.CONN) ? 120l : 110l)), 255, 255, 255 }));
						}
					} else {
						geom.add(new Fparams(S57field.FSPT, new Object[] { ((hash(elem.id) << 8) + 130l), (elem.forward ? 1 : 2), ((outers-- > 0) ? 1 : 2), 2 }));
					}
				}

				ArrayList<ArrayList<Fparams>> objects = new ArrayList<>();
				ArrayList<Long> slaves = new ArrayList<>();
				long slaveid = feature.id + 0x0100000000000000l;
				for (Entry<Obj, ObjTab> objs : feature.objs.entrySet()) {
					Obj objobj = objs.getKey();
					boolean master = true;
					for (Entry<Integer, AttMap> object : objs.getValue().entrySet()) {
						ArrayList<Fparams> objatts = new ArrayList<>();
						master = (feature.type == objobj) && ((object.getKey() == 0) || (object.getKey() == 1));
						long id = hash(master ? feature.id : slaveid);
						objatts.add(new Fparams(S57field.FRID, new Object[] { 100, id, prim, grup, S57obj.encodeType(objobj), 1, 1 }));
						objatts.add(new Fparams(S57field.FOID, new Object[] { agen, id, 1 }));
						Object[] attf = new Object[0];
						Object[] natf = new Object[0];
						AttMap atts = map.new AttMap();
						atts.putAll(object.getValue());
						if (master) {
							atts.putAll(feature.atts);
						}
						for (Entry<Att, AttVal<?>> att : atts.entrySet()) {
							if (!((obj == Obj.SOUNDG) && (att.getKey() == Att.VALSOU))) {
								long attl = S57att.encodeAttribute(att.getKey());
								Object[] next = new Object[] { attl, S57val.encodeValue(att.getValue(), att.getKey()) };
								if ((attl < 300) || (attl > 304)) {
									attf = Arrays.copyOf(attf, (attf.length + next.length));
									System.arraycopy(next, 0, attf, (attf.length - next.length), next.length);
								} else {
									natf = Arrays.copyOf(natf, (natf.length + next.length));
									System.arraycopy(next, 0, natf, (natf.length - next.length), next.length);
								}
							}
						}
						if (attf.length > 0) {
							objatts.add(new Fparams(S57field.ATTF, attf));
						}
						if (natf.length > 0) {
							objatts.add(new Fparams(S57field.NATF, attf));
						}
						if (master) {
							objects.add(objatts);
						} else {
							slaves.add(id);
							objects.add(0, objatts);
							slaveid += 0x0100000000000000l;
						}
					}
				}

				if (!slaves.isEmpty()) {
					ArrayList<Fparams> refs = new ArrayList<>();
					Object[] params = new Object[0];
					while (!slaves.isEmpty()) {
						long id = slaves.remove(0);
						Object[] next = new Object[] { (long) ((((id & 0xffffffff) + 0x100000000l) << 16) + (agen & 0xffff)), 2, "" };
						params = Arrays.copyOf(params, (params.length + next.length));
						System.arraycopy(next, 0, params, (params.length - next.length), next.length);
					}
					refs.add(new Fparams(S57field.FFPT, params));
					objects.get(objects.size() - 1).addAll(refs);
				}

				for (ArrayList<Fparams> object : objects) {
					object.addAll(geom);
					record = S57dat.encRecord(recs++, object);
					System.arraycopy(record, 0, buf, idx, record.length);
					idx += record.length;
					if ((obj == Obj.M_COVR) || (obj == Obj.M_NSYS)) {
						metas++;
					} else {
						geos++;
					}
				}
			}
		}

		// Re-write DSID/DSSI with final totals
		ds = new ArrayList<>();
		ds.add(new Fparams(S57field.DSID, new Object[] { 10, 1, 1, intu, file, "1", "0", date, date, "03.1", 1, "ENC", "2.0", 1, agen, "Generated by OpenSeaMap.org" }));
		ds.add(new Fparams(S57field.DSSI, new Object[] { 2, 1, 2, metas, 0, geos, 0, isols, conns, edges, 0 }));
		record = S57dat.encRecord(1, ds);
		System.arraycopy(record, 0, buf, header.length, record.length);

		return idx;
	}

}
