/*
 * 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: 18-Jun-2008
 */
package uk.me.parabola.imgfmt.app.trergn;

/**
 * Class to calculate the values that occur at offset 9a in the TRE header.
 * As I don't know the purpose of these the naming is a bit arbitrary here...
 *
 * This was worked out in the display project, so see the TreCalc file
 * there for more history.  This is a cleaned up version of what was
 * written there.
 *
 * @author Steve Ratcliffe
 * @see <a href="http://svn.parabola.me.uk/display/trunk/src/test/display/TreCalc.java">TreCalc.java</a>
 */
public class MapValues {
	private final int mapId;
	private final int length;

	private final byte[][] values = new byte[4][8];

	// Converts the digits in the map id to the values seen in this section.
	private static final byte[] mapIdCodeTable = {
			0, 1, 0xf, 5,
			0xd, 4, 7, 6,
			0xb, 9, 0xe, 8,
			2, 0xa, 0xc, 3
	};

	// Used to work out the required offset that is applied to all the
	// digits of the values.
	private final int[] offsetMap = {
			6, 7, 5, 11,
			3, 10, 13, 12,
			1, 15, 4, 14,
			8, 0, 2, 9
	};

	public MapValues(int mapId, int headerLength) {
		this.mapId = mapId;
		this.length = headerLength;
	}

	/**
	 * There are four values.  Get value n.
	 * @param n Get value n, starting at 0 up to four.
	 */
	public int value(int n) {
		byte[] out = values[n];

		int res = 0;
		for (int i = 0; i < 8; i++) {
			res |= ((out[i] & 0xf) << (4 * (7 - i)));
		}
		return res;
	}

	public void calculate() {
		// Done in this order because the first and second depend on things
		// we have already calculated in three.
		calcThird();
		calcFourth();
		calcFirst();
		calcSecond();

		addOffset();
	}

	/**
	 * Add an offset to all previously calculated values.
	 */
	private void addOffset() {
		// To get the offset value we add up all the even nibbles of the map
		// number and transform via a table.
		int n = mapIdDigit(1) + mapIdDigit(3) + mapIdDigit(5) + mapIdDigit(7);

		int offset = offsetMap[n & 0xf];
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 8; j++) {
				values[i][j] += offset;
			}
		}
	}

	/**
	 * This value is made from the third value, combined with the raw
	 * map id values.
	 */
	private void calcFirst() {
		byte[] out = values[0];
		byte[] v3 = values[3];

		// First bytes are the low bytes of the mapId, with the corresponding
		// value from value[3] added.
		out[0] = (byte) (mapIdDigit(4) + v3[0]);
		out[1] = (byte) (mapIdDigit(5) + v3[1]);
		out[2] = (byte) (mapIdDigit(6) + v3[2]);
		out[3] = (byte) (mapIdDigit(7) + v3[3]);

		// Copies of v3
		out[4] = v3[4];
		out[5] = v3[5];
		out[6] = v3[6];

		// Always (?) one more.  The one likely comes from some other
		// part of the header, but we don't know if or where.
		out[7] = (byte) (v3[7] + 1);
	}

	/**
	 * This is made from various parts of the third value and the raw digits
	 * from the map id.  There are two digits where the header length digits
	 * are used (or that could be a coincidence, but it holds up well so far).
	 */
	private void calcSecond() {
		byte[] out = values[1];
		byte[] v3 = values[3];

		// Just same as in v3
		out[0] = v3[0];
		out[1] = v3[1];

		int h1 = length >> 4;
		int h2 = length;
		out[2] = (byte) ((v3[2] + h1) & 0xf);
		out[3] = (byte) ((v3[3] + h2) & 0xf);

		// The following are the sum of individual nibbles in U3 and the
		// corresponding nibble in the top half of mapId.
		out[4] = (byte) (v3[4] + mapIdDigit(0));
		out[5] = (byte) (v3[5] + mapIdDigit(1));
		out[6] = (byte) (v3[6] + mapIdDigit(2));
		out[7] = (byte) (v3[7] + mapIdDigit(3));
	}

	/**
	 * This is made of the hex digits of the map id in a given order
	 * translated according to a given table of values.
	 */
	private void calcThird() {
		byte[] out = values[2];
		for (int i = 0; i < 8; i++) {
			int n = mapIdDigit(i);
			out[(i ^ 1)] = mapIdCodeTable[n];
		}
	}

	/**
	 * This is just a copy of the third value.
	 */
	private void calcFourth() {
		System.arraycopy(values[2], 0, values[3], 0, values[3].length);
	}

	/**
	 * Extract the given nibble of the map id.  0 is the highest four bits.
	 * @param i The nibble number, 0 most significant, 7 the least.
	 * @return The given nibble of the map id.
	 */
	private int mapIdDigit(int i) {
		return (mapId >>> (4 * (7 - i))) & 0xf;
	}
}
