/*
 * 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 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.
 * 
 * 
 * Author: Steve Ratcliffe
 * Create date: 06-Jul-2008
 */
package uk.me.parabola.imgfmt.app.net;

import java.util.Arrays;

import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.CommonHeader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.ImgFileWriter;
import uk.me.parabola.imgfmt.app.Section;

/**
 * Header information for the NOD file.
 *
 * This is a routing network for the map.
 *
 * @author Steve Ratcliffe
 */
public class NODHeader extends CommonHeader {
	public static final int HEADER_LEN = 127;

	static final char DEF_ALIGN = 6;
	private static final char BOUNDARY_ITEM_SIZE = 9;

	private final Section nodes = new Section();
	private final Section roads = new Section(nodes);
	private final Section boundary = new Section(roads, BOUNDARY_ITEM_SIZE);
	private final Section highClassBoundary = new Section(boundary);
	private final int[] classBoundaries = new int[5];
	private final Section nod5 = new Section();
	private final Section nod6 = new Section();

    private int flags;
    private int align;
    private int mult1;
	private int tableARecordLen;
	private boolean driveOnLeft;

	private int indexIdSize;

	public NODHeader() {
		super(HEADER_LEN, "GARMIN NOD");
		Arrays.fill(classBoundaries, Integer.MAX_VALUE);
	}

	/**
	 * Read the rest of the header.  Specific to the given file.  It is guaranteed
	 * that the file position will be set to the correct place before this is
	 * called.
	 *
	 * @param reader The header is read from here.
	 */
	protected void readFileHeader(ImgFileReader reader) {
        nodes.readSectionInfo(reader, false);
        flags = reader.get2u();
        reader.get2u();
        align = reader.get1u();
        mult1 = reader.get1u();
        tableARecordLen = reader.get2u();
        roads.readSectionInfo(reader, false, true);
        boundary.readSectionInfo(reader, true, true);
		if (getHeaderLength() > 0x3f) {
			highClassBoundary.readSectionInfo(reader, false);

			classBoundaries[0] = reader.get4();
			classBoundaries[1] = classBoundaries[0] + reader.get4();
			classBoundaries[2] = classBoundaries[1] + reader.get4();
			classBoundaries[3] = classBoundaries[2] + reader.get4();
			classBoundaries[4] = classBoundaries[3] + reader.get4();
		}
		if (getHeaderLength() >= 0x7f) {
			reader.position(reader.getGMPOffset() + 0x67);
			nod5.readSectionInfo(reader, false);
			reader.get2u();
			nod6.readSectionInfo(reader, true);
			indexIdSize = Utils.numberToPointerSize(nod6.getNumItems());
		}

	}

	/**
	 * Write the rest of the header.  It is guaranteed that the writer will be set
	 * to the correct position before calling.
	 *
	 * @param writer The header is written here.
	 */
	
	// multiplier shift for road + arc length values, the smaller the shift the higher the precision and NOD size 
	// as it has an influence on the number of bits needed to encode a length
	static final int DISTANCE_MULT_SHIFT = 1; // 0..7  1 seems to be a good compromise
	static final int DISTANCE_MULT = 1 << DISTANCE_MULT_SHIFT;
	protected void writeFileHeader(ImgFileWriter writer) {
		nodes.setPosition(HEADER_LEN);
		nodes.writeSectionInfo(writer);

		// 0x0001 always set, meaning ?
		// 0x0002 (enable turn restrictions)
		// 0x001c meaning ?
		// 0x00E0 distance multiplier, effects predicted travel time
		int flags = 0x0207;
		assert Integer.bitCount(DISTANCE_MULT) == 1;
		assert DISTANCE_MULT_SHIFT < 8;
		flags |= DISTANCE_MULT_SHIFT << 5;
		if(driveOnLeft)
			flags |= 0x0100;
		
		writer.put4(flags);

		writer.put1u(DEF_ALIGN);
		writer.put1u(0); // pointer multiplier
		writer.put2u(5);

		roads.writeSectionInfo(writer);
		writer.put4(0);

		boundary.writeSectionInfo(writer);
		// new fields for header length > 0x3f
		writer.put4(2); // no other value spotted, meaning ?
		highClassBoundary.writeSectionInfo(writer);
		writer.put4(classBoundaries[0]);
		for (int i = 1; i < classBoundaries.length; i++){
			writer.put4(classBoundaries[i] - classBoundaries[i-1]);
		}
	}

	private static final double UNIT_TO_METER = 2.4;
	public static int metersToRaw(double m) {
		double d = m / (DISTANCE_MULT * UNIT_TO_METER);
		return (int) Math.round(d);
	}
	
    public int getNodeStart() {
        return nodes.getPosition();
    }

	public void setNodeStart(int start) {
		nodes.setPosition(start);
	}

    public int getNodeSize() {
        return nodes.getSize();
    }

	public void setNodeSize(int size) {
		nodes.setSize(size);
	}

	public Section getNodeSection() {
		return nodes;
	}

	public void setRoadSize(int size) {
		roads.setSize(size);
	}

	public Section getRoadSection() {
		return roads;
	}

	public void setBoundarySize(int size) {
		boundary.setSize(size);
	}

	public Section getBoundarySection() {
		return boundary;
	}

	public void setHighClassBoundarySize(int size) {
		highClassBoundary.setSize(size);
	}
	public Section getHighClassBoundary() {
		return highClassBoundary;
	}

	public int[] getClassBoundaries() {
		return classBoundaries;
	}

	public void setDriveOnLeft(boolean dol) {
		driveOnLeft = dol;
	}

    public int getFlags() {
        return flags;
    }

    public int getAlign() {
        return align;
    }

	public int getMult1() {
		return mult1;
	}

	public int getTableARecordLen() {
		return tableARecordLen;
	}

	/**
	 * @return the indexIdSize
	 */
	public int getIndexIdSize() {
		return indexIdSize;
	}
}
